class FastSend
A Rack middleware that sends the response using file buffers. If the response body returned by the upstream application supports “each_file”, then the middleware will call this method, grab each yielded file in succession and use the fastest possible way to send it to the client (using a response wrapper or Rack hijacking). If sendfile support is available on the client socket, sendfile() will be used to stream the file via the OS.
A sample response body object will look like this:
class Files def each_file File.open('data1.bin','r') {|f| yield(f) } File.open('data2.bin','r') {|f| yield(f) } end end # and then in your Rack app return [200, {'Content-Type' => 'binary/octet-stream'}, Files.new]
Note that the receiver of `each_file` is responsbble for closing and deallocating the file if necessary.
You can also supply the following response headers that will be used as callbacks during the response send on the way out.
`fast_send.started' => ->(zero_bytes) { } # When the response is started `fast_send.bytes_sent' => ->(sent_this_time, sent_total) { } # Called on each sent chunk `fast_send.complete' => ->(sent_total) { } # When response completes without exceptions `fast_send.aborted' => ->(exception) { } # When the response is not sent completely, both for exceptions and client closes `fast_send.error' => ->(exception) { } # the response is not sent completely due to an error in the application `fast_send.cleanup' => ->(sent_total) { } # Called at the end of the response, in an ensure block
Constants
- CALLBACK_HEADER_NAMES
- CLIENT_DISCONNECTS
All exceptions that get raised when the client closes a connection before receiving the entire response. IOError can be raised when an IO times out in IO.select()
- C_Connection
- C_SERVER_SOFTWARE
- C_close
- C_dispatch
- C_hijack
- C_naive
- C_rack_hijack
- C_rack_logger
- NOOP
- UnknownCallback
Gets raised if a fast_send.something is mentioned in the response headers but is not supported as a callback (the dangers of hashmaps as datastructures is that you can sometimes mistype keys)
- VERSION
Public Class Methods
# File lib/fast_send.rb, line 102 def initialize(with_rack_app) @app = with_rack_app end
Public Instance Methods
# File lib/fast_send.rb, line 106 def call(env) s, h, b = @app.call(env) return [s, h, b] unless b.respond_to?(:each_file) @logger = env.fetch(C_rack_logger) { NullLogger } server = env[C_SERVER_SOFTWARE] if has_robust_hijack_support?(env) @logger.debug { 'Server (%s) allows partial hijack, setting up Connection: close' % server } h[C_Connection] = C_close h[C_dispatch] = C_hijack response_via_hijack(s, h, b) else @logger.warn { msg = 'Server (%s) has no hijack support or hijacking is broken. Unwanted buffering possible.' msg % server } h[C_dispatch] = C_naive response_via_naive_each(s, h, b) end end
Private Instance Methods
# File lib/fast_send.rb, line 142 def callbacks_from_headers(h) headers_related = h.keys.grep(/^fast\_send\./i) headers_related.each do | header_name | unless CALLBACK_HEADER_NAMES.include?(header_name) msg = "Unknown callback #{header_name.inspect} (supported: #{CALLBACK_HEADER_NAMES.join(', ')})" raise UnknownCallback, msg end end CALLBACK_HEADER_NAMES.map{|cb_name| h.delete(cb_name) || NOOP } end
# File lib/fast_send.rb, line 131 def has_robust_hijack_support?(env) return false unless env['rack.hijack?'] return false if env['SERVER_SOFTWARE'] =~ /^WEBrick/ # WEBrick implements hijack using a pipe true end
# File lib/fast_send.rb, line 153 def response_via_hijack(status, headers, each_file_body) headers[C_rack_hijack] = SocketHandler.new(each_file_body, @logger, *callbacks_from_headers(headers)) [status, headers, []] end
# File lib/fast_send.rb, line 137 def response_via_naive_each(s, h, b) body = NaiveEach.new(b, *callbacks_from_headers(h)) [s, h, body] end