本文へ移動
Hikari 仕様

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/serverserve に渡す block はこの型を満たす

3 型は std:http型メンバーとして export される (.hikatype/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/clientrequest 引数レコード ({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/serverserve(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:mapInsertionMap (map.md) で、正規化・同名連結規則は std:http/client (http/client.md §2) の定義本体を正とする。サーバー側 (std:http/server) のリクエスト/応答ヘッダーも同じ規則に従う。

6. capability

std:http は型定義のみを持ち、ネットワーク I/O を行わないstd:httpimport はいかなる 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 §11sink 生産者モデルを再利用する。