Class: Rack::HttpStreamingResponse
- Inherits:
-
Object
- Object
- Rack::HttpStreamingResponse
- Defined in:
- lib/rack/http_streaming_response.rb
Overview
A lazy Rack body that streams a backend Net::HTTP response without buffering it.
The request runs inside a Fiber, using only the public block form of
Net::HTTP#request: the Fiber pauses (Fiber.yield res) the moment the
status and headers are available, and #each resumes it to pull body chunks
as the server consumes them. This replaced the 2010-era monkey-patch of
private net/http internals (net_http_hacked.rb, deleted in 1.0) and
inherits upstream's handling of 1xx interim responses, keep-alive
negotiation, and transport errors.
A Fiber can only be resumed from the thread that created it. The request Fiber is created lazily on first use (#code/#headers/#each), so the normal Rack flow — one thread calls the app and then iterates the body — is fine. If #close is called from a different thread (some servers do this on client abort), the Fiber unwind is skipped and the connection is hard-closed instead, which releases the socket either way.
Defined Under Namespace
Classes: ResponseTooLarge, StreamAborted
Constant Summary collapse
- STATUSES_WITH_NO_ENTITY_BODY =
{ 204 => true, 205 => true, 304 => true }.freeze
Instance Attribute Summary collapse
-
#cert ⇒ Object
Returns the value of attribute cert.
-
#key ⇒ Object
Returns the value of attribute key.
-
#logger ⇒ Object
Returns the value of attribute logger.
-
#max_response_length ⇒ Object
Returns the value of attribute max_response_length.
-
#read_timeout ⇒ Object
Returns the value of attribute read_timeout.
-
#ssl_version ⇒ Object
Returns the value of attribute ssl_version.
-
#use_ssl ⇒ Object
Returns the value of attribute use_ssl.
-
#verify_mode ⇒ Object
Returns the value of attribute verify_mode.
Instance Method Summary collapse
- #body ⇒ Object
-
#close ⇒ Object
Rack calls #close on the response body when it is done with it, including when it bails out early (HEAD, 304, a client disconnect).
- #code ⇒ Object (also: #status)
-
#each(&block) ⇒ Object
Can be called only once!.
- #headers ⇒ Object
-
#initialize(request, host, port = nil, &configure) ⇒ HttpStreamingResponse
constructor
An optional block receives the Net::HTTP instance for configuration before it connects — this is the single source of truth used by Rack::Proxy (see Rack::Proxy#configure_backend_connection).
- #to_s ⇒ Object
Constructor Details
#initialize(request, host, port = nil, &configure) ⇒ HttpStreamingResponse
An optional block receives the Net::HTTP instance for configuration before it connects — this is the single source of truth used by Rack::Proxy (see Rack::Proxy#configure_backend_connection). When no block is given, the public accessors above are applied instead (backward-compatible path for direct users of this class).
50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/rack/http_streaming_response.rb', line 50 def initialize(request, host, port = nil, &configure) @request, @host, @port, @configure = request, host, port, configure # Forward the backend body verbatim. Without this, Net::HTTP inflates # gzip/deflate bodies for requests that opted in (the default for e.g. # Net::HTTP::Get), leaving the already-forwarded Content-Length and # Content-Encoding describing bytes the client never receives. The old # patched read path never decoded; keep that contract. if request.instance_variable_defined?(:@decode_content) request.instance_variable_set(:@decode_content, false) end end |
Instance Attribute Details
#cert ⇒ Object
Returns the value of attribute cert.
42 43 44 |
# File 'lib/rack/http_streaming_response.rb', line 42 def cert @cert end |
#key ⇒ Object
Returns the value of attribute key.
42 43 44 |
# File 'lib/rack/http_streaming_response.rb', line 42 def key @key end |
#logger ⇒ Object
Returns the value of attribute logger.
42 43 44 |
# File 'lib/rack/http_streaming_response.rb', line 42 def logger @logger end |
#max_response_length ⇒ Object
Returns the value of attribute max_response_length.
43 44 45 |
# File 'lib/rack/http_streaming_response.rb', line 43 def max_response_length @max_response_length end |
#read_timeout ⇒ Object
Returns the value of attribute read_timeout.
42 43 44 |
# File 'lib/rack/http_streaming_response.rb', line 42 def read_timeout @read_timeout end |
#ssl_version ⇒ Object
Returns the value of attribute ssl_version.
42 43 44 |
# File 'lib/rack/http_streaming_response.rb', line 42 def ssl_version @ssl_version end |
#use_ssl ⇒ Object
Returns the value of attribute use_ssl.
42 43 44 |
# File 'lib/rack/http_streaming_response.rb', line 42 def use_ssl @use_ssl end |
#verify_mode ⇒ Object
Returns the value of attribute verify_mode.
42 43 44 |
# File 'lib/rack/http_streaming_response.rb', line 42 def verify_mode @verify_mode end |
Instance Method Details
#body ⇒ Object
63 64 65 |
# File 'lib/rack/http_streaming_response.rb', line 63 def body self end |
#close ⇒ Object
Rack calls #close on the response body when it is done with it, including when it bails out early (HEAD, 304, a client disconnect). Without this, a body that is never iterated leaks the backend TCP/TLS connection until GC.
116 117 118 |
# File 'lib/rack/http_streaming_response.rb', line 116 def close close_connection end |
#code ⇒ Object Also known as: status
67 68 69 70 71 |
# File 'lib/rack/http_streaming_response.rb', line 67 def code response.code.to_i.tap do |response_code| STATUSES_WITH_NO_ENTITY_BODY[response_code] && close_connection end end |
#each(&block) ⇒ Object
Can be called only once!
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rack/http_streaming_response.rb', line 80 def each(&block) return if connection_closed response # make sure the request has started and the headers are in bytes = 0 while @fiber.alive? chunk = @fiber.resume # The last resume returns the Fiber's terminal value, not a body chunk. next unless chunk.is_a?(String) if max_response_length bytes += chunk.bytesize if bytes > max_response_length raise ResponseTooLarge, "backend response exceeded max_response_length=#{max_response_length}" end end block.call(chunk) end rescue => e # The status/headers are already on the wire, so we can't turn a mid-stream # backend failure into a 502. Log it and re-raise so the server aborts the # transfer (the client sees a truncated response, not a false "complete"). logger << "rack-proxy: streaming backend read failed: #{e.class}: #{e.}\n" if logger.respond_to?(:<<) raise ensure close_connection end |
#headers ⇒ Object
75 76 77 |
# File 'lib/rack/http_streaming_response.rb', line 75 def headers Rack::Proxy.build_header_hash(response.to_hash) end |
#to_s ⇒ Object
109 110 111 |
# File 'lib/rack/http_streaming_response.rb', line 109 def to_s @to_s ||= StringIO.new.tap { |io| each { |line| io << line } }.string end |