From ed68af0f62f4f6b7ee107734d1ab1694f7d104d0 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 12 Jun 2024 09:52:06 +0900 Subject: [PATCH] Utilize Rack 3 streaming. --- .../lib/action_controller/metal/streaming.rb | 89 ++----------------- .../new_base/render_streaming_test.rb | 28 ++---- 2 files changed, 12 insertions(+), 105 deletions(-) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 586c107b6d..81ad9855a0 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -165,95 +165,16 @@ module ActionController # :nodoc: # # ## Web server support # - # Not all web servers support streaming out-of-the-box. You need to check the - # instructions for each of them. - # - # #### Unicorn - # - # Unicorn supports streaming but it needs to be configured. For this, you need - # to create a config file as follow: - # - # # unicorn.config.rb - # listen 3000, tcp_nopush: false - # - # And use it on initialization: - # - # unicorn_rails --config-file unicorn.config.rb - # - # You may also want to configure other parameters like `:tcp_nodelay`. - # - # For more information, please check the - # [documentation](https://bogomips.org/unicorn/Unicorn/Configurator.html#method- - # i-listen). - # - # If you are using Unicorn with NGINX, you may need to tweak NGINX. Streaming - # should work out of the box on Rainbows. - # - # #### Passenger - # - # Phusion Passenger with NGINX, offers two streaming mechanisms out of the box. - # - # 1. NGINX response buffering mechanism which is dependent on the value of - # `passenger_buffer_response` option (default is "off"). - # 2. Passenger buffering system which is always 'on' irrespective of the value - # of `passenger_buffer_response`. - # - # - # When `passenger_buffer_response` is turned "on", then streaming would be done - # at the NGINX level which waits until the application is done sending the - # response back to the client. - # - # For more information, please check the [documentation] - # (https://www.phusionpassenger.com/docs/references/config_reference/nginx/#passenger_buffer_response). + # Rack 3+ compatible servers all support streaming. module Streaming - class Body # :nodoc: - TERM = "\r\n" - TAIL = "0#{TERM}" - - # Store the response body to be chunked. - def initialize(body) - @body = body - end - - # For each element yielded by the response body, yield the element in chunked - # encoding. - def each(&block) - term = TERM - @body.each do |chunk| - size = chunk.bytesize - next if size == 0 - - yield [size.to_s(16), term, chunk.b, term].join - end - yield TAIL - yield term - end - - # Close the response body if the response body supports it. - def close - @body.close if @body.respond_to?(:close) - end - end - private - # Set proper cache control and transfer encoding when streaming - def _process_options(options) - super - if options[:stream] - if request.version == "HTTP/1.0" - options.delete(:stream) - else - headers["Cache-Control"] ||= "no-cache" - headers["Transfer-Encoding"] = "chunked" - headers.delete("Content-Length") - end - end - end - # Call render_body if we are streaming instead of usual `render`. def _render_template(options) if options.delete(:stream) - Body.new view_renderer.render_body(view_context, options) + # It shoudn't be necessary to set this. + headers["cache-control"] ||= "no-cache" + + view_renderer.render_body(view_context, options) else super end diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index d9789355f9..8066590f45 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -44,31 +44,27 @@ def explicit_cache end class StreamingTest < Rack::TestCase - def get(path, headers: { "SERVER_PROTOCOL" => "HTTP/1.1", "HTTP_VERSION" => "HTTP/1.1" }) - super - end - test "rendering with streaming enabled at the class level" do get "/render_streaming/basic/hello_world" - assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_body "Hello world, I'm here!" assert_streaming! end test "rendering with streaming given to render" do get "/render_streaming/basic/explicit" - assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_body "Hello world, I'm here!" assert_streaming! end test "rendering with streaming do not override explicit cache control given to render" do get "/render_streaming/basic/explicit_cache" - assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_body "Hello world, I'm here!" assert_streaming! "private" end test "rendering with streaming no layout" do get "/render_streaming/basic/no_layout" - assert_body "b\r\nHello world\r\n0\r\n\r\n" + assert_body "Hello world" assert_streaming! end @@ -79,13 +75,13 @@ def get(path, headers: { "SERVER_PROTOCOL" => "HTTP/1.1", "HTTP_VERSION" => "HTT test "rendering with layout exception" do get "/render_streaming/basic/layout_exception" - assert_body "d\r\n\r\n0\r\n\r\n" + assert_body "" assert_streaming! end test "rendering with template exception" do get "/render_streaming/basic/template_exception" - assert_body "37\r\n\">\r\n0\r\n\r\n" + assert_body "\">" assert_streaming! end @@ -102,19 +98,9 @@ def get(path, headers: { "SERVER_PROTOCOL" => "HTTP/1.1", "HTTP_VERSION" => "HTT end end - test "do not stream on HTTP/1.0" do - get "/render_streaming/basic/hello_world", headers: { "HTTP_VERSION" => "HTTP/1.0" } - assert_body "Hello world, I'm here!" - assert_status 200 - assert_equal "22", headers["Content-Length"] - assert_nil headers["Transfer-Encoding"] - end - def assert_streaming!(cache = "no-cache") assert_status 200 - assert_nil headers["Content-Length"] - assert_equal "chunked", headers["Transfer-Encoding"] - assert_equal cache, headers["Cache-Control"] + assert_equal cache, headers["cache-control"] end end end