Skip to content

Commit

Permalink
Replace RewindableInput with TeeInput which doesn't block until the e…
Browse files Browse the repository at this point in the history
…ntire input is read.
  • Loading branch information
FooBarWidget committed Feb 10, 2012
1 parent ead136f commit 0c26905
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 127 deletions.
4 changes: 2 additions & 2 deletions lib/phusion_passenger/rack/request_handler.rb
Expand Up @@ -23,7 +23,7 @@
# THE SOFTWARE.

require 'phusion_passenger/abstract_request_handler'
require 'phusion_passenger/utils/rewindable_input'
require 'phusion_passenger/utils/tee_input'

module PhusionPassenger
module Rack
Expand Down Expand Up @@ -65,7 +65,7 @@ def initialize(owner_pipe, app, options = {})
protected
# Overrided method.
def process_request(env, input, output, full_http_response)
rewindable_input = PhusionPassenger::Utils::RewindableInput.new(input)
rewindable_input = PhusionPassenger::Utils::TeeInput.new(input, env)
begin
env[RACK_VERSION] = RACK_VERSION_VALUE
env[RACK_INPUT] = rewindable_input
Expand Down
125 changes: 0 additions & 125 deletions lib/phusion_passenger/utils/rewindable_input.rb

This file was deleted.

174 changes: 174 additions & 0 deletions lib/phusion_passenger/utils/tee_input.rb
@@ -0,0 +1,174 @@
# encoding: binary
#
# This file is taken from Unicorn. The following license applies to this file
# (and this file only, not to the rest of Phusion Passenger):
#
# 1. You may make and give away verbatim copies of the source form of the
# software without restriction, provided that you duplicate all of the
# original copyright notices and associated disclaimers.
#
# 2. You may modify your copy of the software in any way, provided that
# you do at least ONE of the following:
#
# a) place your modifications in the Public Domain or otherwise make them
# Freely Available, such as by posting said modifications to Usenet or an
# equivalent medium, or by allowing the author to include your
# modifications in the software.
#
# b) use the modified software only within your corporation or
# organization.
#
# c) rename any non-standard executables so the names do not conflict with
# standard executables, which must also be provided.
#
# d) make other distribution arrangements with the author.
#
# 3. You may distribute the software in object code or executable
# form, provided that you do at least ONE of the following:
#
# a) distribute the executables and library files of the software,
# together with instructions (in the manual page or equivalent) on where
# to get the original distribution.
#
# b) accompany the distribution with the machine-readable source of the
# software.
#
# c) give non-standard executables non-standard names, with
# instructions on where to get the original software distribution.
#
# d) make other distribution arrangements with the author.
#
# 4. You may modify and include the part of the software into any other
# software (possibly commercial). But some files in the distribution
# are not written by the author, so that they are not under this terms.
#
# 5. The scripts and library files supplied as input to or produced as
# output from the software do not automatically fall under the
# copyright of the software, but belong to whomever generated them,
# and may be sold commercially, and may be aggregated with this
# software.
#
# 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE.

require 'stringio'
require 'phusion_passenger/utils/tmpio'

module PhusionPassenger
module Utils

# acts like tee(1) on an input input to provide a input-like stream
# while providing rewindable semantics through a File/StringIO backing
# store. On the first pass, the input is only read on demand so your
# Rack application can use input notification (upload progress and
# like). This should fully conform to the Rack::Lint::InputWrapper
# specification on the public API. This class is intended to be a
# strict interpretation of Rack::Lint::InputWrapper functionality and
# will not support any deviations from it.
#
# When processing uploads, Unicorn exposes a TeeInput object under
# "rack.input" of the Rack environment.
class TeeInput
CONTENT_LENGTH = "CONTENT_LENGTH".freeze

# The maximum size (in +bytes+) to buffer in memory before
# resorting to a temporary file. Default is 112 kilobytes.
@@client_body_buffer_size = 112 * 1024

# sets the maximum size of request bodies to buffer in memory,
# amounts larger than this are buffered to the filesystem
def self.client_body_buffer_size=(bytes)
@@client_body_buffer_size = bytes
end

# returns the maximum size of request bodies to buffer in memory,
# amounts larger than this are buffered to the filesystem
def self.client_body_buffer_size
@@client_body_buffer_size
end

# Initializes a new TeeInput object. You normally do not have to call
# this unless you are writing an HTTP server.
def initialize(socket, env)
@len = env[CONTENT_LENGTH]
@len = @len.to_i if @len
@socket = socket
@tmp = @len && @len <= @@client_body_buffer_size ?
StringIO.new("") : TmpIO.new("PassengerTeeInput")
end

def close
@tmp.close
end

def size
@len and return @len
pos = @tmp.pos
consume!
@tmp.pos = pos
@len = @tmp.size
end

def read(*args)
if socket_drained?
@tmp.read(*args)
else
tee(@socket.read(*args))
end
end

def gets
if socket_drained?
@tmp.gets
else
tee(@socket.gets)
end
end

def rewind
return 0 if 0 == @tmp.size
consume! if !socket_drained?
@tmp.rewind # Rack does not specify what the return value is here
end

def each
while line = gets
yield line
end

self # Rack does not specify what the return value is here
end

private

def socket_drained?
if @socket
if @socket.eof?
@socket = nil
true
else
false
end
else
false
end
end

# consumes the stream of the socket
def consume!
junk = ""
nil while read(16 * 1024, junk)
end

def tee(buffer)
if buffer && buffer.size > 0
@tmp.write(buffer)
end
buffer
end
end

end # module Utils
end # module PhusionPassenger
33 changes: 33 additions & 0 deletions lib/phusion_passenger/utils/tmpio.rb
@@ -0,0 +1,33 @@
require 'tmpdir'

module PhusionPassenger
module Utils

# some versions of Ruby had a broken Tempfile which didn't work
# well with unlinked files. This one is much shorter, easier
# to understand, and slightly faster.
class TmpIO < File

# creates and returns a new File object. The File is unlinked
# immediately, switched to binary mode, and userspace output
# buffering is disabled
def self.new(namespace)
fp = begin
super("#{Dir::tmpdir}/#{namespace}-#{rand}", RDWR|CREAT|EXCL, 0600)
rescue Errno::EEXIST
retry
end
unlink(fp.path)
fp.binmode
fp.sync = true
fp
end

# for easier env["rack.input"] compatibility with Rack <= 1.1
def size
stat.size
end unless File.method_defined?(:size)
end

end # module Utils
end # module PhusionPassenger
6 changes: 6 additions & 0 deletions lib/phusion_passenger/utils/unseekable_socket.rb
Expand Up @@ -156,6 +156,12 @@ def each(&block)
rescue => e
raise annotate(e)
end

def eof?
@socket.eof?
rescue => e
raise annotate(e)
end

def closed?
@socket.closed?
Expand Down

0 comments on commit 0c26905

Please sign in to comment.