Pro 版本
Webview — 深度参考
本页记录公开的 NextPDF\Pro\Webview 接口面、字节范围请求/响应模型,以及公开着陆页之外的确切失败模式。
可用性与许可
标题为“可用性与许可”的章节此能力随 NextPDF Pro(nextpdf/pro)交付,并以 Pro 层级的许可信封激活。没有该授权的部署不会加载此能力的类。比较各版本并获取授权。
不存在逐功能的许可标志;代码随 Pro 版本交付。PSR-17 ResponseFactoryInterface / StreamFactoryInterface 与正文媒体类型是运行时构造函数参数,而非许可控制项。
公共 API 接口面
标题为“公共 API 接口面”的章节composer require nextpdf/proNextPDF\Pro\Webview 下的公共类型:
LinearizedDocument——一份经校验、为投递准备好的线性化 PDF。ByteRangeResponder——RFC 9110 字节范围 HTTP responder。ByteRange——在一个表示上的一个可满足的包含式字节范围。FirstPageProber——结构性的”整份下载完成前先给第一页”证明。
NextPDF\Pro\Webview\Exception 下的异常类型:
WebviewException(标记接口)、UnsupportedDocumentException、RangeNotSatisfiableException。
LinearizedDocument
标题为“LinearizedDocument”的章节一个带私有构造函数的 final readonly 类;通过命名构造函数实例化。
static fromBytes(string $bytes): self——通过 Core 读取侧的LinearizationView::fromPdf()解析字节。当文档未线性化、声明的/L与实际字节长度不匹配,或/E第一页结束偏移量不是文件内的一个正偏移量时,抛出UnsupportedDocumentException。length(): int——以字节计的文档长度。firstPagePrefixLength(): int——包含完整第一页的最小开头前缀:被钳制到文件长度的/E偏移量。firstPageByteRange(): ByteRange——投递第一页的包含式范围[0, /E - 1]。仅当前缀为空时抛出RangeNotSatisfiableException(纵深防御;fromBytes()已保证0 < /E <= length)。slice(int $firstByte, int $lastByte): string——带包含式偏移量的严格程序化切片;越界时抛出RangeNotSatisfiableException。etag(): string——对字节的强、确定性 SHA-256 实体标签,在构造时记忆化一次。
公共只读属性:bytes(原始 PDF 字节)与 view(Core 的 LinearizationView)。
ByteRangeResponder
标题为“ByteRangeResponder”的章节一个仅依据 PSR-7 / PSR-17 实现的 final readonly 类。
__construct(ResponseFactoryInterface $responses, StreamFactoryInterface $streams, string $contentType = 'application/pdf')——当$contentType含有控制字符时抛出InvalidArgumentException(它会被插值进响应头与 multipart 部件头;CR/LF 及其他控制字节被拒绝以防止头注入)。respond(LinearizedDocument $document, ServerRequestInterface $request): ResponseInterface——用文档自身的ETag应答一个针对线性化文档的范围请求。respondToBytes(string $bytes, ServerRequestInterface $request, ?string $etag = null): ResponseInterface——应答一个针对任意字节的范围请求(一个应当支持范围、但无需线性化的表示)。当etag为null时,ETag从字节派生。firstPageResponse(LinearizedDocument $document): ResponseInterface——构建一个恰好携带第一页字节范围的206;即”整份下载完成前先给第一页”的服务器推送形式。
ByteRange
标题为“ByteRange”的章节一个用于单个可满足的包含式字节范围的 final readonly 值对象(RFC 9110 §14.1.2)。
__construct(int $firstByte, int $lastByte, int $contentLength)——强制可满足性0 <= firstByte <= lastByte <= contentLength - 1;否则抛出RangeNotSatisfiableException。length(): int——包含式跨度(lastByte - firstByte + 1),始终>= 1。contentRange(): string——RFC 9110 §14.4 的Content-Range字段值bytes first-last/length。
公共只读属性:firstByte、lastByte、contentLength。
FirstPageProber
标题为“FirstPageProber”的章节一个由 LinearizedDocument 构造的 final readonly 类。
prefixLength(): int——渲染第一页所需的最小开头字节数。prefixFraction(): float——该前缀所占整份文件的比例(0.0–1.0);对一份零长度文件返回1.0。hintStreamWithinPrefix(): bool——主提示流对象是否完整地位于第一页前缀之内(使该前缀本身就让一个阅读器能定位第 1 页对象)。提示流必须有正的长度。isFirstPageSelfContained(): bool——组合后的结构性证明:一个正的前缀,它既容纳于文件之内,又完整地包含提示流。
字节范围请求/响应模型
标题为“字节范围请求/响应模型”的章节ByteRangeResponder 遵循 RFC 9110 §14。在计算长度与一个强 SHA-256 ETag 之后,它读取 Range 与 If-Range 头并作出决定:
| 条件 | 状态 | 说明 |
|---|---|---|
无适用的 Range,或 If-Range 与当前强 ETag 不匹配 | 200 OK | 完整正文。仅 If-Range 的强实体标签形式被采纳(RFC 9110 §13.1.5)。 |
无法识别的范围单位或语法上无效的 Range | 200 OK | 该头被忽略(RFC 9110 §14.2)。 |
| 一个可满足的范围 | 206 Partial Content | 携带 Content-Range。 |
| 多个可满足的范围 | 206 Partial Content | multipart/byteranges,带一个派生的边界。 |
| 有效字节范围,无一可满足 | 416 Range Not Satisfiable | 携带 Content-Range: bytes */length(RFC 9110 §15.3.7)。 |
每个响应都通告 Accept-Ranges: bytes 与强 ETag。200 与 206 响应还会设置 Content-Type 与 Content-Length。
范围解析仅接受 bytes= 单位。它支持显式 first-last、开放式 first-(钳制到末尾),以及后缀 -N(最后的 N 字节;一个至少与表示一样大的后缀会选中其整体)。一个裸的 -(或任何其他格式错误的规格)会使整个 Range 头语法上无效,因此该头被忽略并返回完整的 200 OK 表示。一个 -0 后缀,或任何其首偏移量位于末尾处或之后的规格,是一个不可满足的规格并被丢弃;如果头中没有任何规格可满足,则响应为 416 Range Not Satisfiable。大的十进制偏移量在比较时不依赖整数溢出饱和,因此一个 30 位的 Range 值会被以与平台无关的方式处理。重叠的可满足范围会在构建任何正文之前被合并;真正不同(不重叠)的范围会被保留为各自独立的 multipart 部件。
失败模式与异常模型
标题为“失败模式与异常模型”的章节WebviewException 是一个继承 Throwable 的标记接口;捕获它即可统一处理整个子系统。两个具体异常都实现它。
UnsupportedDocumentException(继承InvalidArgumentException)——当字节不是一份可用的线性化文档时,由LinearizedDocument::fromBytes()引发。命名构造函数:notLinearized()(无/Linearized参数字典)、lengthMismatch($declaredLength, $actualLength)(声明的/L与实际长度不匹配——被截断、通过一次增量更新被追加到超出/L,或不符合规范),以及malformedFirstPageOffset($firstPageEndOffset, $length)(/E偏移量不是文件内的一个正偏移量)。RangeNotSatisfiableException(继承OutOfRangeException)——程序化切片错误,当一个包含式范围落在文档之外时,由ByteRange::__construct()与LinearizedDocument::slice()/firstPageByteRange()引发。命名构造函数:outOfBounds($firstByte, $lastByte, $length)。
HTTP responder 对客户端 Range 头不抛出 RangeNotSatisfiableException——一个不可满足的 HTTP 范围是一个 416 响应(RFC 9110 §15.3.7),而非一个异常。那个异常保留给直接的程序化切片,在那里一个越界请求是调用方错误。当配置的 contentType 含有控制字符时,ByteRangeResponder::__construct() 抛出一个普通的 InvalidArgumentException(不是一个 WebviewException)。
拒绝服务加固
标题为“拒绝服务加固”的章节responder 对每个请求所采纳的不同合并范围数量设有上限(multipart 范围放大这一类,Apache HTTPD CVE-2011-3192)。当一个请求要求的合并范围数多于该上限,或要求的总字节数多于整份表示时,Range 会被忽略,并返回一个完整的 200。multipart 边界被确定性地派生,并被反复重新派生直到它保证不会出现在正文内部,从而在排除边界碰撞的同时保持可复现的输出。
一致性
标题为“一致性”的章节字节范围行为遵循 RFC 9110(HTTP Semantics):§14(范围请求)、§13.1.5(If-Range)、§14.4(Content-Range),以及 §15.3.7(416)。线性化文档布局是 ISO 32000-2 Annex F 的 Fast Web View 模型。本模块除其测试所验证的行为之外,不主张任何进一步的外部条款标识。
边界情况与 FIPS 模式行为
标题为“边界情况与 FIPS 模式行为”的章节- 当不需要第一页语义时,
respondToBytes()在任意字节上提供范围。 - 一个
If-RangeHTTP-date 验证器被视为不匹配 → 完整的200(客户端只需重新抓取)。 ETag是一个纯粹用作强缓存验证器的 SHA-256 哈希;本模块不执行任何签名或其他密码学操作,也不定义任何 FIPS 特定行为。
发布边界
标题为“发布边界”的章节本页仅记录外部可观察的行为与受支持的公共 API 接口面。内部命名空间路径、辅助类、机制表、runbook 文件名与工单前缀不在范围内。