跳转到内容
getnextpdf.com

Pro 版本

Webview

Webview 通过 HTTP 投递一份线性化(Fast Web View)PDF,使客户端能从一小段开头前缀开始渲染第 1 页,而文件其余部分仍在传输途中。它把原始字节包装成一个 LinearizedDocument,通过一个 PSR-7 ByteRangeResponder 以 RFC 9110 部分内容响应来应答 Range 请求,并能(通过 FirstPageProber)证明第一页在该前缀中是自包含的。

此功能随 NextPDF Pronextpdf/pro)提供,并通过 Pro 层级的许可证信封激活。没有该授权的部署不会加载此功能的类。比较版本并获取许可证

没有单独的单项功能许可标志。responder 在运行时被接线到你自己的 PSR-17 工厂——一个 ResponseFactoryInterface 和一个 StreamFactoryInterface——媒体类型默认为 application/pdf,且作为一个构造函数参数,而非许可开关。

Terminal window
composer require nextpdf/pro

代码位于 NextPDF\Pro\Webview 命名空间下。

一份线性化 PDF 的布局方式是:让文档的第一页——线性化参数字典、主提示流(primary hint stream),以及第 1 页的对象——位于一个在 /E 偏移量处结束的开头区段。Webview 把那种布局转化为渐进式投递。

LinearizedDocument::fromBytes() 通过 Core 读取侧的 LinearizationView 解析字节(Pro 绝不重新实现线性化解析),并拒绝任何不是可用线性化文档的内容:完全未线性化、声明的 /L 长度与真实字节长度不匹配,或 /E 第一页结束偏移量不是文件内的一个正偏移量。因此构造是全函数式(total)的——一旦你持有一个 LinearizedDocument,它暴露的每一个偏移量都是可信的。

随后 ByteRangeResponder 应答一个 HTTP 请求。它仅依据 PSR-7 / PSR-17 实现,不与任何框架耦合。它始终通告 Accept-Ranges: bytes 和一个强、确定性的 SHA-256 ETag,依据 RFC 9110 §14 解析客户端的 Range 头,并返回一个完整的 200 OK、一个 206 Partial Content 单范围、一个用于多个范围的 206 multipart/byteranges 响应,或一个 416 Range Not Satisfiable

FirstPageProber 是结构性证明侧:它量化第一页前缀、该前缀占整份文件的比例,以及主提示流是否完整地位于其内——正是后者这个属性,让一个阅读器仅凭该前缀就能定位第 1 页的对象。

Webview 从不自己重新解析线性化。它借用 Core 读取侧的 LinearizationView,因此投递层继承的是一个经过审计的解析器,而非第二份会逐渐漂移的副本。构造刻意做成全函数式的。LinearizedDocument::fromBytes() 在一开始就拒绝畸形布局,因此一个 Range 响应所信任的每一个偏移量都已先被校验。responder 只讲 PSR-7 和 PSR-17,所以同一份代码可从任何 HTTP 栈提供一份线性化 PDF。正是这种纪律,使渐进式范围投递能安全地大规模暴露给不可信客户端。

设计背景:大规模文档生成

  1. 从渲染后的 PDF 字节构建一个 LinearizedDocument。无效输入会在一开始就引发 UnsupportedDocumentException
  2. 把该文档与入站的 PSR-7 ServerRequestInterface 交给 ByteRangeResponder::respond()。responder 读取 Range(以及可选的 If-Range 前置条件),并产出正确的 PSR-7 ResponseInterface
  3. 客户端先请求开头前缀(或者你用 firstPageResponse() 推送它),渲染第 1 页,然后随着用户滚动再请求其余范围。

