1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
| | # -*- encoding: binary -*-
# 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 Unicorn::TeeInput < Unicorn::StreamInput
# The maximum size (in +bytes+) to buffer in memory before
# resorting to a temporary file. Default is 112 kilobytes.
@@client_body_buffer_size = Unicorn::Const::MAX_BODY
# 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, request)
@len = request.content_length
super
@tmp = @len && @len <= @@client_body_buffer_size ?
StringIO.new("") : Unicorn::TmpIO.new
end
# :call-seq:
# ios.size => Integer
#
# Returns the size of the input. For requests with a Content-Length
# header value, this will not read data off the socket and just return
# the value of the Content-Length header as an Integer.
#
# For Transfer-Encoding:chunked requests, this requires consuming
# all of the input stream before returning since there's no other
# way to determine the size of the request body beforehand.
#
# This method is no longer part of the Rack specification as of
# Rack 1.2, so its use is not recommended. This method only exists
# for compatibility with Rack applications designed for Rack 1.1 and
# earlier. Most applications should only need to call +read+ with a
# specified +length+ in a loop until it returns +nil+.
def size
@len and return @len
pos = @tmp.pos
consume!
@tmp.pos = pos
@len = @tmp.size
end
# :call-seq:
# ios.read([length [, buffer ]]) => string, buffer, or nil
#
# Reads at most length bytes from the I/O stream, or to the end of
# file if length is omitted or is nil. length must be a non-negative
# integer or nil. If the optional buffer argument is present, it
# must reference a String, which will receive the data.
#
# At end of file, it returns nil or "" depend on length.
# ios.read() and ios.read(nil) returns "".
# ios.read(length [, buffer]) returns nil.
#
# If the Content-Length of the HTTP request is known (as is the common
# case for POST requests), then ios.read(length [, buffer]) will block
# until the specified length is read (or it is the last chunk).
# Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
# ios.read(length [, buffer]) will return immediately if there is
# any data and only block when nothing is available (providing
# IO#readpartial semantics).
def read(*args)
@socket ? tee(super) : @tmp.read(*args)
end
# :call-seq:
# ios.gets => string or nil
#
# Reads the next ``line'' from the I/O stream; lines are separated
# by the global record separator ($/, typically "\n"). A global
# record separator of nil reads the entire unread contents of ios.
# Returns nil if called at the end of file.
# This takes zero arguments for strict Rack::Lint compatibility,
# unlike IO#gets.
def gets
@socket ? tee(super) : @tmp.gets
end
# :call-seq:
# ios.rewind => 0
#
# Positions the *ios* pointer to the beginning of input, returns
# the offset (zero) of the +ios+ pointer. Subsequent reads will
# start from the beginning of the previously-buffered input.
def rewind
return 0 if 0 == @tmp.size
consume! if @socket
@tmp.rewind # Rack does not specify what the return value is here
end
private
# consumes the stream of the socket
def consume!
junk = ""
nil while read(@@io_chunk_size, junk)
end
def tee(buffer)
if buffer && buffer.size > 0
@tmp.write(buffer)
end
buffer
end
end
|