コンテンツにスキップ
getnextpdf.com

Pro エディション

Webview

Webview は、線形化済み(Fast Web View)の PDF を HTTP 経由で配信します。これにより、クライアントは、ファイルの残りがまだ転送中の間に、小さな先頭プレフィックスからページ 1 のレンダリングを開始できます。生のバイト列を LinearizedDocument としてラップし、PSR-7 の ByteRangeResponder を通じて Range リクエストに RFC 9110 の partial-content レスポンスで応答します。さらに(FirstPageProber を介して)最初のページがプレフィックス内で自己完結していることを証明できます。

この機能は NextPDF Pronextpdf/pro)に同梱されており、Pro ティアのライセンスエンベロープで有効化されます。その権利を持たないデプロイメントでは、この機能のクラスはロードされません。エディションを比較してライセンスを取得する

機能ごとの個別のライセンスフラグはありません。レスポンダーはランタイムであなた自身の PSR-17 ファクトリー — ResponseFactoryInterfaceStreamFactoryInterface — に結線され、メディアタイプはライセンスのスイッチではなくコンストラクター引数として、デフォルトで application/pdf になります。

Terminal window
composer require nextpdf/pro

コードは NextPDF\Pro\Webview 名前空間の下にあります。

線形化済みの PDF は、ドキュメントの最初のページ — 線形化パラメーターディクショナリ、プライマリヒントストリーム、ページ 1 のオブジェクト — が /E オフセットで終わる先頭セクションに配置されるようにレイアウトされています。Webview は、そのレイアウトをプログレッシブ配信へと変換します。

LinearizedDocument::fromBytes() は、Core の読み取り側 LinearizationView を通じてバイト列を解析し(Pro は線形化の解析を決して再実装しません)、使用可能な線形化済みドキュメントではないものを拒否します。すなわち、まったく線形化されていないもの、宣言された /L の長さが実際のバイト長と一致しないもの、/E(最初のページの末尾)オフセットがファイル内の正のオフセットではないものです。したがって構築は全域的です。いったん LinearizedDocument を保持すれば、それが公開するすべてのオフセットは信頼できます。

次に ByteRangeResponder が HTTP リクエストに応答します。これは PSR-7 / PSR-17 のみに対して実装されており、フレームワークへの結合はありません。常に Accept-Ranges: bytes と、強く決定論的な SHA-256 の ETag をアドバタイズし、クライアントの Range ヘッダーを RFC 9110 §14 に従って解析して、完全な 200 OK、単一レンジの 206 Partial Content、複数レンジに対する 206 multipart/byteranges レスポンス、あるいは 416 Range Not Satisfiable のいずれかを返します。

FirstPageProber は構造的な証明側です。最初のページのプレフィックス、そのプレフィックスがファイル全体に占める割合、そしてプライマリヒントストリームがその内側に完全に収まっているか — リーダーがプレフィックスだけからページ 1 のオブジェクトを特定できるようにする性質 — を定量化します。

Webview は線形化そのものを決して再解析しません。Core の読み取り側 LinearizationView を借用するため、配信層は、ドリフトしていく 2 つ目の複製ではなく、監査済みの単一のパーサーを継承します。構築は意図的に全域的です。LinearizedDocument::fromBytes() は不正なレイアウトを最初に拒否するため、Range レスポンスが信頼するすべてのオフセットは、まず検証されています。レスポンダーは PSR-7 と PSR-17 だけを話すため、同じコードが任意の HTTP スタックから線形化済み PDF を提供します。この規律こそが、プログレッシブなレンジ配信を、信頼できないクライアントに対して大量に安全に公開できるようにするものです。

設計の背景: 大量ドキュメント生成

  1. レンダリングされた PDF バイト列から LinearizedDocument を構築します。無効な入力は、最初に UnsupportedDocumentException を発生させます。
  2. そのドキュメントと、受信した PSR-7 の ServerRequestInterfaceByteRangeResponder::respond() に渡します。レスポンダーは 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)。
  • 1 つの充足可能なレンジは Content-Range を伴う 206 Partial Content を返します。複数の充足可能なレンジは 206 multipart/byteranges を返します。有効なバイトレンジでありながらいずれも充足不可能な場合は、Content-Range: bytes */length を伴う 416 を返します(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 を超えて追記されたドキュメントは、バイトレンジのオフセットがもはや信頼できなくなるため、長さの不一致として拒否されます。
  • レスポンダーは、1 リクエストあたりに尊重する個別レンジの数に上限を設けます。上限を超える数の結合済みレンジを要求するリクエスト、または表現全体を超える合計バイト数を要求するリクエストは、その Range を無視され、全体の 200 が提供されます。

最初のページのプレフィックスは、ファイル長にクランプされた /E(最初のページの末尾)オフセットであるため、FirstPageProber::prefixFraction() は、初回フェッチがファイル全体に対してどれほど小さいかを報告します — 多ページのドキュメントにとって、これこそが Fast Web View の要点です。レスポンスの構築はインメモリのバイト文字列をスライスします。そのコストは選択されたバイト数に比例します。ETag はドキュメントごとに一度だけ計算されます。代表的なドキュメントで測定してください。

入力は信頼できないものとして扱ってください。LinearizedDocument::fromBytes() は、いずれのオフセットが使用される前にも線形化の不変条件を検証します。レスポンダーは、ヘッダーインジェクションを防ぐために制御文字を含む contentType を拒否し、本文内に出現しないことが保証された multipart 境界を導出し、重複するレンジを結合してその数と合計サイズに上限を設けることで、multipart レンジ増幅クラスのサービス拒否(Apache HTTPD CVE-2011-3192)に対して防御します。このモジュールはドキュメントの内容をログに記録しません。

バイトレンジ配信は RFC 9110(HTTP Semantics)に従います — レンジリクエストは §14、If-Range は §13.1.5、416 は §15.3.7 です。線形化済みドキュメントのモデルは、ISO 32000-2 Annex F に記述された Fast Web View レイアウトです。このモジュールは、テストによって検証された振る舞いを超えて、さらなる外部の節識別子を一切主張しません。

Enterprise は Webview の振る舞いを変更しません。Enterprise は、別途文書化された上位層のコンプライアンスおよびアーカイブ機能を追加します。それらは、線形化済み PDF をバイトレンジ経由で提供するのには必要ありません。

このページは、外部から観測可能な振る舞いと、サポートされる公開 API サーフェスのみを文書化します。内部の名前空間パス、ヘルパークラス、メカニズムの一覧表、ランブックのファイル名、チケットのプレフィックスは範囲外です。