#77441accepted
net/http: add Server.HandleRequest
ステータス変更: likely_accept accepted
要約
AIによる要約であり、誤りを含む場合があります。
概要
net/http.Server に HandleRequest メソッドを追加する提案です。ネットワークから読み込んだリクエストでなくても、*http.Request を「サーバーが受信したリクエスト」と同じ扱いで処理できるようになります。具体的には、サーバー内部で行われている前処理(OPTIONS * への応答、ハンドラーの選択、panic の回復とログ出力)をそのまま通せます。
ステータス変更
likely_accept → accepted
2026年10月8日に likely accept となり、その後1週間で異論が出なかったため、10月14日に正式に承認されました。レビュー会議で懸念とされたのは「利用者がかなり少ない」という点です。一方で、このロジックを実装している内部関数が "linkname hall of shame"(外部パッケージから //go:linkname で不正に参照されている内部シンボルの一覧)に載っていました。Go チーム外にも実際の需要があることを示す証拠として扱われ、これが承認の決め手になりました。名前は最後まで議論されましたが、より良い候補が見つからず HandleRequest に決まりました。
技術的背景
現状の問題点
net/http のサーバーでは、リクエストは次の順に処理されます。
- HTTP/1 または HTTP/2 の処理系がリクエストを読み込み、
*Requestを組み立てる - サーバー内部のハンドラーが前処理を行う
OPTIONS *リクエストに 200 OK(Content-Length: 0)で応答する(DisableGeneralOptionsHandlerで無効にできる)Server.Handlerが nil ならhttp.DefaultServeMuxを使う- ハンドラー内の panic を回復し、
ErrAbortHandler以外ならログに出す
- ユーザーのハンドラーやミドルウェアが処理する
2番目のステップは、ネットワークからリクエストを受け取らない限り使えません。そのため、net/httpの外で HTTP/3 サーバー(例:ServePacketConnでnet.PacketConnからリクエストを受ける実装)を作る場合、このロジックを自前で書き写す必要がありました。実際、HTTP/2 も標準ライブラリに取り込まれる前は同じ処理の複製を持っていました。
提案された解決策
package http
// HandleRequest handles a request as if it was received by the server.
//
// - If Server.DisableGeneralOptionsHandler is not true,
// it responds to "OPTIONS *" requests with 200 OK and Content-Length: 0.
// - If Server.Handler is nil, it uses [http.DefaultServeMux].
// - Panics in the handler are recovered.
// If the panic is not [ErrAbortHandler], the panic is logged.
//
// HandleRequest returns a non-nil error if the handler panics.
// It returns [ErrAbortHandler] if the handler panicked with that value.
func (*Server) HandleRequest(ResponseWriter, *Request) error
ハンドラーが panic した場合は nil 以外のエラーを返し、panic の値が ErrAbortHandler ならそれをそのまま返します。panic の値そのものは取り出せません。必要になれば、将来 HandlerPanicError のようなエラー型を追加して対応する余地が残されています。
これによって何ができるようになるか
- 独自プロトコルの実装: HTTP/3(quic-go など)のように
net/httpの外で動くサーバーでも、標準サーバーと同じ前処理を重複なしで適用できます。 - サーバー全体を通したテスト: ハンドラー関数だけでなく、
Serverの設定(DisableGeneralOptionsHandlerなど)まで含めた振る舞いを、ネットワークを使わずに確認できます。 - 将来追加される機能への追従: サーバー全体に
http.CrossOriginProtectionを既定で適用するオプションなど、今後サーバー内部のハンドラーに機能が増えた場合も、HandleRequestを呼んでいる実装はそのまま恩恵を受けられます。
コード例
// Before: 独自のHTTP/3サーバーで net/http の前処理を自前で再現
func (s *H3Server) serveRequest(w http.ResponseWriter, r *http.Request) {
defer func() {
if p := recover(); p != nil && p != http.ErrAbortHandler {
log.Printf("http3: panic serving %v: %v", r.RemoteAddr, p)
}
}()
if r.RequestURI == "*" && r.Method == "OPTIONS" &&
!s.srv.DisableGeneralOptionsHandler {
w.Header().Set("Content-Length", "0")
w.WriteHeader(http.StatusOK)
return
}
h := s.srv.Handler
if h == nil {
h = http.DefaultServeMux
}
h.ServeHTTP(w, r)
// net/http側で前処理が増えても、ここには反映されない
}
// After: Server.HandleRequest に処理を任せる
func (s *H3Server) serveRequest(w http.ResponseWriter, r *http.Request) {
if err := s.srv.HandleRequest(w, r); err != nil {
// ハンドラーがpanicした(ErrAbortHandlerの場合を含む)
s.resetStream(r) // 例: ストリームを中断する
}
}
議論のハイライト
- 名前の議論: 当初は
ServeHTTPという名前でしたが、Server.Serveと紛らわしいとの指摘を受けて見直されました。候補にはRespond、ServerHandler() Handler、ServeRequestなどが挙がりました。HandleRequestが選ばれた理由は次の3点です。Serverの既存メソッドと似た名前がない- 動詞を含むので、フィールドの
Server.Handlerと区別しやすい http.Handlerを実装しないため、あるServerをうっかり別のServerのハンドラーにする使い方を防げる
- 名前が魅力的すぎるという懸念: 利用者が少ない API なのに
HandleRequestは気軽に呼びたくなる名前だ、という懸念が出ました。Respondも「短すぎて、かえって使いたくなる」という理由で採用されませんでした。 - ミドルウェア形式の案は不採用:
Preprocess(h Handler) Handlerのようにハンドラーを包む形も提案されました。しかし今回の目的は「サーバー自身のハンドラーで処理すること」なので、趣旨が違うとして採用されませんでした。 - error を返す設計: panic をエラーとして返すのは複雑すぎるという反対意見がありました。これに対しては、通常のリクエスト処理経路でも同じ処理がすでに必要なので、複雑さが増えるわけではないと説明されました。
- API の範囲:
Serverのフィールドの大半は、ネットワーク上のデータをRequestに変換するための設定です。それ以外にあるのは「Request からハンドラーへ」のステップだけなので、ここを公開すれば必要な部分はそろうと判断されました。