Class: FastSend

Inherits:
Object
  • Object
show all
Defined in:
lib/fast_send.rb,
lib/fast_send/version.rb

Overview

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

Defined Under Namespace

Classes: NaiveEach, SocketHandler

Constant Summary collapse

CLIENT_DISCONNECTS =

All exceptions that get raised when the client closes a connection before receiving the entire response

[Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN, Errno::EPROTOTYPE]
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)

Class.new(StandardError)
VERSION =
"1.1.3"
CALLBACK_HEADER_NAMES =
%w( 
  fast_send.started
  fast_send.aborted
  fast_send.error
  fast_send.complete
  fast_send.bytes_sent
  fast_send.cleanup
).freeze

Instance Method Summary collapse

Constructor Details

#initialize(with_rack_app) ⇒ FastSend

Returns a new instance of FastSend.



101
102
103
# File 'lib/fast_send.rb', line 101

def initialize(with_rack_app)
  @app = with_rack_app
end

Instance Method Details

#call(env) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/fast_send.rb', line 105

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