forked from macournoyer/thin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
request.rb
158 lines (131 loc) · 4.59 KB
/
request.rb
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
require 'thin_parser'
require 'tempfile'
module Thin
# Raised when an incoming request is not valid
# and the server can not process it.
class InvalidRequest < IOError; end
# A request sent by the client to the server.
class Request
# Maximum request body size before it is moved out of memory
# and into a tempfile for reading.
MAX_BODY = 1024 * (80 + 32)
BODY_TMPFILE = 'thin-body'.freeze
MAX_HEADER = 1024 * (80 + 32)
# Freeze some HTTP header names & values
SERVER_SOFTWARE = 'SERVER_SOFTWARE'.freeze
SERVER_NAME = 'SERVER_NAME'.freeze
LOCALHOST = 'localhost'.freeze
HTTP_VERSION = 'HTTP_VERSION'.freeze
HTTP_1_0 = 'HTTP/1.0'.freeze
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'.freeze
CONTENT_LENGTH = 'CONTENT_LENGTH'.freeze
CONNECTION = 'HTTP_CONNECTION'.freeze
KEEP_ALIVE_REGEXP = /\bkeep-alive\b/i.freeze
CLOSE_REGEXP = /\bclose\b/i.freeze
# Freeze some Rack header names
RACK_INPUT = 'rack.input'.freeze
RACK_VERSION = 'rack.version'.freeze
RACK_ERRORS = 'rack.errors'.freeze
RACK_MULTITHREAD = 'rack.multithread'.freeze
RACK_MULTIPROCESS = 'rack.multiprocess'.freeze
RACK_RUN_ONCE = 'rack.run_once'.freeze
ASYNC_CALLBACK = 'async.callback'.freeze
ASYNC_CLOSE = 'async.close'.freeze
# CGI-like request environment variables
attr_reader :env
# Unparsed data of the request
attr_reader :data
# Request body
attr_reader :body
def initialize
@parser = Thin::HttpParser.new
@data = ''
@nparsed = 0
@body = StringIO.new
@env = {
SERVER_SOFTWARE => SERVER,
SERVER_NAME => LOCALHOST,
# Rack stuff
RACK_INPUT => @body,
RACK_VERSION => VERSION::RACK,
RACK_ERRORS => STDERR,
RACK_MULTITHREAD => false,
RACK_MULTIPROCESS => false,
RACK_RUN_ONCE => false
}
end
# Parse a chunk of data into the request environment
# Raises a +InvalidRequest+ if invalid.
# Returns +true+ if the parsing is complete.
def parse(data)
if @parser.finished? # Header finished, can only be some more body
body << data
else # Parse more header using the super parser
@data << data
raise InvalidRequest, 'Header longer than allowed' if @data.size > MAX_HEADER
@nparsed = @parser.execute(@env, @data, @nparsed)
# Transfert to a tempfile if body is very big
move_body_to_tempfile if @parser.finished? && content_length > MAX_BODY
end
if finished? # Check if header and body are complete
@data = nil
@body.rewind
true # Request is fully parsed
else
false # Not finished, need more data
end
end
# +true+ if headers and body are finished parsing
def finished?
@parser.finished? && @body.size >= content_length
end
# Expected size of the body
def content_length
@env[CONTENT_LENGTH].to_i
end
# Returns +true+ if the client expect the connection to be persistent.
def persistent?
# Clients and servers SHOULD NOT assume that a persistent connection
# is maintained for HTTP versions less than 1.1 unless it is explicitly
# signaled. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html)
if @env[HTTP_VERSION] == HTTP_1_0
@env[CONNECTION] =~ KEEP_ALIVE_REGEXP
# HTTP/1.1 client intends to maintain a persistent connection unless
# a Connection header including the connection-token "close" was sent
# in the request
else
@env[CONNECTION].nil? || @env[CONNECTION] !~ CLOSE_REGEXP
end
end
def remote_address=(address)
@env[REMOTE_ADDR] = address
end
def forwarded_for
@env[FORWARDED_FOR]
end
def threaded=(value)
@env[RACK_MULTITHREAD] = value
end
def async_callback=(callback)
@env[ASYNC_CALLBACK] = callback
@env[ASYNC_CLOSE] = EventMachine::DefaultDeferrable.new
end
def async_close
@async_close ||= @env[ASYNC_CLOSE]
end
# Close any resource used by the request
def close
@body.delete if @body.class == Tempfile
end
private
def move_body_to_tempfile
current_body = @body
current_body.rewind
@body = Tempfile.new(BODY_TMPFILE)
@body.binmode
@body << current_body.read
@env[RACK_INPUT] = @body
end
end
end