Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 4, Puma, Nginx - ActionController::Live Streaming dies after first chunk sent

Here's a bare-bones Rails 4 project I've set up to troubleshoot my problem:

https://github.com/rejacobson/rails4-streamtest

I have a route set up at /home/stream that should stream a line of text 5 times in 1 second intervals.

def stream
  5.times do |n|
    puts "Streaming: #{n}"
    response.stream.write "Streaming: #{n+1}"
    sleep 1
  end
rescue IOError => e
  puts 'Connection closed'
ensure
  response.stream.close
end

When I run puma using tcp://, without nginx, the streaming works perfectly.

curl -N http://localhost:3000/home/stream

And I get the 5 lines streamed back, no problem.

When I introduce nginx, curl will output the first line but immediately exit after that. I do continue to see output from the puts calls on the server and logs, so I know the request is still processing in the 5.times loop.

It also doesn't throw an IOError exception like it would if the user cut the connection.

Here's what I've tried so far:

  • Different combinations of nginx directives in the main conf file and the server conf.
  • Changing rails settings.
  • Various puma config settings.
  • Setting all kinds of different headers in the controller method in the hopes that it was a caching issue.

Searching the internet has provided very little help as well.

I'm at a loss and require some direction.

Here's the output of both curl calls, with and without nginx.

Without nginx

~/projects/streamtest ▰ master ▰
ryan mirage ▰▰▰▰ curl -i -N http://192.168.1.100:3000/home/stream
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-UA-Compatible: chrome=1
Cache-Control: no-cache
Content-Type: text/html; charset=utf-8
Set-Cookie: request_method=GET; path=/
X-Request-Id: 9ce86358-4476-404a-97e5-769c16ec7b0c
X-Runtime: 0.978099
Transfer-Encoding: chunked

Streaming: 1Streaming: 2Streaming: 3Streaming: 4Streaming: 5

puma.stdout

Streaming: 0
Streaming: 1
Streaming: 2
Streaming: 3
Streaming: 4
[8048] 192.168.1.100 - - [14/Mar/2014 21:04:50] "GET /home/stream HTTP/1.1" 200 - 6.0661

With nginx

~/projects/streamtest ▰ master ▰
ryan mirage ▰▰▰▰ curl -i -N http://192.168.1.100:3000/home/stream
HTTP/1.1 200 OK
Server: nginx/1.4.5
Date: Sat, 15 Mar 2014 04:02:40 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-UA-Compatible: chrome=1
ETag: "a505e0aa3b11b25301a9a704252a519a"
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: request_method=GET; path=/
X-Request-Id: 8983d199-026b-4082-a5f1-f1d6c886a3d6
X-Runtime: 0.016516

Streaming: 1

puma.stdout

Streaming: 0
[7558] 192.168.1.100 - - [14/Mar/2014 21:02:40] "GET /home/stream HTTP/1.0" 200 - 0.0214
Streaming: 1
Streaming: 2
Streaming: 3
Streaming: 4

What's interesting, and I've just noticed it, is that the location of the get request log line:

"GET /home/stream HTTP/1.0" 200

is different in each curl call, and is placed in relation to how much text is actually streamed.

Any ideas as to what's going on here? Why can't rails stream the entire thing when using nginx?

like image 844
ooog Avatar asked Mar 15 '14 04:03

ooog


1 Answers

Solved

It turns out all that I needed was this line in my location directive to get streaming working through nginx:

proxy_http_version 1.1;

I discovered this fix when investigating the keepalive directive in nginx's upstream module:

http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive

This piece of text in particular clued me in:

For HTTP, the proxy_http_version directive should be set to “1.1” and the “Connection” header field should be cleared:

upstream http_backend {
    server 127.0.0.1:8080;

    keepalive 16;
}

server {
    ...

    location /http/ {
        proxy_pass http://http_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        ...
    }
}

It seems that nginx defaults to, proxy_http_version 1.0;

And according to Wikipedia, http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol

HTTP/1.1 introduced chunked transfer encoding to allow content on persistent connections to be streamed rather than buffered.

So there was my problem.

TIL

like image 167
ooog Avatar answered Sep 21 '22 19:09

ooog