テレビのアニメはどこから来る? / 第64話

第64話:イトは、HTTPのbodyだけをお願い票だと決めなかった|どこへ何を頼む?

ピコルート第64話。イトがbodyだけのblind retryを止め、method・target・field・contentとresponseを一組に戻して失敗を切り分けます。

HTTP requestのmethod・target・field・contentHTTP responseのstatus・field・contentmessage pairとone-change comparisonacceptedとcompletedの違い10分更新

第63話で、イトはAPIからJSONが届いたことを、自動制御に使ってよい成功だと決めなかった。

demoの30°Fと、liveの2℃を分け、温室のroofを閉じたまま自動plugを抜いた。

翌朝。

roof controllerはまだmanualのまま。

再接続する前に、昨夜のfailureを学習fixtureで再現し、次の担当者へrequest / response pairを渡さなければならない。

fixture replay slotが閉じるまで、あと八十秒。

イトの手元には、昨夜保存したJSON bodyが一枚だけあった。

{
  "action": "hold",
  "commandId": "trial-07"
}

「お願いの中身は残ってる。これをもう一度送ればいい」

イトはblank senderへbodyを貼り、defaultのまま送った。

senderが作ったrequestは、POST /。

content typeはtext/plain。

authority holderはempty。

返事は404。

イトはholdをstopへ変え、同じbuttonをもう一度押した。

また404。

roof commandのexecuted countは0。

blind retry countは2。

replay slotが閉じるまで、あと五十四秒。

bodyを変えても、頼む相手と頼み方は戻らない

イトは二枚のJSON bodyを並べた。

ピコはbodyへ触れず、空のmethod holder、target rail、field pocketを照らした。

イト

JSONのactionを変えたのに、返事が同じだ

ピコ

serverは、そのbodyをroof commandの入口で受け取っていないよ

イト

bodyがお願いそのものじゃないの?

ピコ

何をしたいか、どのresourceへ送るか、どんなfieldを添えたかと一組でrequestになる

今回のsender、roof command handler、receipt store、response viewerは、live温室から切り離した学習fixtureだ。

real roof、production endpoint、credential、personal data、external networkへrequestを送らない。

authorization valueはfixture tokenでもviewerへ表示せず、redacted markerだけを残す。

見るのは、requestのmethod、target、field、contentと、responseのstatus、field、content、selected handlerだ。

HTTP messageは、control data・field・contentを持つ

RFC 9110 HTTP SemanticsはHTTP messageを、message semanticsを記述するcontrol data、messageを拡張するheader fields、optional contentからなるものとして扱う。

requestとresponseでcontrol dataの中身は違う。

requestではmethodとtargetが中心になる。

responseではstatus codeが中心になる。

fieldはrequest / responseへ追加情報を添える。

contentはmessageのあとに必ずあるわけではない。

bodyという言葉は実装や画面でよく使われるが、bodyだけを切り出しても、complete request semanticsは戻らない。

message syntax、transport framing、message semanticsも同じではない。

byte列をどこで区切るかと、そのrequestが何を意味するかを分けて考える。

methodは「何をしたいか」を示す

RFC 9110では、request method tokenがrequest semanticsの第一のsourceになる。

GET。

HEAD。

POST。

PUT。

DELETE。

同じtargetでもmethodが違えば、求める処理は同じとは限らない。

GETだから必ず安全、POSTだから必ず保存、というproduct固有のshortcutへしない。

standard method semanticsと、そのresourceがsupportするmethodを確認する。

wrong methodなら、targetは存在しても405 Method Not Allowed等のresponseになる場合がある。

Allow fieldでsupportされるmethodが示される場合もある。

ただしstatusだけを見て自動的に別methodへ切り替えない。

method変更は処理の意味を変える可能性がある。

targetは「どのresourceへ」を示す

request targetは、methodを適用するtarget resourceを識別する。

RFC 9112 HTTP/1.1では、origin serverへ直接送る一般的なrequest-targetをabsolute pathとoptional queryからなるorigin-formとして示す。