字节范围模型依据 RFC 9110 §14.1.2 使用包含式偏移量:一个 ByteRange 是在一个 contentLength 的表示上的 firstBytelastByte,其 Content-Range 字段是 bytes first-last/length

  • LinearizedDocument::fromBytes() 是全函数式的:一份非线性化文档、一处 /L 不匹配,或一个非正/越出文件的 /E 偏移量,都会引发 UnsupportedDocumentException,而不会产出一份不安全的文档。
  • ETag 是对确切字节的一个强 SHA-256 实体标签,在构造时记忆化一次。相同的渲染输入产出相同的字节,因而产出相同的 ETag,所以缓存与 If-Range 的行为是可预期的。
  • 一个没有适用 Range 的请求返回带完整正文的 200 OK。一个与当前强 ETag 不匹配的 If-Range 会导致 Range 被忽略并返回一个完整的 200(RFC 9110 §13.1.5)。仅 If-Range 的强实体标签形式被采纳;一个 HTTP-date 形式的 If-Range 被视为不匹配。
  • 一个无法识别的范围单位或一个语法上无效的 Range 会被忽略,并返回一个完整的 200(RFC 9110 §14.2)。
  • 一个可满足的范围返回带 Content-Range206 Partial Content;多个可满足的范围返回 206 multipart/byteranges。有效字节范围但无一可满足时返回带 Content-Range: bytes */length416(RFC 9110 §15.3.7)。
  • firstPageResponse() 发出一个恰好携带第一页字节范围 [0, /E - 1]206——即“整份下载完成前先给第一页”的服务器推送形式。

以下反映已记录的公开 API。该仓库不为本模块提供可运行示例。

use NextPDF\Pro\Webview\LinearizedDocument;
use NextPDF\Pro\Webview\ByteRangeResponder;
$document = LinearizedDocument::fromBytes($pdfBytes);
$responder = new ByteRangeResponder($responseFactory, $streamFactory);
$response = $responder->respond($document, $request);
use NextPDF\Pro\Webview\LinearizedDocument;
use NextPDF\Pro\Webview\ByteRangeResponder;
use NextPDF\Pro\Webview\FirstPageProber;
use NextPDF\Pro\Webview\Exception\UnsupportedDocumentException;
try {
$document = LinearizedDocument::fromBytes($pdfBytes);
} catch (UnsupportedDocumentException $e) {
// Not a usable linearized document — fall back to plain full delivery.
// ...
return;
}
$prober = new FirstPageProber($document);
if ($prober->isFirstPageSelfContained()) {
// Push exactly the first page's bytes for an instant render.
$response = (new ByteRangeResponder($responseFactory, $streamFactory))
->firstPageResponse($document);
}
  • Webview 需要一份真正线性化的 PDF。如果渲染后的文档未线性化,请在渲染时启用线性化,或用普通完整投递来提供它——当你只需要范围支持、而不需要第一页语义时,respondToBytes() 仍可在任意(非线性化)字节上提供范围。
  • 增量更新事关重大:一份被追加到超出其声明 /L 的文档会被作为长度不匹配而拒绝,因为字节范围偏移量将不再可信。
  • responder 对每个请求所采纳的不同范围数量设有上限。一个请求要求的合并范围数多于该上限,或要求的总字节数多于整份表示时,其 Range 会被忽略,并被提供一个完整的 200

第一页前缀是被钳制到文件长度的 /E 第一页结束偏移量,因此 FirstPageProber::prefixFraction() 报告相对于整份文件而言初始抓取有多小——对于一份多页文档,这正是 Fast Web View 的全部意义所在。响应构建会切取内存中的字节串;成本与所选字节成正比。ETag 每份文档计算一次。请用有代表性的文档测量。

请把输入视为不可信。LinearizedDocument::fromBytes() 在任何偏移量被使用之前校验线性化不变量。responder 拒绝含有控制字符的 contentType 以防止头注入,派生一个保证不会出现在正文内部的 multipart 边界,并合并重叠范围、限定其数量与总大小,以防御 multipart 范围放大这一类拒绝服务(Apache HTTPD CVE-2011-3192)。本模块不记录任何文档内容。

字节范围投递遵循 RFC 9110(HTTP Semantics)——§14 用于范围请求、§13.1.5 用于 If-Range,以及 §15.3.7 用于 416。线性化文档模型是 ISO 32000-2 Annex F 所描述的 Fast Web View 布局。本模块除其测试所验证的行为之外,不主张任何进一步的外部条款标识。

Enterprise 不改变 Webview 行为。Enterprise 增加更高层级的合规与归档功能,另行记录;它们并非通过字节范围提供一份线性化 PDF 所必需。

本页仅记录外部可观察的行为以及受支持的公开 API 表面。内部命名空间路径、辅助类、机制表格、runbook 文件名和工单前缀均不在范围内。