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

new(with_rack_app) click to toggle source
# File lib/fast_send.rb, line 102
def initialize(with_rack_app)
  @app = with_rack_app
end

Public Instance Methods

call(env) click to toggle source
# 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

callbacks_from_headers(h) click to toggle source
# 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
has_robust_hijack_support?(env) click to toggle source
# 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
response_via_hijack(status, headers, each_file_body) click to toggle source
# 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
response_via_naive_each(s, h, b) click to toggle source
# 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