HTTP/1.1ではHost field、HTTP/2 / HTTP/3では:authority等がtarget URIのauthority情報を運ぶ。

今回のcontract targetは、fixture内の/commands/roof。

bodyへroofと書いても、request targetが/なら同じresourceへ届いたことにはならない。

unknown targetを、bodyの書き換えで直そうとしない。

redirect responseがある場合も、Locationとstatus semanticsを確認する。

credentialを付けたままunknown destinationへ追従しない。

fieldは、messageへ追加条件を添える

fieldにはさまざまな役割がある。

target URIのauthority。

contentのmedia type。

受け取りたいrepresentation。

authentication credential。

cache condition。

date、trace context、precondition等。

すべてのrequestが同じfieldを持つわけではない。

field nameが同じでも、request / response contextで意味や適用範囲を確認する。

今回のJSON contentにはContent-Type: application/jsonを添えるcontractだ。

text/plainのまま送れば、文字列としては同じ見た目でも、handlerがsupportしないmedia typeとして415 Unsupported Media Type等を返せる。

authentication fieldは、debug screenshot、issue本文、public logへそのまま残さない。

secretを消すことと、field自体があった事実まで消すことを分ける。

Authorization: [redacted]のように、比較に必要なpresenceだけを残せる。

contentは「ある場合の中身」

request contentは、methodとresource semanticsに従って処理されるdataだ。

JSON。

form data。

image bytes。

plain text。

またはcontentなし。

JSON syntaxがvalidでも、method、target、media type、schema、authorization、current stateが合う証明にはならない。

content lengthやtransfer codingはmessage framingに関係する。

applicationが欲しいcommandIdやactionの意味とは別のlayerだ。

framingを手書きで推測せず、library / serverが生成・検証するboundaryを使う。

request viewerではraw secretを隠しつつ、実際に送ったmethod、target、field names、content hash / safe previewを残す。

responseも、statusだけでは完成しない

responseのcontrol dataにはstatus codeがある。

fieldはrepresentation metadata、authentication challenge、redirect location、retry condition等を持てる。

contentにはselected representation、処理結果、error detail等が入る場合がある。

200なら常に欲しい処理が完了した、404ならnetworkが壊れた、と決めない。

status semanticsへ戻る。

fieldを読む。

content formatとbodyを読む。

どのhandlerが返したかをobservabilityで確認する。

202 Acceptedは、requestがprocessingのため受理されたが、processingが完了していないことを示す。

accepted receiptをcompleted resultと同じgreen lampへ畳まない。

receipt id、current stateを確かめる別のrequestが必要なcontractもある。

一方、synchronousにfinal resultを返すAPIもある。

service contractを確認する。

イトの前に、三つの選択肢が開く

replay slotが閉じるまで、あと三十一秒。

A bodyのactionだけを変えて再送する

method、target、fieldが空 / defaultのままなので、同じroute failureを増やす。

B 別の200 screenshotを成功証拠にする

GET /healthのresponseがgreenでも、roof command requestが受理・実行された証拠にはならない。

C blind retryを閉じ、exact request / response pairを再構成する

contractからmethod、target、field、contentを戻す。

authorization valueはredactする。

同じfixtureで一つだけ条件を変え、response status / field / content / handler / executed countを比較する。

ピコはretry wheelにもsenderにも触れなかった。

「bodyを増やすんじゃない。失ったmessageの外側を戻して、一通ずつ比べる」

イトはCを選んだ。

自分でbody-only auto-retry wheelを閉じた。

exact pairを残して、一条件ずつ変えた

イトはbaseline requestを組み直した。

methodはPOST。

authorityはfixtureのhost。

targetは/commands/roof。

Content-Typeはapplication/json。

authorization valueはviewer上でredacted。

contentはaction: holdとcommandId: trial-07。

一通目を送る。

response statusは202。

media typeはapplication/json。

contentにはreceipt idとstate queued。

selected handlerはroof-command。

この時点のcompleted countは0。

イトはgreen completion lampをつけなかった。

次に、methodだけをGETへ変えた。

responseは405。

AllowはPOST。

executed countは0のまま。

