跳到內容
getnextpdf.com

Pro 版本

Webview

Webview 會在 HTTP 上提供一份已線性化(Fast Web View)的 PDF,讓用戶端能從一段短小的前導前綴(leading prefix)開始算繪第 1 頁,而檔案其餘部分仍在傳輸途中。它會把原始位元組包裹為一個 LinearizedDocument,透過一個 PSR-7 ByteRangeResponder 以 RFC 9110 partial-content 回應來回答 Range 請求,並能(透過 FirstPageProber)證明第一頁在該前綴中是自足的。

此能力隨附於 NextPDF Pronextpdf/pro),並以一份 Pro 級授權封裝啟用。一個沒有該權利的部署不會載入此能力的類別。比較版本並取得授權

沒有獨立的個別功能授權旗標。responder 會在執行階段接上你自己的 PSR-17 工廠——一個 ResponseFactoryInterface 與一個 StreamFactoryInterface——而媒體型別預設為 application/pdf,是一個建構子引數,而非授權開關。

Terminal window
composer require nextpdf/pro

程式碼位於 NextPDF\Pro\Webview 命名空間下。

一份線性化的 PDF 是這樣排版的:文件的第一頁——linearization 參數字典、主要的 hint stream,以及第 1 頁的物件——位於一個結束於 /E 偏移的前導區段。Webview 把那種排版轉成漸進式交付。

LinearizedDocument::fromBytes() 會透過 Core 的讀取端 LinearizationView 解析這些位元組(Pro 絕不重新實作 linearization 解析),並拒絕任何不是可用線性化文件的東西:根本未線性化、宣告的 /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 是結構性證明這一側:它量化第一頁前綴、該前綴占整份檔案的比例,以及主要的 hint stream 是否完全落在其內——這正是讓讀取端能僅從前綴定位第 1 頁物件的屬性。

Webview 絕不自行重新解析 linearization。它借用 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 頁,再隨使用者捲動而請求其餘範圍。

byte-range 模型依 RFC 9110 §14.1.2 使用**包含式(inclusive)**偏移:一個 ByteRange 是某個 contentLength 表示上的 firstBytelastByte,而其 Content-Range 欄位為 bytes first-last/length

  • LinearizedDocument::fromBytes() 是全函式的:一份非線性化文件、一個 /L 不符,或一個非正值/越界的 /E 偏移,都各自引發 UnsupportedDocumentException,而非產生一份不安全的文件。
  • ETag 是涵蓋確切位元組的一個強 SHA-256 entity-tag,在建構時記憶化一次。相同的算繪輸入會產生相同的位元組,因而產生相同的 ETag,使快取與 If-Range 的行為可預期。
  • 一個沒有任何適用 Range 的請求會回傳帶有完整主體的 200 OK。一個與當前強 ETag 不符的 If-Range 會導致 Range 被忽略並回傳一個完整的 200(RFC 9110 §13.1.5)。只有強 entity-tag 形式的 If-Range 會被遵守;一個 HTTP-date 的 If-Range 會被視為不符。
  • 一個無法辨識的範圍單位或一個語法上無效的 Range 會被忽略,並回傳一個完整的 200(RFC 9110 §14.2)。
  • 一個可滿足的範圍會回傳帶有 Content-Range206 Partial Content;多個可滿足的範圍會回傳 206 multipart/byteranges。有效但無一可滿足的 byte range 會回傳 416,並帶有 Content-Range: bytes */length(RFC 9110 §15.3.7)。
  • firstPageResponse() 會發出一個恰好攜帶第一頁 byte range [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。如果算繪後的文件未線性化,請在算繪時啟用 linearization,或以一般的完整交付提供它——當你只需要範圍支援、而非第一頁語意時,respondToBytes() 仍能在任意(非線性化)位元組上提供範圍。
  • 增量更新很重要:一份被附加到超出其宣告 /L 之外的文件會以長度不符被拒絕,因為 byte-range 偏移將不再可信。
  • responder 會為每個請求所遵守的相異範圍數目設上限。一個請求若要求比上限更多的合併(coalesced)範圍,或比整份表示更多的總位元組,其 Range 會被忽略,並改服務一個完整的 200

第一頁前綴是 /E 第一頁結尾偏移夾限(clamp)至檔案長度,因此 FirstPageProber::prefixFraction() 會回報初始抓取相對於整份檔案有多小——對一份多頁文件而言,這正是 Fast Web View 的全部重點。回應建構會切取記憶體內的位元組字串;成本與所選取的位元組成正比。ETag 每份文件計算一次。請以具代表性的文件量測。

請將輸入視為不可信。LinearizedDocument::fromBytes() 會在任何偏移被使用之前驗證 linearization 不變式。responder 會拒絕一個含控制字元的 contentType 以防止標頭注入,推導出一個保證不會出現在主體內的 multipart 邊界,並合併重疊的範圍且為其數目與總大小設界,以抵禦 multipart range-amplification 這一類阻斷服務攻擊(Apache HTTPD CVE-2011-3192)。本模組不會記錄任何文件內容。

byte-range 交付遵循 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 加入了另行記錄的更高階一致性與封存功能;在 byte range 上提供一份線性化 PDF 並不需要它們。

本頁僅記載外部可觀察的行為與所支援的公開 API 表面。內部命名空間路徑、輔助類別、機制表格、runbook 檔名,以及工單前綴皆不在範圍內。