メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#77518active

net/http: server.Start and server.StartTLS

新規提案

要約

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

概要

net/http.Server に、リスナーの準備が完了してから制御を返す Start / StartTLS メソッドを追加する提案です。現在の ListenAndServe は呼び出し元をブロックし続けるため、非同期にサーバーを起動しつつ「起動完了」を検知する標準的な手段がありませんでした。

ステータス変更

(未設定)active
2026-08-19の提案レビュー会議(@aclements, @cherrymui, @griesemer, @neild, @rolandshoemaker)で議事録(minutes)に追加され、"Active" 列に分類されました。これは提案が今後の週次レビュー会議で継続的に検討される段階に入ったことを意味し、まだ承認・却下の結論は出ていません。@aclements が「本提案はproposals projectのactive columnに追加され、今後毎週のレビュー会議で検討される」とコメントしています。

技術的背景

現状の問題点

Server.ListenAndServe / ListenAndServeTLS は呼び出すとサーバーがシャットダウンするまでブロックし続けます。そのため、サーバー起動直後にリクエストを送るようなコード(例: ローカルサーバーでOAuth2コールバックを受けるMCPクライアント等)では、サーバーがまだAcceptできる状態になっていないうちにリクエストが送られてしまう競合状態が発生し得ます。

server := &http.Server{Addr: "localhost:8080"}
go server.ListenAndServe() // 起動完了を待てない
resp, _ := http.Get("http://localhost:8080/") // 早すぎて失敗する可能性

現状の回避策は、自前で net.Listen してから go server.Serve(listener) する方法や、サーバーへポーリングし続ける方法しかなく、net/http/httptest パッケージも同様の理由で独自にリスナーを作成しています。

提案された解決策

以下のシグネチャを持つメソッドの追加が提案されています。

// Start は s.Addr でリッスンし、Serve を別goroutineで呼び出す。
// ListenAndServe と等価だが、サーバーが接続を受け付け可能になってから返る。
func (*Server) Start() error
// StartTLS は s.Addr でリッスンし、ServeTLS を別goroutineで呼び出す。
func (*Server) StartTLS(certFile, keyFile string) error

なお本issueは10年前に却下された類似提案 #12731(特権ポートでリッスンした後にプロセスのuidを変更するユースケースに限定)と実質同一とされていますが、今回はより一般的な「listen後に何かする」ユースケースとして再検討されています。

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

サーバー起動と最初のリクエスト送信の間の競合状態を、標準ライブラリの機能だけで安全に解消できるようになります。特にテストコードやローカルコールバックサーバー(OAuth2認可コードフローなど)で有用です。

コード例

// Before: 起動完了を待てず、レースコンディションが起きうる
server := &http.Server{Addr: "localhost:0"}
go server.ListenAndServe()
// この時点でサーバーがAcceptできる保証がない
// または、自前でnet.Listenして回避する必要がある
listener, err := net.Listen("tcp", "localhost:0")
server := &http.Server{}
go server.Serve(listener)
// After: Startが返った時点でリクエスト受付可能
server := &http.Server{Addr: "localhost:0"}
err := server.Start()
if err != nil {
    // 起動失敗
}
// この時点でリクエスト送信が安全

議論のハイライト

  • 既視感のある提案: @neild が指摘した通り、本提案は10年前に却下された #12731 と酷似しているが、当時は特権ポート+uid変更という狭いユースケースが理由だったため、より一般的な観点から再検討する価値があるとされています。
  • 利便性のみか否か: @mvdan から「net.Listen+Serve の組み合わせでできることの単なる利便性向上ではないか」という疑問が出され、@neild は「その通りだが、便利であること自体に価値がある」と回答しています。
  • リッスンしたアドレスの取得: @seankhliao や @bradfitz(@neild経由)から、:0 指定時に実際に割り当てられたポートを取得する手段が欲しいという要望があり、Start()net.Addr を返す案、Server.Addr を書き換える案、BaseURL() を返す案などが検討されていますが、HTTP/1・HTTP/2(TCP)とHTTP/3(UDP)の違いやTLSでの適切なオーソリティ取得の難しさから、結論は出ていません。
  • サーバー終了・エラー検知の欠如: @rittneje・@aofei から、Start が内部でgoroutineを起動してしまうと、Serve のエラーや終了をどう検知するかという課題が指摘されました。Server.Wait メソッドの追加案、Start が待機用の関数を返す案(func (*Server) Start() (wait func() error, error))、OnServeError コールバックフィールドを追加する案などが議論されています。
  • Listen単体追加という代替案: @rittneje は、Wait を追加するなら実質 Serve と同等になるため、Start ではなく Listen メソッド単体を追加し、ListenAndServeListen + Serve の単なる糖衣構文にすべきという代替案を提示しています。
  • 実例による裏付け: @neild は modelcontextprotocol/go-sdk のPRレビュー中に、実際にこの問題(go server.ListenAndServe() 後にブラウザがリダイレクトされる前にサーバー起動を待てない)に遭遇した具体例を共有し、提案の実用性を補強しています。

関連リンク