baselineへ戻し、Content-Typeだけをtext/plainへ変えた。

responseは415。

selected handlerはroof-commandだが、content parserは未実行。

executed countは0のまま。

baselineへ戻し、targetだけを/roofへ変えた。

responseは404。

selected handlerはnot-found。

executed countは0のまま。

最後にbaseline receiptのstateを取得した。

response statusは200。

receipt stateはapplied。

baseline command executedは1。

duplicate executionは0。

blind retry after closureは0。

request / response pair savedは5。

unredacted secret savedは0。

イトが同じviolet body cardを吐き出すretry wheelを自分で閉じ、method、target、field、contentの四層を一通のrequest holderへ組み直している。右側ではstatus、field、contentの三層を持つresponse holderが対になり、条件を一つだけ変えた四つの結果が別trayへ残る
bodyだけを再送せず、requestとresponseの外側まで一組に戻し、一条件ずつ変えた結果を別々に残す。

202を、完了のgreen lampへ変えない

baseline requestが202を受けた時点では、handlerがprocessingのため受理したことまでしか分からない。

queueへ入った。

receiptが発行された。

しかしroof commandがappliedになったとはまだ決めない。

今回のfixture contractでは、receipt resourceを取得し、state appliedとexecuted count 1を確認する。

別のsystemではcallback、event、polling、job status、final response等を使う場合がある。

timeout時のretryも、method semantics、idempotency design、command identity、deduplication、processing stateを確認して決める。

「返事が遅いから同じrequestをもう一度」は、duplicate actionを起こす場合がある。

安全に再試行できる保証を推測しない。

今回のfixture commandはcommandIdでduplicateを拒むが、すべてのAPIが同じではない。

exactと言っても、secretを丸ごと残さない

incident evidenceには、request / response pairが必要になる。

しかしraw pairを誰でも読める場所へcopyしない。

credential。

session identifier。

personal data。

request content。

response content。

URL query。

fieldごとにsensitivityが違う。

必要最小限のfield name、redacted value、hash、timestamp、trace id、status、handler等をaccess-controlled storageへ残す。

redaction後も再現に必要な情報が足りるかを確認する。

秘密を守ることと、失敗原因を消すことを同じにしない。

retentionと削除ownerも決める。

bodyではなく、message pairを残す

workbenchには、六つのevidenceが残った。

baseline executed one。

duplicate execution zero。

blind retry after closure zero。

saved message pair five。

unredacted secret zero。

completed state one。

HTTP requestを「serverへ送るbody」と判断しない。methodで何をしたいかを示し、targetでresourceを選び、fieldでauthority・media type・authentication等を添え、必要な場合にcontentを持つ。responseもstatusだけでなくfield、content、selected handlerを一組で読む。acceptedとcompletedを分け、blind retryの前にexact pairをredactして残し、同じfixtureで一条件ずつ比較する。

「直すのはbodyとは限らない。どこへ、何を、どんな条件で頼み、何が返ったかを一通ずつ残す」

イトは五組のrequest / response cardを、一つのsuccess screenshotへ重ねなかった。

method、target、field、contentを左に置く。

status、field、contentを右に置く。

中央には、selected handlerとreceipt stateを置いた。

最後のresponse contentから、cream色のtext sheetが一枚だけ滑り出す。

先頭にはangle bracketの形が見えた。

同じtextでも、browserはどうやってheading、paragraph、linkへ分けるのだろうか。

次回:HTMLは、見た目の設計図?

イトはcream sheetをscreenへ貼り、文字の大きさを変えようとした。

「HTMLは、pageをきれいに見せるための書き方だよね」

ピコは色や大きさへ触れず、element、attribute、tree、meaningのholderを照らした。

次回、ピコルート第65話。

イトは、HTMLを見た目を描く紙だと決めなかった|何を組み立てる?

この物語を、解説で深める

第64話で生まれた疑問を、解説記事で少し深く見ていきます。物語で感じた不思議さを残したまま、言葉と仕組みを順番に整理しましょう。

HTTPとは?リクエスト・レスポンスとメソッドの仕組みを読む