メインコンテンツへスキップ
#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 のサーバーでは、リクエストは次の順に処理されます。

  1. HTTP/1 または HTTP/2 の処理系がリクエストを読み込み、*Request を組み立てる
  2. サーバー内部のハンドラーが前処理を行う
    • OPTIONS * リクエストに 200 OK(Content-Length: 0)で応答する(DisableGeneralOptionsHandler で無効にできる)
    • Server.Handler が nil なら http.DefaultServeMux を使う
    • ハンドラー内の panic を回復し、ErrAbortHandler 以外ならログに出す
  3. ユーザーのハンドラーやミドルウェアが処理する
    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 からハンドラーへ」のステップだけなので、ここを公開すれば必要な部分はそろうと判断されました。

関連リンク