Skip to content

Commit

Permalink
Add rack.response_finished to Rack::Lint
Browse files Browse the repository at this point in the history
This updates Rack::Lint to validate that `rack.response_finished` is an
array of callables when present in the `env`. e.g. procs, lambdas, or
objects that respond to `call`.

This validates that:

* `rack.response_finished` is an array
* The contents of the array all respond to `call`
  • Loading branch information
BlakeWilliams authored and ioquatix committed Aug 26, 2022
1 parent 1a37044 commit 9c88c6a
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 1 deletion.
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,
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

0 comments on commit 9c88c6a

Please sign in to comment.