HTTP 499 エラーはクライアントがHTTPリクエストを送った後にレスポンスを待たずに切断(FIN)を送った場合にnginxがログに出力するステータス。
実験
以下のように意図的に30秒くらい時間がかかるwebサーバを用意しておく
package main import ( "fmt" "log" "net/http" "time" ) func helloHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("recv") hello := []byte("Hello World!!!") time.Sleep(time.Second * 5) _, err := w.Write(hello) if err != nil { log.Fatal(err) } fmt.Println("end") } func main() { http.HandleFunc("/hello", helloHandler) fmt.Println("Server Start Up........") log.Fatal(http.ListenAndServe("localhost:8081", nil)) }
nginxを用意する。proxy_passさえ用意されていればなんでもよい
worker_processes 1; events { worker_connections 1024; } http { default_type application/octet-stream; sendfile on; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; keepalive_timeout 65; server { listen 80 reuseport; server_name localhost; location / { proxy_ignore_client_abort off; proxy_pass http://127.0.0.1:8081; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
これに対してcurlを実行してsleepしてる最中にcurlを止めてみる
$ curl localhost/hello
そうするとnginxには499のログが出力される
127.0.0.1 - - [23/Feb/2023:12:56:35 +0900] "GET /hello HTTP/1.1" 499 0 "-" "curl/7.81.0" "-"
この時にnginxでは何が起きているのかを見ていく。upstream、クライアントとの通信はngx_http_upstream_check_broken_connection
で管理している。ここでは最後にクライアントとのコネクションが繋がっているかを確認していて切断済みならngx_http_upstream_finalize_requestを呼び出す。どうやって実現しているかというとnginxが使っているepoll_wait(2)がTCP でピアがシャットダウンしたことを検出したかどうかで判断している。ユーザーとのコネクションがない状態でupstreamとコネクションがつながっている状態=499となる
static void ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, ngx_event_t *ev) if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ngx_use_epoll_rdhup) { if (!u->cacheable && u->peer.connection) { // ユーザーとのコネクションが ngx_log_error(NGX_LOG_INFO, ev->log, err, "epoll_wait() reported that client prematurely closed " "connection, so upstream connection is closed too"); ngx_http_upstream_finalize_request(r, u, NGX_HTTP_CLIENT_CLOSED_REQUEST); return; }
ngx_http_upstream_finalize_request
ではいろいろじょうけんを見つつupstreamとの接続を切断する処理となっている。
static void ngx_http_upstream_finalize_request(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_int_t rc) { ngx_close_connection(u->peer.connection);
これだけをみるとわかるがupstreamにはTCP-FINが飛んでくるだけになる。つまり受信側の切断検出は、recv()がlength==0で返ってくるかなどを見てあげる必要がある。普通にアプリケーション書いてるだけでは実はアプリケーションは継続を続ける(はず)。今回の場合はFINで閉じるのでsend(2)してれば2回目で気づける。(send()自体はカーネルの送信バッファにデータコピー)
あとはEPOLLRDHUPというフラグをepollで検出するようにアプリケーションを実装するとよさそう。webアプリケーションがそんな実装をしているケースはあるのだろうか?ちょっと調べてみたがなさそうだったのでどうなんだろう?という気持ち