Skip to content
getnextpdf.com

Pro edition

Webview — Deep Reference

This page documents the public NextPDF\Pro\Webview surface, the byte-range request/response model, and the exact failure modes beyond the public landing page.

This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license.

There is no per-feature license flag; the code ships with the Pro edition. The PSR-17 ResponseFactoryInterface / StreamFactoryInterface and the body media type are runtime constructor parameters, not licensing controls.

Terminal window
composer require nextpdf/pro

Public types under NextPDF\Pro\Webview:

  • LinearizedDocument — a validated linearized PDF prepared for delivery.
  • ByteRangeResponder — the RFC 9110 byte-range HTTP responder.
  • ByteRange — one satisfiable inclusive byte range over a representation.
  • FirstPageProber — the structural “first page before full download” proof.

Exception types under NextPDF\Pro\Webview\Exception:

  • WebviewException (marker interface), UnsupportedDocumentException, RangeNotSatisfiableException.

A final readonly class with a private constructor; instantiate via the named constructor.

  • static fromBytes(string $bytes): self — parses the bytes through Core’s read-side LinearizationView::fromPdf(). Throws UnsupportedDocumentException when the document is not linearized, when the declared /L does not match the actual byte length, or when the /E end-of-first-page offset is not a positive offset within the file.
  • length(): int — the document length in bytes.
  • firstPagePrefixLength(): int — the minimal leading prefix that contains the complete first page: the /E offset, clamped to the file length.
  • firstPageByteRange(): ByteRange — the inclusive range [0, /E - 1] that delivers the first page. Throws RangeNotSatisfiableException only if the prefix is empty (defence-in-depth; fromBytes() already guarantees 0 < /E <= length).
  • slice(int $firstByte, int $lastByte): string — strict programmatic slice with inclusive offsets; throws RangeNotSatisfiableException when out of bounds.
  • etag(): string — the strong, deterministic SHA-256 entity-tag for the bytes, memoised once at construction.

Public readonly properties: bytes (the raw PDF bytes) and view (the Core LinearizationView).

A final readonly class implemented against PSR-7 / PSR-17 only.

  • __construct(ResponseFactoryInterface $responses, StreamFactoryInterface $streams, string $contentType = 'application/pdf') — throws InvalidArgumentException when $contentType contains control characters (it is interpolated into response and multipart part headers; CR/LF and other control bytes are rejected to prevent header injection).
  • respond(LinearizedDocument $document, ServerRequestInterface $request): ResponseInterface — answers a range request for a linearized document, using the document’s own ETag.
  • respondToBytes(string $bytes, ServerRequestInterface $request, ?string $etag = null): ResponseInterface — answers a range request for arbitrary bytes (a representation that should support ranges without being linearized). The ETag is derived from the bytes when null.
  • firstPageResponse(LinearizedDocument $document): ResponseInterface — builds a 206 carrying exactly the first page’s byte range; the server-push form of “first page before full download”.

A final readonly value object for a single satisfiable inclusive byte range (RFC 9110 §14.1.2).

  • __construct(int $firstByte, int $lastByte, int $contentLength) — enforces satisfiability 0 <= firstByte <= lastByte <= contentLength - 1; throws RangeNotSatisfiableException otherwise.
  • length(): int — the inclusive span (lastByte - firstByte + 1), always >= 1.
  • contentRange(): string — the RFC 9110 §14.4 Content-Range field value bytes first-last/length.

Public readonly properties: firstByte, lastByte, contentLength.

A final readonly class constructed from a LinearizedDocument.

  • prefixLength(): int — minimal leading byte count needed to render the first page.
  • prefixFraction(): float — fraction of the whole file (0.0–1.0) that the prefix represents; returns 1.0 for a zero-length file.
  • hintStreamWithinPrefix(): bool — whether the primary hint stream object lies fully inside the first-page prefix (so the prefix alone lets a reader locate page-1 objects). The hint stream must have positive length.
  • isFirstPageSelfContained(): bool — the combined structural proof: a positive prefix that fits within the file and fully contains the hint stream.

