-
Notifications
You must be signed in to change notification settings - Fork 1
Always send lowlevel_error response to client (#2731) #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: pr_052_before
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,11 +46,7 @@ def handle_request(client, lines, requests) | |
| env[HIJACK_P] = true | ||
| env[HIJACK] = client | ||
|
|
||
| body = client.body | ||
|
|
||
| head = env[REQUEST_METHOD] == HEAD | ||
|
|
||
| env[RACK_INPUT] = body | ||
| env[RACK_INPUT] = client.body | ||
| env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP | ||
|
|
||
| if @early_hints | ||
|
|
@@ -69,36 +65,58 @@ def handle_request(client, lines, requests) | |
| # A rack extension. If the app writes #call'ables to this | ||
| # array, we will invoke them when the request is done. | ||
| # | ||
| after_reply = env[RACK_AFTER_REPLY] = [] | ||
| env[RACK_AFTER_REPLY] = [] | ||
|
|
||
| begin | ||
| begin | ||
| status, headers, res_body = @thread_pool.with_force_shutdown do | ||
| @app.call(env) | ||
| status, headers, res_body = @thread_pool.with_force_shutdown do | ||
| @app.call(env) | ||
| end | ||
|
|
||
| return :async if client.hijacked | ||
|
|
||
| status = status.to_i | ||
|
|
||
| if status == -1 | ||
| unless headers.empty? and res_body == [] | ||
| raise "async response must have empty headers and body" | ||
| end | ||
|
|
||
| return :async if client.hijacked | ||
| return :async | ||
| end | ||
| rescue ThreadPool::ForceShutdown => e | ||
| @events.unknown_error e, client, "Rack app" | ||
| @events.log "Detected force shutdown of a thread" | ||
|
|
||
| status = status.to_i | ||
| status, headers, res_body = lowlevel_error(e, env, 503) | ||
| rescue Exception => e | ||
| @events.unknown_error e, client, "Rack app" | ||
|
|
||
| if status == -1 | ||
| unless headers.empty? and res_body == [] | ||
| raise "async response must have empty headers and body" | ||
| end | ||
| status, headers, res_body = lowlevel_error(e, env, 500) | ||
| end | ||
|
|
||
| return :async | ||
| end | ||
| rescue ThreadPool::ForceShutdown => e | ||
| @events.unknown_error e, client, "Rack app" | ||
| @events.log "Detected force shutdown of a thread" | ||
| write_response(status, headers, res_body, lines, requests, client) | ||
| end | ||
|
|
||
| status, headers, res_body = lowlevel_error(e, env, 503) | ||
| rescue Exception => e | ||
| @events.unknown_error e, client, "Rack app" | ||
| # Does the actual response writing for Request#handle_request and Server#client_error | ||
| # | ||
| # @param status [Integer] the status returned by the Rack application | ||
| # @param headers [Hash] the headers returned by the Rack application | ||
| # @param res_body [Array] the body returned by the Rack application | ||
| # @param lines [Puma::IOBuffer] modified in place | ||
| # @param requests [Integer] number of inline requests handled | ||
| # @param client [Puma::Client] | ||
| # @return [Boolean,:async] | ||
| def write_response(status, headers, res_body, lines, requests, client) | ||
| env = client.env | ||
| io = client.io | ||
|
|
||
| status, headers, res_body = lowlevel_error(e, env, 500) | ||
| end | ||
| return false if closed_socket?(io) | ||
| lines.clear | ||
|
|
||
| head = env[REQUEST_METHOD] == HEAD | ||
| after_reply = env[RACK_AFTER_REPLY] || [] | ||
|
|
||
| begin | ||
| res_info = {} | ||
| res_info[:content_length] = nil | ||
| res_info[:no_body] = head | ||
|
|
@@ -149,9 +167,9 @@ def handle_request(client, lines, requests) | |
| res_body.each do |part| | ||
| next if part.bytesize.zero? | ||
| if chunked | ||
| fast_write io, (part.bytesize.to_s(16) << line_ending) | ||
| fast_write io, part # part may have different encoding | ||
| fast_write io, line_ending | ||
| fast_write io, (part.bytesize.to_s(16) << line_ending) | ||
| fast_write io, part # part may have different encoding | ||
| fast_write io, line_ending | ||
| else | ||
| fast_write io, part | ||
| end | ||
|
|
@@ -169,7 +187,7 @@ def handle_request(client, lines, requests) | |
| ensure | ||
| uncork_socket io | ||
|
|
||
| body.close | ||
| client.body.close if client.body | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After-reply callbacks may be invoked twice on errorMedium Severity When an exception occurs during Additional Locations (1) |
||
| client.tempfile.unlink if client.tempfile | ||
| res_body.close if res_body.respond_to? :close | ||
|
|
||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early return bypasses resource cleanup in ensure block
Medium Severity
The new
closed_socket?(io)check at line 113 returnsfalsebefore entering thebegin/ensureblock. If the socket closes after the app executes but beforewrite_responsestarts, resources are not cleaned up:client.bodyis not closed (file handle leak),res_body.closeis not called, andrack.after_replycallbacks are not invoked (Rack spec violation). In the old code, response writing was inside thebegin/ensureblock ofhandle_request, so cleanup always ran.