メインコンテンツへスキップ
#77441likely_accept

net/http: add Server.HandleRequest

ステータス変更: active likely_accept

要約

AIによる要約であり、誤りを含む場合があります。

概要

net/http.Server に HandleRequest メソッドを追加する提案です。ネットワークから受信したのではない *http.Request にも、Server が内部で行っている前処理を適用できるようにします。前処理とは、OPTIONS * への応答、ハンドラの選択、panic の回復とログ出力のことです。主な利用者として、HTTP/3 などのプロトコル実装者と、サーバー全体の挙動を確かめたいテストを想定しています。

ステータス変更

active → likely_accept
2026年9月から10月にかけて、Proposal Review Meeting で3回議論されました。

  • 反対論: 「利用者がごく少ない(プロトコル実装者は世界で10人未満程度)」という意見がありました。
  • 必要性の根拠: この処理を現在担っている内部関数が「linkname hall of shame」に載っています。//go:linkname で非公開関数を外部から無理やり参照している実例を集めたリストのことです。Go チーム外にも実際の需要があると判断されました。
  • 名前: 最後まで議論が続きました。Respond は短すぎ、かえって気軽に使われやすいとして退けられました。他に説得力のある候補が出なかったため、パッケージ全体で「server が request を handle する」という表現を使っていることとの一貫性から HandleRequest に決まりました。
  • panic を error として返す仕様: 「複雑すぎる」という意見がありました。しかし通常のリクエスト処理経路でも同じ処理が必要なので、複雑さを理由に外す根拠にはならないとされました。

技術的背景

現状の問題点

net/http.Server がリクエストを処理する流れは次のとおりです。

  1. HTTP/1、HTTP/2、(将来の)HTTP/3 の各実装が、ネットワーク上のデータから *Request を組み立てる
  2. サーバー内部のハンドラが前処理をする
    • OPTIONS * に 200 OK を返す(DisableGeneralOptionsHandler で無効化できる)
    • Server.Handler が nil なら DefaultServeMux を使う
    • panic を回復し、ErrAbortHandler 以外ならログに出す
  3. ユーザーのミドルウェアと最終的なハンドラが処理する
    2 の処理は非公開で、ネットワークから読んだリクエストでなければ通せません。そのため外部の HTTP/3 実装(quic-go など)は、同じロジックを自前で書き直す必要があります。HTTP/2 も標準ライブラリに取り込まれる前は、この処理を重複して持っていました。
    さらに、今後この前処理に機能が増える可能性があります。例として、サーバー全体に CrossOriginProtection を既定で適用する設定が検討されています(#73626 での議論)。機能が増えるほど、重複実装の問題は大きくなります。

提案された解決策

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

これによって何ができるようになるか

  • 独自プロトコルのサーバー実装: HTTP/3 サーバーが ServePacketConn などでリクエストを受け取ったあと、net/http.Server と同じ前処理を重複実装せずに適用できます。今後 Server に追加される機能(CSRF 対策など)にも自動的に追随できます。
  • テスト: 個別のハンドラ関数だけでなく、「Server 全体がこのリクエストをどう処理するか」を、実際のソケットを使わずに検証できます。
  • panic の検出: 戻り値の error を見れば、ハンドラが panic したかどうかを呼び出し側で判断できます。たとえばストリームを中断(abort)する、といった対応が可能です。

コード例

// Before: 独自プロトコル実装で、Server の内部処理を自前で再現する必要がある
func (s *H3Server) handle(w http.ResponseWriter, r *http.Request) {
	if r.Method == "OPTIONS" && r.URL.Path == "*" && !s.srv.DisableGeneralOptionsHandler {
		w.Header().Set("Content-Length", "0")
		w.WriteHeader(http.StatusOK)
		return
	}
	h := s.srv.Handler
	if h == nil {
		h = http.DefaultServeMux
	}
	defer func() {
		if p := recover(); p != nil && p != http.ErrAbortHandler {
			log.Printf("http: panic serving: %v", p)
		}
	}()
	h.ServeHTTP(w, r) // 将来 Server に追加される機能には追随できない
}
// After: Server と同じ処理をそのまま適用できる
func (s *H3Server) handle(w http.ResponseWriter, r *http.Request) {
	if err := s.srv.HandleRequest(w, r); err != nil {
		// ハンドラが panic した。ErrAbortHandler の場合も含む
		s.abortStream(r)
	}
}

議論のハイライト

  • 名前の変遷: 当初は ServeHTTP という名前でした。しかし Server.Serve のような「長時間動くサーバー起動」と紛らわしく、しかも http.Handler インタフェースを満たしてしまうため見直されました。ServerHandler() Handler も「srv.Handler との違いが分かりにくい」と指摘され(@bradfitz)、HandleRequest になりました。
  • あえて Handler を実装しない: HandleRequest は error を返すので http.Handler を満たしません。これは意図的な設計です。ある Server を別の Server のハンドラとして安易に入れ子にする使い方を防ぎます。
  • ミドルウェア形式の案は却下: Preprocess(h Handler) Handler という案も出ました。しかしこの提案の目的は「サーバー自身のハンドラで、受信したかのように処理する」ことです。任意のハンドラをラップする機能とは目的が異なるため退けられました(@neild)。
  • error の戻り値を追加: 議論の途中で、panic を検知できるように error の戻り値が追加されました。panic の値そのものは返しませんが、将来 HandlerPanicError{PanicValue any} のような型で拡張する余地を残しています。
  • 残っている懸念: HandleRequest という名前が魅力的すぎて、本番コードで不必要に使われることを心配する声があります(@earthboundkid)。httpguts のような別パッケージに置く案も出ましたが、最終的に Server のメソッドとして進めることになりました。

関連リンク