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
Correct Rack::MockRequest
/Rack::Lint::Wrapper::InputWrapper
re #set_encoding
#2115
Conversation
…doesn't assume the input can #set_encoding
|
||
## * +set_encoding+ just passes through to the underlying object. | ||
def set_encoding(*args) | ||
@input.set_encoding(*args) if @input.respond_to?(:set_encoding) |
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.
Is this necessary? Surely this is on the mock request to deal with - i.e. we shouldn't change the interface requirements of the input.
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.
I encountered this situation when appropriating Rack::MockRequest.env_for
to generate the elements for (what are effectively) subrequests within an app, what happened was rack.input
was an instance of Rack::Lint::Wrapper::InputWrapper
wrapping an instance of Puma::NullIO
and neither respond to #set_encoding
, so it would crash.
I genuinely believe it's more clement behaviour for Rack::MockRequest.env_for
to test if the input object responds to #set_encoding
before trying to call it (just like it does for #size
directly below).
I'm not married to that pass-through method in Rack::Lint::Wrapper::InputWrapper
but the motivation there was Postel's law. In case the underlying input does respond to #set_encoding
, perform the expected behaviour.
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.
What you are asking, is for every implementation of the input body to optionally adopt this interface. But the expectation is, the input should always be binary. So, I'm not sure whether this is a good idea. If the input is not binary, that sounds like a bug in whatever is generating the body.
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.
I am doing nothing more than ask for things not to crash when they don't need to. Prior to this patch, the implementation of Rack::MockRequest.env_for
does exactly that: crash because it already assumes whatever handed to it responds to #set_encoding
. Do you have an issue with that too, or just the pass-through method in Rack::Lint::Wrapper::InputWrapper
?
From SPEC: |
From: SPEC: "The input stream must respond to The phrase "The input stream is an IO-like object" is not definitive, especially given the above sentence... Happy to add it to Puma's |
That's a fair point. I think we could add I would recommend adding |
IO has a lot of methods. So, I'd prefer that the SPEC stayed as is, with an addition of |
For the record the code that tripped the linter error looks like this: # Using the current request as a basis, fake up a new request with
# the given URI.
#
# @param req [Rack::Request] the current request.
# @param uri [URI, RDF::URI] the new URI
# @param method [Symbol, #to_sym] the request method (GET)
# @param headers [Hash, #to_h] overriding request headers
# @param body [#each, #call] a new body
#
# @return [Rack::Request] a new request
#
def dup_request req, uri, method: nil, headers: {}, body: nil
# coerce the URI just so we can flatten it again so we can parse again
uri = Intertwingler::Resolver.coerce_resource uri, as: :uri
# override the method (maybe)
method ||= req.request_method
# same deal with with the body
body ||= req.env['rack.input']
# fake up an environment
env = req.env.merge Rack::MockRequest.env_for uri.to_s, input: body,
method: method.to_s.strip.upcase, script_name: req.script_name
# correct (non-standard??) REQUEST_URI which will be wrong now if it exists
env['REQUEST_URI'] = uri.request_uri.b if env.key? 'REQUEST_URI'
# supplant rack.errors which will also be wrong
env['rack.errors'] = req.env['rack.errors']
# now overwrite the headers
headers.each do |hdr, val|
hdr = hdr.to_s.strip.upcase.tr_s(?-, ?_)
hdr = "HTTP_#{hdr}" unless hdr.start_with? 'HTTP_'
val = val.join ', ' if val.is_a? Array
env[hdr] = val
end
# et voilà
Rack::Request.new env
end I used
|
I disagree. Input streams are required by SPEC to be IO-like, and IO-like objects should support
We don't want this because then it won't catch issues where non- |
Ah, the sense I got from reading the spec was that the input object was duck-typed with a minimal footprint of required methods/signatures/behaviours; if (I will add that the spec appears fine as-is with regard to the input object: whether it is an
I was imagining something like having it |
Re Puma, the SPEC states "The input stream is an IO-like object which contains the raw HTTP POST data" Ok, so why doesn't the SPEC define what happens when there is no "HTTP POST data"? Maybe in Rack 4 |
TBH I would change the SPEC on that; there way more methods than
I interpret that paragraph as "content (ie a body) in a |
I think that, rather than 'HTTP POST data', 'request body' or 'request content' is probably a better term for the SPEC. Regardless, requiring an "IO like object" when there is no content seems odd. |
Yeah I agree; it should be able to be The argument for always having something there is so you don't have to test for it first but ehh |
From the SPEC:
The body must be in binary mode, and not being in binary mode should be considered a bug, at least in the current interpretation. If we add
|
The key issue is this line of code: Line 149 in fd7e0ad
I'd be fine with introducing: if rack_input.respond_to?(:set_encoding)
rack_input.set_encoding(Encoding::BINARY)
end However, at this point, I'm not convinced we should be modifying the SPEC for this. In other words, if you supply a if String === opts[:input]
rack_input = StringIO.new(opts[:input], encoding: Encoding::BINARY)
else
rack_input = opts[:input]
end
if rack_input
# Delete set_encoding |
Sorry, just noticed the following in the SPEC env section (bold added) "The environment is required to include these variables (adopted from / PEP 333), except when they’d be empty" The code you showed above LGTM. Puma uses |
|
There is one more point worth making here, and it's that |
Ok, somewhat off topic... Given the following:
So, re requests with content, I suspect the vast majority don't have very large content, so the content could effectively be passed as a frozen string. Larger content would probably (?) be better left as an IO. App servers could have an option as to size threshold for that. Any thoughts? Or wait until Rack 4 is on the horizon? |
@MSP-Greg if you want to discuss such a thing, please use https://github.com/rack/rack/discussions or make a separate issue, thanks! |
Rack::MockRequest.env_for
would crash if the input didn't respond to#set_encoding
.Rack::Lint::Wrapper::InputWrapper
likewise would not convey#set_encoding
to its@input
member. This patch adds a condition to the former and a pass-through method to the latter.