この記事はQiitaで公開されていました
素直な実装
特に何もしない場合、net/httpはタイムアウトしません。以下のサーバにHTTPアクセスをすると、1分後にhello!というレスポンスを返します。
package main import ( "log" "net/http" "time" ) type handler struct{} func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { <-time.After(1 * time.Minute) w.WriteHeader(http.StatusOK) w.Write([]byte("hello!") log.Println("ok") } func main() { log.SetFlags(0) log.SetPrefix("server: ") var h handler http.Handle("/", &h) http.ListenAndServe(":8080", nil) }
以下は手元での実行例です。
$ go run server.go & $ curl http://localhost:8080/ server: ok hello!
ハンドラのタイムアウトを設定する
どのリクエストも一律同じ時間でタイムアウトさせれば良い場合、http.TimeoutHandler()
を使えば、一定時間でクライアントに503 Service Unavailableを返すようになります。
タイムアウトが発生したかどうかは、http.ResponseWriter
のWrite()
がhttp.ErrHandlerTimeout
を返すかどうかで判断できます。
package main import ( "log" "net/http" "time" ) type handler struct{} func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { <-time.After(1 * time.Minute) w.WriteHeader(http.StatusOK) _, err := w.Write([]byte("hello!")) switch err { case http.ErrHandlerTimeout: log.Println("timeout") case nil: log.Println("ok") default: log.Println("err:", err) } } func main() { log.SetFlags(0) log.SetPrefix("server: ") var h handler http.Handle("/", http.TimeoutHandler(&h, 10*time.Second, "timeout!")) http.ListenAndServe(":8080", nil) }
このサーバへリクエストを送ると、10秒後にタイムアウトして、エラーとしてクライアントへ返却されます。ただしhandler.ServeHTTP()
は実行され続けているので、1分後に本来の処理が完了します。
$ go run server.go & $ curl http://localhost:8080/ timeout! # 10秒後 server: timeout # 1分後
このため、アプリケーションによっては、タイムアウトしていたらロールバックする等の処理が必要かもしれません。
その他のタイムアウト
http.TimeoutHandler()
の他にも、net/httpには色々なタイムアウトがあります。Then complete guide to Go net/http timeoutsには、どのタイムアウトがどこにかかるのかなど詳しく書かれているので、がとても分かりやすくおすすめです。