std:http — HTTP 共通型
標準ライブラリモジュール (index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../language-spec.md を参照。実際に通信するクライアントは http/client.md、待ち受けるサーバーは http/server.md。
import h := "std:http" は Request / Response / Handler の 3 つの型メンバーのみを持つ namespace object を h に束縛する。値スロット (関数等) は一切持たない。capability なし (純粋・ネットワーク I/O を一切行わない) — HTTP のルーティング・middleware ライブラリなど、型だけに依存するコードはこのモジュールだけを import すればよく、そのコード自体はネットワーク能力を持たないことが静的に読み取れる。
import { Request, Response, Handler } := "std:http" import client := "std:http/client" h: Handler := { req | { status := 200, body := "ok" |} } res: Response := client.get("https://example.com")!.unwrap! res matches Response # true
1. 型メンバー
| 型 | 定義 (構造的) | 用途 |
|---|---|---|
Request |
{method: String, path: String, query: String, headers, body: String, body_bytes: Bytes |} |
std:http/server のハンドラーが受け取るリクエストの形 (http/server.md §2) |
Response |
{status: Int |} |
ハンドラーが返す応答の最小契約 (http/server.md §3)。headers / body / body_bytes は任意スロット |
Handler |
{Request | Response} (関数型) |
ハンドラーの契約。std:http/server の serve に渡す block はこの型を満たす |
3 型は std:http の型メンバーとして export される (.hika の type/enum と同じ機構、language-spec.md §13.4。std:time の Instant 等の型メンバーと対称)。import h := "std:http" のもとで h.Request のように修飾参照するか、import { Request, Response, Handler } := "std:http" で名前を取り出して型注釈・matches に使う。
いずれも type で定義した構造的型である。名目ではなく形 (shape) で同一性を判定するため、由来を問わず同じ形の値はこの型を満たす (language-spec.md §17.4)。
2. Request
{method: String, path: String, query: String, headers, body: String, body_bytes: Bytes |} — std:http/server のハンドラーが受け取るリクエストレコードの構造。各スロットの意味・値の詳細は http/server.md §2 を正とする。
std:http/client の request 引数レコード ({method, url, headers?, body?}。http/client.md §8) は url を持ち任意スロットも省略できるため、この共通 Request 型には乗らない (幅も要否も異なる形)。クライアント側リクエストの共通型化は持たない。
3. Response
{status: Int |} — ハンドラーが返す応答の最小契約。headers / body / body_bytes / stream は任意スロットであり、型としては要求しない。stream は応答本文を逐次 (チャンク) 送出する生産者 callable で、詳細は http/server.md §11。
構造的型の照合は幅サブタイプ (追加スロットを持つ値も満たす。language-spec.md §17.4) なので、std:http/client の応答レコード (status に加え headers / body / body_bytes を持つ。http/client.md §1) はこの Response を構造的に満たす — クライアントで受けた応答をそのままサーバーハンドラーの戻り値として使える (http/server.md §3 のプロキシ例)。
4. Handler
{Request | Response} — Request 1 個を受け取り Response を返す関数型。std:http/server の serve(port, handler) (http/server.md §1) が要求する契約であり、ルーティング・middleware ライブラリ (pkg: エコシステム) がこの型に依存して書ける (WAI / Rack 相当のアンカー)。
middleware は「Handler を受け取り Handler を返す」普通の関数合成で書ける:
import { Handler } := "std:http" with_logging: { Handler | Handler } := { h | { req | print("${req.method} ${req.path}") h(req) }} handler: Handler := { req | { status := 200, body := "ok" |} } logged: Handler := with_logging(handler)
5. ヘッダーマップ
Request.headers / Response.headers はいずれも std:map の InsertionMap (map.md) で、正規化・同名連結規則は std:http/client (http/client.md §2) の定義本体を正とする。サーバー側 (std:http/server) のリクエスト/応答ヘッダーも同じ規則に従う。
6. capability
std:http は型定義のみを持ち、ネットワーク I/O を行わない。std:http の import はいかなる capability も宣言しない。実際に通信・待ち受ける capability はそれぞれ std:http/client (http/client.md §11)・std:http/server (http/server.md 冒頭) が個別に宣言する。
7. ストリーミングの抽象 (受信側を足すときの制約)
ストリーミングは「送信 / 受信 × server / client」の 4 象限だが、データフローの向きで見れば
書き出す側 (producer / sink) と読み取る側 (consumer / source) の 2 抽象にまとめられる。
実装しているのは sink のみで、応答本文の送出に使う (http/server.md §11 の
{ sink | sink.write(chunk)! })。
受信側ストリーミングを将来足すときは、4 つを個別に作らず次の形に従う。
source(source.read()!相当) はsinkの双対として本書に 1 回だけ定義する。
本書はRequest/Response/Handlerが属する共通型モジュールであり、sourceも同じ層に置く。- server のリクエスト本文読みと client の応答本文読みは、どちらもこの同一の
sourceを消費する。 - client のリクエスト本文送信は新しい抽象を作らず、http/server.md §11 の
sink生産者モデルを再利用する。