Skip to content
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

Add rack.response_finished to Rack::Lint. #1952

Merged
merged 2 commits into from Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Expand Up @@ -19,9 +19,10 @@ All notable changes to this project will be documented in this file. For info on
- Middleware must no longer call `#each` on the body, but they can call `#to_ary` on the body if it responds to `#to_ary`.
- `rack.input` is no longer required to be rewindable.
- `rack.multithread`/`rack.multiprocess`/`rack.run_once`/`rack.version` are no longer required environment keys.
- `SERVER_PROTOCOL` is now a required key, matching the HTTP protocol used in the request.
- `SERVER_PROTOCOL` is now a required environment key, matching the HTTP protocol used in the request.
- `rack.hijack?` (partial hijack) and `rack.hijack` (full hijack) are now independently optional.
- `rack.hijack_io` has been removed completely.
- `rack.response_finished` is an optional environment key which contains an array of callable objects that must accept `(env, status, headers, error)` and is invoked after the response is finished (either successfully or unsucessfully).

### Removed

Expand All @@ -41,6 +42,7 @@ All notable changes to this project will be documented in this file. For info on
- Allow response headers to contain array of values. ([#1598](https://github.com/rack/rack/issues/1598), [@ioquatix])
- Support callable body for explicit streaming support and clarify streaming response body behaviour. ([#1745](https://github.com/rack/rack/pull/1745), [@ioquatix], [#1748](https://github.com/rack/rack/pull/1748), [@wjordan])
- Allow `Rack::Builder#run` to take a block instead of an argument. ([#1942](https://github.com/rack/rack/pull/1942), [@ioquatix])
- Add `rack.response_finished` to `Rack::Lint`. ([#1802](https://github.com/rack/rack/pull/1802), [@BlakeWilliams], [#1952](https://github.com/rack/rack/pull/1952), [@ioquatix])

### Changed

Expand Down Expand Up @@ -787,3 +789,4 @@ Items below this line are from the previously maintained HISTORY.md and NEWS.md
[@jeremyevans]: https://github.com/jeremyevans "Jeremy Evans"
[@amatsuda]: https://github.com/amatsuda "Akira Matsuda"
[@wjordan]: https://github.com/wjordan "Will Jordan"
[@BlakeWilliams]: https://github.com/BlakeWilliams "Blake Williams"
4 changes: 4 additions & 0 deletions SPEC.rdoc
Expand Up @@ -132,6 +132,10 @@ There are the following restrictions:
set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
<tt>SCRIPT_NAME</tt> is empty.
<tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
<tt>rack.response_finished</tt>:: An array of callables run after the HTTP response has been finished,
ioquatix marked this conversation as resolved.
Show resolved Hide resolved
either normally or with an error (e.g. the client disconnected).
Invoked with <tt>(env, status, headers, error)</tt> arguments. If there is no error sending the response,
the error argument will be +nil+, otherwise you can expect an exception object, e.g. +IOError+.

=== The Input Stream

Expand Down
1 change: 1 addition & 0 deletions lib/rack/constants.rb
Expand Up @@ -51,6 +51,7 @@ module Rack
RACK_RECURSIVE_INCLUDE = 'rack.recursive.include'
RACK_MULTIPART_BUFFER_SIZE = 'rack.multipart.buffer_size'
RACK_MULTIPART_TEMPFILE_FACTORY = 'rack.multipart.tempfile_factory'
RACK_RESPONSE_FINISHED = 'rack.response_finished'
RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
RACK_REQUEST_FORM_VARS = 'rack.request.form_vars'
Expand Down
14 changes: 13 additions & 1 deletion lib/rack/lint.rb
Expand Up @@ -363,6 +363,18 @@ def check_environment(env)
unless env[SCRIPT_NAME] != "/"
raise LintError, "SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'"
end

## <tt>rack.response_finished</tt>:: An array of callables run after the HTTP response has been finished,
## either normally or with an error (e.g. the client disconnected).
## Invoked with <tt>(env, status, headers, error)</tt> arguments. If there is no error sending the response,
## the error argument will be +nil+, otherwise you can expect an exception object, e.g. +IOError+.
if callables = env[RACK_RESPONSE_FINISHED]
raise LintError, "rack.response_finished must be an array of callable objects" unless callables.is_a?(Array)

callables.each do |callable|
raise LintError, "rack.response_finished values must respond to call(env, status, headers, error)" unless callable.respond_to?(:call)
end
end
end

##
Expand Down Expand Up @@ -582,7 +594,7 @@ def check_hijack_response(headers, env)
## ignore the +body+ part of the response tuple when the
## +rack.hijack+ response header is present. Using an empty +Array+
## instance is recommended.
else
else
##
## The special response header +rack.hijack+ must only be set
## if the request +env+ has a truthy +rack.hijack?+.
Expand Down
21 changes: 21 additions & 0 deletions test/spec_lint.rb
Expand Up @@ -253,6 +253,16 @@ def obj.fatal(*) end
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
}.must_raise(Rack::Lint::LintError).
message.must_match(/cannot be .* make it ''/)

lambda {
Rack::Lint.new(nil).call(env("rack.response_finished" => "not a callable"))
}.must_raise(Rack::Lint::LintError).
message.must_match(/rack.response_finished must be an array of callable objects/)

lambda {
Rack::Lint.new(nil).call(env("rack.response_finished" => [-> (env) {}, "not a callable"]))
}.must_raise(Rack::Lint::LintError).
message.must_match(/rack.response_finished values must respond to call/)
end

it "notice input errors" do
Expand Down Expand Up @@ -811,4 +821,15 @@ def assert_lint(*args)
}.must_raise(Rack::Lint::LintError).
message.must_equal 'rack.hijack header must respond to #call'
end

it "pass valid rack.response_finished" do
callable_object = Class.new do
def call(env, status, headers, error)
end
end.new

Rack::Lint.new(lambda { |env|
[200, {}, ["foo"]]
}).call(env({ "rack.response_finished" => [-> (env) {}, lambda { |env| }, callable_object], "content-length" => "3" })).first.must_equal 200
end
end