Pro edition
Webview — Deep Reference
At a glance
Section titled “At a glance”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.
Availability & licensing
Section titled “Availability & licensing”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.
Public API surface
Section titled “Public API surface”composer require nextpdf/proPublic 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.
LinearizedDocument
Section titled “LinearizedDocument”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-sideLinearizationView::fromPdf(). ThrowsUnsupportedDocumentExceptionwhen the document is not linearized, when the declared/Ldoes not match the actual byte length, or when the/Eend-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/Eoffset, clamped to the file length.firstPageByteRange(): ByteRange— the inclusive range[0, /E - 1]that delivers the first page. ThrowsRangeNotSatisfiableExceptiononly if the prefix is empty (defence-in-depth;fromBytes()already guarantees0 < /E <= length).slice(int $firstByte, int $lastByte): string— strict programmatic slice with inclusive offsets; throwsRangeNotSatisfiableExceptionwhen 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).
ByteRangeResponder
Section titled “ByteRangeResponder”A final readonly class implemented against PSR-7 / PSR-17 only.
__construct(ResponseFactoryInterface $responses, StreamFactoryInterface $streams, string $contentType = 'application/pdf')— throwsInvalidArgumentExceptionwhen$contentTypecontains 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 ownETag.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). TheETagis derived from the bytes whennull.firstPageResponse(LinearizedDocument $document): ResponseInterface— builds a206carrying exactly the first page’s byte range; the server-push form of “first page before full download”.
ByteRange
Section titled “ByteRange”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 satisfiability0 <= firstByte <= lastByte <= contentLength - 1; throwsRangeNotSatisfiableExceptionotherwise.length(): int— the inclusive span (lastByte - firstByte + 1), always>= 1.contentRange(): string— the RFC 9110 §14.4Content-Rangefield valuebytes first-last/length.
Public readonly properties: firstByte, lastByte, contentLength.
FirstPageProber
Section titled “FirstPageProber”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; returns1.0for 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.
Byte-range request/response model
Section titled “Byte-range request/response model”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:
| Condition | Status | Notes |
|---|---|---|
No applicable Range, or If-Range does not match the current strong ETag | 200 OK | Full body. Only the strong entity-tag form of If-Range is honoured (RFC 9110 §13.1.5). |
Unrecognised range unit or syntactically invalid Range | 200 OK | The header is ignored (RFC 9110 §14.2). |
| One satisfiable range | 206 Partial Content | Carries Content-Range. |
| Multiple satisfiable ranges | 206 Partial Content | multipart/byteranges with a derived boundary. |
| Valid byte ranges, none satisfiable | 416 Range Not Satisfiable | Carries 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.
Failure modes & exception model
Section titled “Failure modes & exception model”WebviewException is a marker interface extending Throwable; catch it to handle the whole subsystem uniformly. Both concrete exceptions implement it.
UnsupportedDocumentException(extendsInvalidArgumentException) — raised byLinearizedDocument::fromBytes()when the bytes are not a usable linearized document. Named constructors:notLinearized()(no/Linearizedparameter dictionary),lengthMismatch($declaredLength, $actualLength)(declared/Ldoes not match the actual length — truncated, appended-to via an incremental update beyond/L, or non-conformant), andmalformedFirstPageOffset($firstPageEndOffset, $length)(the/Eoffset is not a positive offset within the file).RangeNotSatisfiableException(extendsOutOfRangeException) — the programmatic-slice error, raised byByteRange::__construct()andLinearizedDocument::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.
Denial-of-service hardening
Section titled “Denial-of-service hardening”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.
Conformance
Section titled “Conformance”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.
Edge cases & FIPS-mode behavior
Section titled “Edge cases & FIPS-mode behavior”respondToBytes()serves ranges over arbitrary bytes when first-page semantics are not needed.- An
If-RangeHTTP-date validator is treated as a non-match → full200(the client simply re-fetches). - The
ETagis 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.
Publication boundary
Section titled “Publication boundary”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.