Repository navigation
Behavior changes: read before upgrading
A rewrite or access handler that returns Nginx::DECLINED after Nginx::Async.sleep no longer makes a later handler answer 500 (#740)
- Before: when a rewrite handler (
mruby_rewrite_handler,mruby_rewrite_handler_code) or an access handler (mruby_access_handler,mruby_access_handler_code) returnedNginx::DECLINEDwithNginx.returnafterNginx::Async.sleep, the request went on to the next phase with its status set to 500. A later mruby handler of the request that set no status and wrote no output then answered 500: for example an access handler after the rewrite handler, also in a location that serves a static file, or a content handler without output after the access handler. Without such a later mruby handler, nginx answered as usual, because its own handlers set their status: a static file was served with 200. - Now: the request goes on to the next phase as it does when the handler declines without a sleep, and a later mruby handler that sets no status answers as it does then, not with 500: after a rewrite handler, the content handler answers with its output, and nginx serves the static file of the location with 200.
- Why it changed:
- The defect: the code that resumes a handler after its sleep set the status of the request to 500 for every result other than
Nginx::OK, also forNginx::DECLINED, with which the request goes on. This was reported by mail. - Why the old behavior could not be kept: a handler declines to let the later handlers answer; the 500 that it left made them answer an error that no handler had set.
- What the documentation promised: Nginx::Async#sleep of 2.7.4 said that the sleep is supported in rewrite and access handlers, and said nothing about a status after it.
- The defect: the code that resumes a handler after its sleep set the status of the request to 500 for every result other than
- Affected: rewrite and access handlers that call
Nginx::Async.sleepand then returnNginx::DECLINED, in locations where a later mruby handler of the request sets no status and writes no output. - What to do: nothing. A configuration that relied on the 500 returns that status from a handler with
Nginx.return.
A HEAD response with an output body filter keeps its connection available (#752)
- Before: a HEAD request to a location with
mruby_output_body_filterormruby_output_body_filter_code, whose response had a known content length, received its response headers, but nginx then closed the connection even when the headers advertised keep-alive. A following GET on that connection received no response. This occurred with both empty and nonempty replacement bodies. - Now: for those responses, after the HEAD headers are sent successfully, the filter sends no body bytes and leaves an otherwise reusable connection available for the next request. GET responses, other header-only responses and header-filter errors keep their behavior.
- Why it changed:
- The defect: the body-filter finalizer returned
NGX_ERRORafter a successful header-only response. nginx treated that return as an output error and closed the connection. The review of #574 recorded the HEAD case, and the review of #628 confirmed it with a following GET. - Why the old behavior could not be kept: a successfully sent HEAD response did not represent an output error, and closing the connection prevented the next request advertised by keep-alive.
- What the documentation promised: Nginx::Filter#body= described replacement bodies and their content length, but did not describe a HEAD connection-close exception.
- The defect: the body-filter finalizer returned
- Affected: HEAD responses with a known content length that are finalized by an mruby output body filter, when the connection can otherwise be kept alive.
- What to do: nothing. To close a connection deliberately, configure nginx's keep-alive policy rather than relying on this filter error.
A UDP session keeps its pending stream handler when more datagrams arrive
Security advisory: GHSA-wx6w-gpg3-cmc3.
- Before: each datagram received from the same client while a UDP stream handler slept started the code again and replaced the pending sleep timer. Only the most recently started code resumed after that timer; later datagrams moved the wait's deadline.
- Now: later datagrams leave the waiting handler and its current deadline in place. Its code resumes once for each sleep it requested. The first datagram remains available to the next phase. As before, payloads of later datagrams received during the wait are discarded, not queued or replayed to
proxy_pass. After the code finishes and the session reachesproxy_pass, nginx forwards subsequent datagrams normally in that session.worker_shutdown_timeoutends a UDP session that is still waiting. - Why it changed:
- The defect: receiving another datagram replaced the pending invocation instead of letting that invocation complete its code after the sleep.
- Why the old behavior could not be kept: the code after the earlier sleep did not run, and its deadline depended on later datagrams rather than the requested delay. A session's access handler cannot provide processing once for every datagram by starting overlapping waits.
- What the documentation promised: Nginx::Stream::Async#sleep in 2.7.4 explicitly said each UDP datagram during a sleep ran the code again. This release changes that documented behavior.
- Affected: UDP servers whose
mruby_streamormruby_stream_codehandler callsNginx::Stream::Async.sleep, when another datagram of the same session arrives before the wait finishes. - What to do: use the stream handler for session-level decisions. Move processing that must run for every datagram to the upstream application. If every payload must arrive there, avoid sleeping in the UDP handler or arrange retransmission in the application protocol; payloads received during the wait are not replayed.
更新前に確認する挙動の変更
Nginx::Async.sleepの後にNginx::DECLINEDを返すrewriteかaccessのハンドラで、後のハンドラが500で応答しなくなります(#740)
- 変更前:rewriteハンドラ、つまり
mruby_rewrite_handlerとmruby_rewrite_handler_codeか、accessハンドラ、つまりmruby_access_handlerとmruby_access_handler_codeが、Nginx::Async.sleepの後にNginx.returnでNginx::DECLINEDを返すと、リクエストはステータスを500にしたまま次のフェーズへ進みました。そのため、そのリクエストの後のmrubyのハンドラがステータスを設定せず出力も書かないと、500で応答しました。例えば、rewriteハンドラの後のaccessハンドラで、静的なファイルを返すlocationでも同じでした。accessハンドラの後の、出力の無いcontentハンドラも同じです。そのような後のmrubyのハンドラが無ければ、nginxのハンドラが自分でステータスを設定するので、nginxはいつもどおり応答し、静的なファイルを200で返していました。 - 変更後:sleepなしで
Nginx::DECLINEDを返した場合と同じステータスで、次のフェーズへ進みます。後のmrubyのハンドラがステータスを設定しなくても、残った500は返しません。rewriteハンドラの後では、contentハンドラは自分の出力で応答し、nginxは静的なファイルを200で返します。 - 変更の理由:
- 不具合:sleepの後にハンドラを再開するコードが、
Nginx::OK以外のすべての結果でリクエストのステータスを500にしていました。リクエストが先へ進むNginx::DECLINEDでも同じでした。この不具合は、メールで報告を受けました。 - 前の挙動を残せなかった理由:ハンドラが
Nginx::DECLINEDを返すのは、後のハンドラに応答させるためです。残った500のせいで、どのハンドラも設定していないエラーを後のハンドラが返していました。 - ドキュメントが示していた挙動:2.7.4のNginx::Async#sleepは、rewriteとaccessのハンドラでsleepを使えると説明し、その後のステータスについては何も書いていませんでした。
- 不具合:sleepの後にハンドラを再開するコードが、
- 影響の範囲:
Nginx::Async.sleepを呼んだ後にNginx::DECLINEDを返すrewriteとaccessのハンドラです。同じリクエストの後のmrubyハンドラがステータスを設定せず出力も書かないlocationに限ります。 - 必要な対応:ありません。500に頼っていた設定では、そのステータスを
Nginx.returnでハンドラから返してください。
出力ボディフィルターを通るHEAD応答の後も接続を使えます(#752)
- 変更前:
mruby_output_body_filterかmruby_output_body_filter_codeのあるlocationへのHEADリクエストで、応答の長さがわかっている場合、応答ヘッダーを受け取った後に接続が閉じられました。ヘッダーがkeep-aliveを示していても同じで、その接続で次に送ったGETへの応答はありませんでした。置き換えた本文が空でも、空でなくても起きました。 - 変更後:HEADのヘッダー送信に成功すると、本文を送らず処理を終えます。再利用できる接続は次のリクエストにも使います。GET応答、HEAD以外のヘッダーだけの応答、ヘッダーフィルターのエラーの扱いは変えません。
- 変更の理由:
- 不具合:ボディフィルターの終了処理が、ヘッダーだけの応答を正常に送った後にも
NGX_ERRORを返していました。nginxは出力エラーとして接続を閉じていました。#574のレビューと#628のレビューで確認されています。 - 前の挙動を残せなかった理由:正常なHEAD応答を出力エラーとして扱い、keep-aliveで使うはずの接続を閉じてしまうためです。
- ドキュメントが示していた挙動:2.7.4のNginx::Filter#body=は、本文の置き換えとその長さを説明していました。HEADの後に接続を閉じるという記載はありませんでした。
- 不具合:ボディフィルターの終了処理が、ヘッダーだけの応答を正常に送った後にも
- 影響の範囲:長さが既知のHEAD応答をmrubyの出力ボディフィルターが送信し、接続を再利用できる場合です。
- 必要な対応:ありません。接続を意図して閉じる場合は、このエラーに頼らずnginxのkeep-aliveを設定してください。
UDPセッションの待機中にデータグラムが届いてもハンドラを開始し直しません
アドバイザリ:GHSA-wx6w-gpg3-cmc3
-
変更前:UDPのstreamハンドラが待機している間に同じクライアントからデータグラムが届くたびに、コードを開始し直して待機タイマーを置き換えました。そのタイマーで再開するのは最後に開始したコードだけで、後から届いたデータグラムによって待機の終了時刻が延びました。
-
変更後:後続のデータグラムが届いても、待機中のハンドラとその待機の終了時刻を保ちます。コードは、自分が呼んだ各sleepの後に1回ずつ再開します。最初のデータグラムは、コードの後のフェーズで使えます。
待機中に届いた後続のペイロードは、従来どおり破棄します。蓄積しておいて、待機後に
proxy_passへ再送することはありません。コードが終了してセッションが
proxy_passへ進んだ後のデータグラムは、nginxが同じセッションで通常どおり転送します。待機中に
worker_shutdown_timeoutを迎えると、UDPセッションを終了します。 -
変更の理由:
- 不具合:別のデータグラムが待機中の呼び出しを置き換え、そのsleep後のコードを完了させませんでした。
- 前の挙動を残せなかった理由:先に呼んだsleepの後のコードが実行されず、その待機の終了時刻も指定した時間ではなく後続のデータグラムに左右されました。セッションのaccessハンドラで待機を重ねて開始しても、各データグラムを1回ずつ処理する仕組みにはなりません。
- ドキュメントが示していた挙動:2.7.4のNginx::Stream::Async#sleepは、待機中にUDPデータグラムが届くたびにコードを開始し直すと明記していました。このリリースでは、その記載どおりの挙動を変更します。
-
影響の範囲:
mruby_streamまたはmruby_stream_codeのハンドラがNginx::Stream::Async.sleepを呼ぶUDPサーバーで、待機が終わる前に同じセッションの別のデータグラムが届く場合です。 -
必要な対応:streamハンドラはセッション単位の判断に使ってください。各データグラムで必ず行う処理は、転送先のアプリケーションへ移してください。
すべてのペイロードを届ける必要がある場合は、UDPハンドラで待機しないか、アプリケーションのプロトコルで再送してください。待機中に届いたペイロードをngx_mrubyが再送することはありません。