ByteRangeResponder follows RFC 9110 §14. After computing the length and a strong SHA-256 ETag, it reads the Range and If-Range headers and decides:

ConditionStatusNotes
No applicable Range, or If-Range does not match the current strong ETag200 OKFull body. Only the strong entity-tag form of If-Range is honoured (RFC 9110 §13.1.5).
Unrecognised range unit or syntactically invalid Range200 OKThe header is ignored (RFC 9110 §14.2).
One satisfiable range206 Partial ContentCarries Content-Range.
Multiple satisfiable ranges206 Partial Contentmultipart/byteranges with a derived boundary.
Valid byte ranges, none satisfiable416 Range Not SatisfiableCarries Content-Range: bytes */length (RFC 9110 §15.3.7).

Every response advertises Accept-Ranges: bytes and the strong ETag. 200 and 206 responses also set Content-Type and Content-Length.

Range parsing accepts the bytes= unit only. It supports explicit first-last, open-ended first- (clamped to the end), and suffix -N (the final N bytes; a suffix at least as large as the representation selects the whole of it). A bare - (or any otherwise malformed spec) makes the whole Range header syntactically invalid, so the header is ignored and the full 200 OK representation is returned. A -0 suffix, or any spec whose first offset is at or beyond the end, is an unsatisfiable spec and is dropped; if no spec in the header is satisfiable the response is 416 Range Not Satisfiable. Large decimal offsets are compared without relying on integer-overflow saturation, so a 30-digit Range value is handled platform-independently. Overlapping satisfiable ranges are coalesced before any body is built; genuinely distinct (non-overlapping) ranges are preserved as separate multipart parts.

WebviewException is a marker interface extending Throwable; catch it to handle the whole subsystem uniformly. Both concrete exceptions implement it.

  • UnsupportedDocumentException (extends InvalidArgumentException) — raised by LinearizedDocument::fromBytes() when the bytes are not a usable linearized document. Named constructors: notLinearized() (no /Linearized parameter dictionary), lengthMismatch($declaredLength, $actualLength) (declared /L does not match the actual length — truncated, appended-to via an incremental update beyond /L, or non-conformant), and malformedFirstPageOffset($firstPageEndOffset, $length) (the /E offset is not a positive offset within the file).
  • RangeNotSatisfiableException (extends OutOfRangeException) — the programmatic-slice error, raised by ByteRange::__construct() and LinearizedDocument::slice() / firstPageByteRange() when an inclusive range falls outside the document. Named constructor: outOfBounds($firstByte, $lastByte, $length).

The HTTP responder does not throw RangeNotSatisfiableException for client Range headers — an unsatisfiable HTTP range is a 416 response (RFC 9110 §15.3.7), not an exception. That exception is reserved for direct programmatic slicing, where an out-of-bounds request is a caller error. ByteRangeResponder::__construct() throws a plain InvalidArgumentException (not a WebviewException) when the configured contentType contains control characters.

The responder caps the number of distinct coalesced ranges honoured per request (the multipart range-amplification class, Apache HTTPD CVE-2011-3192). When a request asks for more than the cap of coalesced ranges, or for more total bytes than the whole representation, the Range is ignored and a full 200 is returned. The multipart boundary is derived deterministically and re-derived until it is guaranteed not to occur inside the body, preserving reproducible output while ruling out boundary collision.

Byte-range behavior follows RFC 9110 (HTTP Semantics): §14 (range requests), §13.1.5 (If-Range), §14.4 (Content-Range), and §15.3.7 (416). The linearized-document layout is the Fast Web View model of ISO 32000-2 Annex F. The module asserts no further external clause identifiers beyond behavior verified by its tests.

  • respondToBytes() serves ranges over arbitrary bytes when first-page semantics are not needed.
  • An If-Range HTTP-date validator is treated as a non-match → full 200 (the client simply re-fetches).
  • The ETag is a SHA-256 hash used purely as a strong cache validator; this module performs no signing or other cryptographic operations and defines no FIPS-specific behavior.

This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.