コンテンツにスキップ

中継の口

dashboard のサーバは中継しかしません。 dashboard のロジックはブラウザの側(public/)に在り、サーバが持つのは、台帳と daemon への道と、dashboard のファイルを配ることだけです。 置いた瞬間に「台帳が知っていることの写し」が生まれるので、ロジックはサーバに置きません。

口 答えるもの
GET /healthz サーバ自身(生存と、dashboard が要る設定)
POST /api/crawls 台帳へ(書く口はこれ 1 本)
/api/*(ほか) 台帳へ素通し(GET だけ)
/wacz/:id/… 台帳に署名をもらい、daemon へ
それ以外 public/ のファイル

口の振り分けは src/server.mjs に、/wacz/ の口の読みと組み替えは src/wacz.mjs に在ります。

どの口も、要求の Host が 127.0.0.1:<口> か localhost:<口> でなければ 403 で断ります。 よそのドメインの名前を 127.0.0.1 に向け直す手(DNS rebinding)で、別のサイトのページが dashboard を自分のオリジンとして読むのを止めるためです。

生存と、dashboard が要る設定を返します。

{
"ok": true,
"subject": "alice",
"organizations": ["acme"],
"replayOrigin": "http://127.0.0.1:8899",
"windmillOrigin": "http://127.0.0.1:8000",
"windmillWorkspace": "crawler",
"writeLimit": 1048576
}

dashboard は開いたときにこれを読み、誰として見ているかと、深いリンクの行き先を知ります。 writeLimit は書く口が受ける本文の上限(バイト)で、新しいクロールの画面はこれを超える本文を送る前に止めます。 /api/ の下には置きません。 あちらは台帳へ素通しする場所で、こちらの答えを混ぜると、どちらが答えたのかが読めなくなります。

台帳は CORS(Cross-Origin Resource Sharing。別のオリジンのページからの要求を許す仕組み)の答えを返しません(OPTIONS /api/archives は 404)。 ブラウザから直に叩く道が塞がっているので、同一オリジンにするために通します。

  • path と query はそのままで、台帳へは GET で通します。 GET 以外は 405 です(書く口は下の POST /api/crawls だけ)。
  • アクセストークンを Authorization: Bearer で付けます。 まだ持っていなければ、先に開発用 issuer から取得します。
  • 401 を受けたら、取得し直して 1 度だけやり直す。 寿命は数えません。 issuer を起こし直すと鍵ごと変わるので、「まだ有効なはず」の時計は当てになりません。 401 は、期限切れも鍵替わりも同じ形で言います。
  • 台帳の答えは、状態と content-type と本文をそのまま返します。

どの口を読むかは dashboard(public/app.js)が決め、サーバは選びません。 口の一覧(allowlist)は持っていません。 だから台帳の GET /api/crawl-rules/check(新しいクロールの画面が、書いている規則を確かめる)と GET /api/crawls/:id/decisions(開いたクロールの規則が何を決めたか)も、ここでは何も要りません。どちらも読む口で、一覧と同じく素通しです。

POST /api/crawls —— クロールを始める口

Section titled “POST /api/crawls —— クロールを始める口”

dashboard のサーバが持つ、ただ 1 本の書く口です。 新しいクロールの画面の欄(画面)が使います。 中身は検査せず、門を通った本文をそのまま台帳の POST /api/crawls へ渡し、答えをそのまま返します。 起点の URL も方針(深さ・枚数・走らせるスクリプト)も、クロールごとの変数(variables。名前の綴り・数・値の長さ・スクリプトが宣言した名前か)も、検査して決めるのは台帳です。 宣言したシークレットが Windmill に在るかも台帳が確かめ、dashboard はシークレットの値に触りません(値は Windmill の変数に在り、dashboard の欄にも本文にもありません)。

  • dashboard のページからの JSON だけを通す。 Origin が dashboard でなければ 403、content-type が JSON でなければ 415、本文が 1 MiB を超えたら 413 です(起点の URL と、欄で書いた JS の合計。台帳の POST /api/crawls と同じ上限)。 別のサイトのページに、利用者のブラウザから POST させる手(CSRF)を止めるためです。 Chrome の Private Network Access には頼りません。
  • アクセストークンの付け方と、401 でのやり直しは、上の /api/* と同じです。
台帳の答え いつ
202 {"crawlId": …} 受理した(完了は待たない)
400 {"error": …} 読めない起点の URL や、台帳にスクリプトが無いとき(hint つき)、知らないスクリプトの id や同じ id の重ね(ids つき)、必須の変数が無い・宣言に無い変数(names つき)、宣言したシークレットが Windmill に無い・secret でない(secrets に名前と path)など
404 名乗りにクロールを始める許可が無い({"error":"not found"})か、台帳にクロールの投げ先の設定が無く、口ごと無い
409 {"error": …, "crawlId": …} 走行中のクロールがある(全体で 1 本)。crawlId が塞いでいる 1 本
503 {"error": "windmill unreachable"} 宣言したシークレットが在るかを Windmill に訊けない(flow も動かないので、台帳はクロールを始めない)

門の判定は src/lib.mjs の writeRefusal に在り、試験から直接呼べます。

/wacz/:id/… —— WACZ の中身へ行く口

Section titled “/wacz/:id/… —— WACZ の中身へ行く口”

WACZ(撮ったページを 1 つの ZIP にまとめたアーカイブ)を検証し、中を読む口です。 中の WARC(撮った要求と応答を順に記録するファイル)や索引は、窓に分けて読みます。

dashboard が呼ぶ daemon へ 返すもの
POST /wacz/:id/validate POST /validate 検証の報告
GET /wacz/:id/lines?path=&from=&count= POST /lines 行の窓
GET /wacz/:id/line?path=&n= POST /line 1 行を丸ごとと、割った fields
GET /wacz/:id/records?path=&from=&count= POST /records WARC を頭から歩いたレコードの一覧
GET /wacz/:id/record?path=&offset=&length= POST /record 1 レコード(WARC の見出し・HTTP・本文)
GET /wacz/:id/record/body?path=&offset=&length= POST /record/body 画像の実体。raster だけで、nosniff と sandbox つき。他は 415

この表は、src/wacz.mjs の OPS と突き合わされます(npm run site:check)。 口を足すときは、OPS と paramsFor とこの表の 3 か所を直します。

  • どの口も同じ 2 段。 台帳の POST /api/archives/:id/url で署名をもらい、その URL を daemon に渡します(順番の意味は WACZ を見る)。
  • dashboard が呼ぶのは GET、daemon へは POST。 <img src> も GET なので、画像の実体も GET で呼べるようにしてあります。 検証だけは dashboard からも POST です(押したときだけ走らせる)。
  • daemon へ送る本文は { "source": { "kind": "uri", "uri": "<署名付き URL>" }, …引数 } です。 検証の引数は locale だけです。

上流(台帳か daemon)の答えから通すヘッダは allowlist で、src/server.mjs の PASS_HEADERS が決めます。 daemon の date や connection は、dashboard の応答に混ぜません。 画像の実体は bytes のまま、daemon が付けた印(nosniff・sandbox・no-store)ごと通します。

失敗は、何が居ないかを言います。 「見えない」と「立っていない」を混ぜません。

状態 いつ 本文
400 /wacz/ の口の引数が足りない、または数でない。台帳へは行かない {"error":"from is required"}
403 Host が dashboard の口でない(どの口も)。書く口では、Origin が dashboard でないときも {"error":"dashboard の Host ではない"} など
405 method が違う。/wacz/ の口(検証は POST、他は GET)と、/api/*(GET だけ。書く口を除く) POST だけ・GET だけ など(Allow ヘッダつき)
413 書く口の本文が 1 MiB を超えた {"error":"本文が 1048576 バイトを超えた"}
415 書く口の content-type が JSON でない {"error":"JSON だけを受ける"}
台帳のまま 台帳が断った(見てよくない archive の 404 など) 台帳の本文
502 issuer・台帳・daemon に届かない、または issuer が名乗りを断った {"error":"… に届かない (…)。…"}。直し方の 1 文つき
404 public/ に無いファイル、または配らない拡張子 not found

答えを流している途中で上流が切れたら、頭はもう書けないので、接続を閉じます。