Skip to content

Add support for Rack::ContentLength middleware#16793

Merged
jeremy merged 1 commit intorails:masterfrom
javan:comply_with_rack_content_length_middleware
Sep 7, 2014
Merged

Add support for Rack::ContentLength middleware#16793
jeremy merged 1 commit intorails:masterfrom
javan:comply_with_rack_content_length_middleware

Conversation

@javan
Copy link
Copy Markdown
Contributor

@javan javan commented Sep 3, 2014

Currently, Rails is not compatible with Rack::ContentLength because ActionDispatch::Response::RackBody instances don't respond to to_ary and, even though it's not part of the Rack spec, it's how Rack decides if a body has a knowable length. There's some discussion in rack/rack@2b3abc7, which was later reverted leading to #1160. And a little more in rack/rack@9495207.

IMO, Rack should remove the to_ary check. This change provides immediate support and should continue to work if / when Rack does make the change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return a copy without the to_ary method so calling flatten on a [response] doesn't recurse infinitely (069bc27).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned about the performance impact of this change. Calling undef_method will break method caches, which means people will add the ContentLength middleware and unwittingly ruin performance of their app.

Can we consider an implementation of to_ary that returns nil?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed 3dbd43300f16f8339fcbbcc77eb8c44cccf92ac7

Feels smelly to satisfy the respond_to? check with no implementation, but Rack::ContentLength is guilty of the opposite and never actually calls to_ary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could return response.body_parts as the implicit conversion. It's always an array, and it seems natural since RackBody serves as a lazy proxy for that array.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That'll break in the [response].flatten scenario (which may be pathological, but there are tests for) because the response.body_parts array will get flattened down to a string (or nil if empty). This is what lead to my original implementation of returning a new RackBody with to_ary undefined.

Here are those various scenarios played out: https://gist.github.com/javan/934aa107fcc08f72fce8

Having to_ary return nil seems to be the sanest option.

@rafaelfranca
Copy link
Copy Markdown
Member

cc @jeremy

@javan
Copy link
Copy Markdown
Contributor Author

javan commented Sep 3, 2014

Another option is to remove the to_ary check in Rack::ContentLength, which may warrant a significant Rack bump.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This technically works (it's even covered by Ruby's own tests, iirc), but it's an awkward concession to get some picky-eater middleware to finish its vegetables.

Rack::ContentLength could employ an implicit conversion in the same way, though. Rather than piggybacking on Array's #to_ary, introduce a #to_rack_response_array and use the same #try_convert technique to implicitly convert the response if possible.

Or, it could accept a hint in env or the response headers indicating that the app explicitly wants a Content-Length and knows it isn't providing one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rack::ContentLength isn't actually doing a conversion. It's using body.respond_to?(:to_ary) to implicitly decide if the body is a fixed length or not, but never calling to_ary. Here's the full condition:

!STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
  !headers['Content-Length'] &&
  !headers['Transfer-Encoding'] &&
  body.respond_to?(:to_ary)

IMO, using the headers and status code is sufficient. Streaming responses should take care to set the appropriate headers, like Rails does.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that status + headers are enough. I'd guess that Rack is trying to accommodate other cases where streaming without a Content-Length or Transfer-Encoding is legit, like HTTP 1.0 responses that are finished when the server closes the connection or HTTP 1.1 responses that use Connection: close. Maybe useful for long polling or SSE.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd guess the same. Hence this PR. 😁

@javan javan force-pushed the comply_with_rack_content_length_middleware branch from 3dbd433 to 66f8997 Compare September 6, 2014 16:36
@javan
Copy link
Copy Markdown
Contributor Author

javan commented Sep 6, 2014

@jeremy - rebased and squashed. With this fixed, wdyt about adding Rack::ContentLength back to the default stack? It was removed in #1160 due to not working.

@jeremy
Copy link
Copy Markdown
Member

jeremy commented Sep 7, 2014

I'm not sure. We've had issues with its behavior changes before, so I default to 😒 on it. I'd be positive on introducing a ContentLength middleware to AP that we version along with Rails releases.

jeremy added a commit that referenced this pull request Sep 7, 2014
…middleware

Add support for Rack::ContentLength middelware
@jeremy jeremy merged commit f5383ee into rails:master Sep 7, 2014
@javan javan changed the title Add support for Rack::ContentLength middelware Add support for Rack::ContentLength middleware Dec 2, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants