rails: Rack::ETag middleware doesn't play nice with Live streaming when commit! is called before write
This is similar to https://github.com/rails/rails/pull/9713, except that that code doesn’t fix the issue I’m experiencing.
Currently that middleware will only skip the body digest (and not call body.each) if Cache-Control
header is set to no-cache
:
https://github.com/rack/rack/blob/master/lib/rack/etag.rb#L56
So, unless you set that header explicitly in the controller’s action streaming won’t work out of the box. I spent several hours yesterday digging in several gem sources (railties, actionpack, active_support, rack) to find out what was preventing streaming from working in my application, so I would appreciate if Rails could make this work transparently if possible.
My first proposal is for Rails to automatically add Cache-Control: no-cache
on the first commit! call to the response object, so that ETag will skip the digest. This should work since the action will wait for the first commit before returning to the dispatch method.
If that’s not possible for some reason, then it would be great if we could flag actions which are supposed to be used with live streaming. Something like:
def my_action
# this would set the Cache-Control header and do whatever else is needed to make streaming just work.
enable_streaming!
# ...
end
About this issue
- Original URL
- State: open
- Created 10 years ago
- Reactions: 2
- Comments: 45 (37 by maintainers)
Rack 2 does not have a good interface for streaming responses. Rack 3 will solve this problem as it introduces an explicit model for streaming and
Rack::ETag
follows that model. Therefore, I’d suggest once Rack 3 PR is merged, we will be able to close this issue.I was going to push this as an issue, but I think it relates here. TL;DR Set a value for the ‘Last-Modified’ header in order to get the etag middleware to stop killing your SSE.
Steps to reproduce
I have a controller like so …
Expected behavior
A request to this controller should persist and server-sent event should be sent to the browser whenever Redis publishes them to the subscribed channel.
Actual behavior
Nothing is sent. No data ends up “on the wire”. I’ve checked with wireshark. Digging into this a bit, it seems that the
ETag
rack middleware is causing the response to “disappear”.I suspect the problem lies with the body replacement taking place here. The original body is a
Thread::SizedQueue
, on which threads are waiting, presumably. But then this middleware replaces that body with a plain array. I think that is the problem.I am trying to understand what is the current workaround for rails 6.0.3.4 with rack 2.2.3 and puma 4.3.6. I think I tried every single combination of headers or workarounds but nothing seem to be working for me. Can you explain maybe in more detail what works for you? In all cases my request gets stuck because SizedQueue hits the limit (10 elements). Apparently nothing is picking up in the other thread.
https://discuss.rubyonrails.org/t/actioncontroller-live-chokes-at-buf-push/76490
a note on the above comment by @jefflasslett in https://github.com/rails/rails/issues/14358#issuecomment-605719033 which also bit me.
The relevant part of rack setting these tags, near as I can tell, is: https://github.com/rack/rack/blob/v2.2.2/lib/rack/etag.rb#L29 https://github.com/rack/rack/blob/2.0.8/lib/rack/etag.rb#L57
A little extra context:
When we bumped rails to 6.0.2 from 6.0.1 it bumped rack from 2.0.8 to 2.2.2, and that seems to change how rack was handling setting ETags - we’d been setting
Cache-Control: no-cache
in headers, and in 2.0.8 that triggers the skip, but it doesn’t in 2.2.2. For reasons I’m not totally clear on this made the browser interpret it as a not-streaming request, and it would try to download the (significantly sized) file in one go rather than streaming. NBD and we’re out of the woods now, but hopefully this saves someone else some trouble someday.Setting the
ETag
header tonil
seems to resolve for us as well.(s/o @rosenfeld for making this issue and thanks to @rafaelfranca for keeping it open)