ADR-0054: HTTP の流れとコンソールを preload とプレビューのスクリプトで記録し、記録の行は端末に出さない¶
Status¶
承認
Context¶
訪問者に、サーバーの間とブラウザとの間でやり取りした HTTP のリクエストとレスポンスを、順に並べて見せる。
コンソールは、ログレベルでフィルタでき、オブジェクトを開いて中を見られる形にする。
この 2 つの記録を、どこで取るかを決める必要がある。
- Node と npm の実行に WebContainers を採る。画面は、実行環境の記録を、インターフェースを通して読む。
packages/uiは、HTTP のやり取りを並べるコンポーネントと、その 1 件の型(HttpExchange)を持つ。コンソールの行のコンポーネントは持たない。- WebContainers は、プレビューのページにスクリプトを入れる API(
setPreviewScript)を持つ。 - WebContainers のプロセスの出力(
process.output)は、端末のテキストのストリームである。console.logとconsole.errorの区別が無く、オブジェクトはutil.inspectの文字列になる。 - ツールのページから、実行環境のサーバーへの
fetchは、CORS で止まる。ツールのページは、実行環境の通信を外から記録できない。
Decision¶
1. HTTP の流れ¶
HTTP の流れは、実行環境の中の Node の preload で記録する。
- preload は、
http.Serverのrequestをラップし、メソッド、URL、ヘッダ、ボディ、ステータスコード、所要時間を記録する。 - サーバーの間の
fetchには、送信元を示すヘッダを追加する。受信側で、どのサーバーが送ったかを区別するためである。 - 記録は、1 件を 1 行の JSON にし、行頭に固定のプレフィックスを付けて、プロセスの出力に出す。画面は、インターフェースを通して読む。
- preload は、実行環境が追加する。運営者が設定に書く起動のコマンドにも、訪問者が端末で打つコマンドにも、現れない。
preload で記録する理由は、ブラウザからサーバーへの通信と、サーバーの間の通信の両方を取れる場所が、preload だけであることである。
プレビューのスクリプトは、HTTP の記録には使わない。
2. コンソール¶
コンソールは、構造のある記録から描く。
- Node の側は、preload で
console.*をラップし、ログレベル、時刻、引数のツリーを記録する。記録は、HTTP の記録と同じく、プレフィックスを付けた 1 行の JSON にして、プロセスの出力に出す。 - 出力したプロセスの名前は、preload ではなく、記録の行を抜き出す実行環境のモジュールが付ける。preload は、実行環境がプロセスに付けた名前を受け取れないためである。
- プレビューの側は、プレビューに入れるスクリプトで
console.*をラップし、ログレベルと引数を、ツールのページへpostMessageで送る。関数や DOM の要素のように複製できない値は、文字列にして送る。 - コンソールと端末は、別の出力先にする。
- コンソールの行のコンポーネント(ログレベルのアイコン、オブジェクトのツリー、ログレベルでのフィルタ)を、
packages/uiに追加する。 - 式を評価する入力欄は、最初の公開に入れない。
3. 端末に出す出力¶
端末には、プロセスの出力のテキストのストリームから、記録の行を除いたものを出す。
- 実行環境のモジュールが、プロセスの出力から、プレフィックスの付いた行を抜き出して、記録にする。端末には、抜き出した後の出力を渡す。
- 抜き出す処理は、WebContainers の API を呼ぶコードの外に置く。実行環境を差し替えるときに、作り直さずに済ませるためである。
記録の行を除く理由は、訪問者の端末が、記録の JSON で埋まらないようにするためである。
Verification¶
WebContainers 1.6.4 の中で、node -r ./trace.cjs の形で preload を追加し、認可コードフローを実行した(Playwright 1.65.0 の Chromium、localhost)。
- preload は、
NODE_OPTIONSでも追加できた。結果は、ADR-0052 の Verification にある。 - 試作の preload は、記録の行に
@@TRACEと@@CONSOLEのプレフィックスを付けた。ツールのページは、プロセスの出力から、プレフィックスの付いた行を取り出して読めた。 - 記録の行を除いた出力を端末のエンジンに書く形は、試していない。出力のチャンクが行の途中で切れたときの扱いは、未確認である。
- preload は、ブラウザからサーバー、サーバーからサーバーの 8 件のやり取りを、順に記録した。
CookieとSet-Cookieを含む。 - 記録の形は、
HttpExchangeの項目(fromId、toId、method、url、status、request、response)にそのままマッピングできた。 - プレビューのスクリプトで
fetchをラップする形は、プレビューのページが出したfetchの URL とステータスコードだけを取れた。ページの移動、リダイレクトの途中のレスポンス、HttpOnlyの Cookie、サーバーの間の通信は取れなかった。 - preload の
console.*の記録は、文字列、数、配列、オブジェクト、Error、Map、Set、循環を区別した。 - プレビューのスクリプトの
console.*の引数は、入れ子のオブジェクトと配列のまま、ツールのページに届いた。関数や DOM の要素は、postMessageで複製できなかった。
Considered Options¶
記録を取る場所の選択肢は次のとおり。
| 選択肢 | 強み | 弱み |
|---|---|---|
| 採用: HTTP は preload で、コンソールは preload とプレビューのスクリプトで記録する | 全てのやり取りを、1 か所で順に取れる。コンソールを、ログレベルと構造を持つ形で描ける | 訪問者のコードが http を書き換えると、記録が欠ける。送信元を示すヘッダが、訪問者のサーバーから見える |
| HTTP を、preload とプレビューのスクリプトの両方で記録し、2 つの記録を比べる | ブラウザの側から見た fetch を、サーバーの側の記録と比べられる |
同じ通信が 2 回記録され、2 つの記録を対応づける処理が要る。プレビューの側で取れるのは fetch だけである |
| HTTP を、ツールのページで記録する | 実行環境の中に、記録のコードを入れない | WebContainers には、ツールのページで通信を受ける場所が無い。サーバーの間の通信を取れない |
| コンソールを作らず、端末のテキストのストリームだけを出す | コンポーネントを追加しない | ログレベルでフィルタできない。オブジェクトを開いて中を見られない |
記録の行の扱いの選択肢は次のとおり。
| 選択肢 | 強み | 弱み |
|---|---|---|
| 採用: プレフィックスの付いた行を抜き出し、端末には残りを出す | 端末に、記録の JSON が出ない | 訪問者のプログラムが、プレフィックスで始まる行を出すと、その行が端末から消える |
| 記録の行を除かず、端末にそのまま出す | 処理が無い | 訪問者の端末が、記録の JSON で埋まる |
| preload が、記録をファイルか Unix ドメインソケットに書く | プロセスの出力に、記録の行が入らない | 動くかは未確認である。ファイルの監視か、接続を受けるプロセスが要る |
Consequences¶
Positive¶
- 訪問者は、リダイレクト、ヘッダ、Cookie を、やり取りの順に読める。
- Cookie を、ブラウザの DevTools を開かずに、記録した HTTP の流れから読める。
- 訪問者の端末に、記録の JSON が出ない。
Negative¶
- 訪問者のサーバーは、実行環境が追加した送信元を示すヘッダを受け取る。記事のコードがヘッダの一覧を表示すると、送信元を示すヘッダが出る。
- 訪問者のコードが
httpかconsoleを書き換えると、記録が欠ける。 - コンソールで式を評価できない。
- 訪問者のプログラムが、プレフィックスで始まる行を出すと、その行は端末に出ない。
- 記録の行は、プロセスの標準出力に書く。訪問者が
node app.js > out.logのように標準出力をファイルにリダイレクトすると、記録の行が、Cookie とボディを含めてそのファイルに残る。