unicorn 0.2.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG +1 -0
- data/DESIGN +4 -0
- data/GNUmakefile +30 -6
- data/Manifest +62 -3
- data/README +52 -42
- data/SIGNALS +17 -17
- data/TODO +27 -5
- data/bin/unicorn +15 -13
- data/bin/unicorn_rails +59 -22
- data/ext/unicorn/http11/http11.c +25 -104
- data/ext/unicorn/http11/http11_parser.c +24 -23
- data/ext/unicorn/http11/http11_parser.h +1 -3
- data/ext/unicorn/http11/http11_parser.rl +2 -1
- data/lib/unicorn.rb +58 -44
- data/lib/unicorn/app/old_rails.rb +23 -0
- data/lib/unicorn/app/old_rails/static.rb +58 -0
- data/lib/unicorn/cgi_wrapper.rb +151 -0
- data/lib/unicorn/configurator.rb +71 -31
- data/lib/unicorn/const.rb +9 -34
- data/lib/unicorn/http_request.rb +63 -66
- data/lib/unicorn/http_response.rb +6 -1
- data/lib/unicorn/socket.rb +15 -2
- data/test/benchmark/README +55 -0
- data/test/benchmark/big_request.rb +35 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/request.rb +47 -0
- data/test/benchmark/response.rb +29 -0
- data/test/exec/test_exec.rb +41 -157
- data/test/rails/app-1.2.3/.gitignore +2 -0
- data/test/rails/app-1.2.3/app/controllers/application.rb +4 -0
- data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-1.2.3/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-1.2.3/config/boot.rb +9 -0
- data/test/rails/app-1.2.3/config/database.yml +12 -0
- data/test/rails/app-1.2.3/config/environment.rb +10 -0
- data/test/rails/app-1.2.3/config/environments/development.rb +7 -0
- data/test/rails/app-1.2.3/config/environments/production.rb +3 -0
- data/test/rails/app-1.2.3/config/routes.rb +4 -0
- data/test/rails/app-1.2.3/db/.gitignore +0 -0
- data/test/rails/app-1.2.3/public/404.html +1 -0
- data/test/rails/app-1.2.3/public/500.html +1 -0
- data/test/rails/app-2.0.2/.gitignore +2 -0
- data/test/rails/app-2.0.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.0.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.0.2/config/boot.rb +9 -0
- data/test/rails/app-2.0.2/config/database.yml +12 -0
- data/test/rails/app-2.0.2/config/environment.rb +14 -0
- data/test/rails/app-2.0.2/config/environments/development.rb +6 -0
- data/test/rails/app-2.0.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.0.2/config/routes.rb +4 -0
- data/test/rails/app-2.0.2/db/.gitignore +0 -0
- data/test/rails/app-2.0.2/public/404.html +1 -0
- data/test/rails/app-2.0.2/public/500.html +1 -0
- data/test/rails/app-2.2.2/.gitignore +2 -0
- data/test/rails/app-2.2.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.2.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.2.2/config/boot.rb +109 -0
- data/test/rails/app-2.2.2/config/database.yml +12 -0
- data/test/rails/app-2.2.2/config/environment.rb +14 -0
- data/test/rails/app-2.2.2/config/environments/development.rb +5 -0
- data/test/rails/app-2.2.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.2.2/config/routes.rb +4 -0
- data/test/rails/app-2.2.2/db/.gitignore +0 -0
- data/test/rails/app-2.2.2/public/404.html +1 -0
- data/test/rails/app-2.2.2/public/500.html +1 -0
- data/test/rails/app-2.3.2.1/.gitignore +2 -0
- data/test/rails/app-2.3.2.1/app/controllers/application_controller.rb +3 -0
- data/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.3.2.1/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.3.2.1/config/boot.rb +107 -0
- data/test/rails/app-2.3.2.1/config/database.yml +12 -0
- data/test/rails/app-2.3.2.1/config/environment.rb +14 -0
- data/test/rails/app-2.3.2.1/config/environments/development.rb +5 -0
- data/test/rails/app-2.3.2.1/config/environments/production.rb +4 -0
- data/test/rails/app-2.3.2.1/config/routes.rb +4 -0
- data/test/rails/app-2.3.2.1/db/.gitignore +0 -0
- data/test/rails/app-2.3.2.1/public/404.html +1 -0
- data/test/rails/app-2.3.2.1/public/500.html +1 -0
- data/test/rails/test_rails.rb +243 -0
- data/test/test_helper.rb +149 -2
- data/test/unit/test_configurator.rb +46 -0
- data/test/unit/test_http_parser.rb +77 -36
- data/test/unit/test_request.rb +2 -0
- data/test/unit/test_response.rb +20 -4
- data/test/unit/test_server.rb +30 -1
- data/test/unit/test_socket_helper.rb +159 -0
- data/unicorn.gemspec +5 -5
- metadata +68 -5
- data/test/benchmark/previous.rb +0 -11
- data/test/benchmark/simple.rb +0 -11
- data/test/benchmark/utils.rb +0 -82
data/lib/unicorn/configurator.rb
CHANGED
|
@@ -8,22 +8,21 @@ module Unicorn
|
|
|
8
8
|
#
|
|
9
9
|
# Example (when used with the unicorn config file):
|
|
10
10
|
# worker_processes 4
|
|
11
|
-
#
|
|
11
|
+
# listen '/tmp/my_app.sock', :backlog => 1
|
|
12
|
+
# listen '0.0.0.0:9292'
|
|
12
13
|
# timeout 10
|
|
13
14
|
# pid "/tmp/my_app.pid"
|
|
14
15
|
# after_fork do |server,worker_nr|
|
|
15
16
|
# server.listen("127.0.0.1:#{9293 + worker_nr}") rescue nil
|
|
16
17
|
# end
|
|
17
18
|
class Configurator
|
|
18
|
-
include ::Unicorn::SocketHelper
|
|
19
|
-
|
|
20
19
|
# The default logger writes its output to $stderr
|
|
21
20
|
DEFAULT_LOGGER = Logger.new($stderr) unless defined?(DEFAULT_LOGGER)
|
|
22
21
|
|
|
23
22
|
# Default settings for Unicorn
|
|
24
23
|
DEFAULTS = {
|
|
25
24
|
:timeout => 60,
|
|
26
|
-
:listeners => [
|
|
25
|
+
:listeners => [],
|
|
27
26
|
:logger => DEFAULT_LOGGER,
|
|
28
27
|
:worker_processes => 1,
|
|
29
28
|
:after_fork => lambda { |server, worker_nr|
|
|
@@ -43,7 +42,6 @@ module Unicorn
|
|
|
43
42
|
server.logger.info("forked child re-executing...")
|
|
44
43
|
},
|
|
45
44
|
:pid => nil,
|
|
46
|
-
:backlog => 1024,
|
|
47
45
|
:preload_app => false,
|
|
48
46
|
:stderr_path => nil,
|
|
49
47
|
:stdout_path => nil,
|
|
@@ -83,23 +81,6 @@ module Unicorn
|
|
|
83
81
|
@set[key]
|
|
84
82
|
end
|
|
85
83
|
|
|
86
|
-
# Changes the listen() syscall backlog to +nr+ for yet-to-be-created
|
|
87
|
-
# sockets. Due to limitations of the OS, this cannot affect
|
|
88
|
-
# existing listener sockets in any way, sockets must be completely
|
|
89
|
-
# closed and rebound (inherited sockets preserve their existing
|
|
90
|
-
# backlog setting). Some operating systems allow negative values
|
|
91
|
-
# here to specify the maximum allowable value. See the listen(2)
|
|
92
|
-
# syscall documentation of your OS for the exact semantics of this.
|
|
93
|
-
#
|
|
94
|
-
# If you are running unicorn on multiple machines, lowering this number
|
|
95
|
-
# can help your load balancer detect when a machine is overloaded
|
|
96
|
-
# and give requests to a different machine.
|
|
97
|
-
def backlog(nr)
|
|
98
|
-
Integer === nr or raise ArgumentError,
|
|
99
|
-
"not an integer: backlog=#{nr.inspect}"
|
|
100
|
-
@set[:backlog] = nr
|
|
101
|
-
end
|
|
102
|
-
|
|
103
84
|
# sets object to the +new+ Logger-like object. The new logger-like
|
|
104
85
|
# object must respond to the following methods:
|
|
105
86
|
# +debug+, +info+, +warn+, +error+, +fatal+, +close+
|
|
@@ -171,16 +152,65 @@ module Unicorn
|
|
|
171
152
|
# sets listeners to the given +addresses+, replacing or augmenting the
|
|
172
153
|
# current set. This is for the global listener pool shared by all
|
|
173
154
|
# worker processes. For per-worker listeners, see the after_fork example
|
|
174
|
-
|
|
155
|
+
# This is for internal API use only, do not use it in your Unicorn
|
|
156
|
+
# config file. Use listen instead.
|
|
157
|
+
def listeners(addresses) # :nodoc:
|
|
175
158
|
Array === addresses or addresses = Array(addresses)
|
|
176
159
|
addresses.map! { |addr| expand_addr(addr) }
|
|
177
160
|
@set[:listeners] = addresses
|
|
178
161
|
end
|
|
179
162
|
|
|
180
|
-
# adds an +address+ to the existing listener set
|
|
181
|
-
|
|
163
|
+
# adds an +address+ to the existing listener set.
|
|
164
|
+
#
|
|
165
|
+
# The following options may be specified (but are generally not needed):
|
|
166
|
+
#
|
|
167
|
+
# +backlog+: this is the backlog of the listen() syscall.
|
|
168
|
+
#
|
|
169
|
+
# Some operating systems allow negative values here to specify the
|
|
170
|
+
# maximum allowable value. In most cases, this number is only
|
|
171
|
+
# recommendation and there are other OS-specific tunables and
|
|
172
|
+
# variables that can affect this number. See the listen(2)
|
|
173
|
+
# syscall documentation of your OS for the exact semantics of
|
|
174
|
+
# this.
|
|
175
|
+
#
|
|
176
|
+
# If you are running unicorn on multiple machines, lowering this number
|
|
177
|
+
# can help your load balancer detect when a machine is overloaded
|
|
178
|
+
# and give requests to a different machine.
|
|
179
|
+
#
|
|
180
|
+
# Default: 1024
|
|
181
|
+
#
|
|
182
|
+
# +rcvbuf+, +sndbuf+: maximum send and receive buffer sizes of sockets
|
|
183
|
+
#
|
|
184
|
+
# These correspond to the SO_RCVBUF and SO_SNDBUF settings which
|
|
185
|
+
# can be set via the setsockopt(2) syscall. Some kernels
|
|
186
|
+
# (e.g. Linux 2.4+) have intelligent auto-tuning mechanisms and
|
|
187
|
+
# there is no need (and it is sometimes detrimental) to specify them.
|
|
188
|
+
#
|
|
189
|
+
# See the socket API documentation of your operating system
|
|
190
|
+
# to determine the exact semantics of these settings and
|
|
191
|
+
# other operating system-specific knobs where they can be
|
|
192
|
+
# specified.
|
|
193
|
+
#
|
|
194
|
+
# Defaults: operating system defaults
|
|
195
|
+
#
|
|
196
|
+
# Due to limitations of the operating system, options specified here
|
|
197
|
+
# cannot affect existing listener sockets in any way, sockets must be
|
|
198
|
+
# completely closed and rebound.
|
|
199
|
+
def listen(address, opt = { :backlog => 1024 })
|
|
200
|
+
address = expand_addr(address)
|
|
201
|
+
if String === address
|
|
202
|
+
Hash === @set[:listener_opts] or
|
|
203
|
+
@set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
|
|
204
|
+
[ :backlog, :sndbuf, :rcvbuf ].each do |key|
|
|
205
|
+
value = opt[key] or next
|
|
206
|
+
Integer === value or
|
|
207
|
+
raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
|
|
208
|
+
end
|
|
209
|
+
@set[:listener_opts][address].merge!(opt)
|
|
210
|
+
end
|
|
211
|
+
|
|
182
212
|
@set[:listeners] = [] unless Array === @set[:listeners]
|
|
183
|
-
@set[:listeners] <<
|
|
213
|
+
@set[:listeners] << address
|
|
184
214
|
end
|
|
185
215
|
|
|
186
216
|
# sets the +path+ for the PID file of the unicorn master process
|
|
@@ -254,16 +284,26 @@ module Unicorn
|
|
|
254
284
|
@set[var] = my_proc
|
|
255
285
|
end
|
|
256
286
|
|
|
287
|
+
# expands "unix:path/to/foo" to a socket relative to the current path
|
|
257
288
|
# expands pathnames of sockets if relative to "~" or "~username"
|
|
258
289
|
# expands "*:port and ":port" to "0.0.0.0:port"
|
|
259
290
|
def expand_addr(address) #:nodoc
|
|
260
291
|
return address unless String === address
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
292
|
+
|
|
293
|
+
case address
|
|
294
|
+
when %r{\Aunix:(.*)\z}
|
|
295
|
+
File.expand_path($1)
|
|
296
|
+
when %r{\A~}
|
|
297
|
+
File.expand_path(address)
|
|
298
|
+
when %r{\A\*:(\d+)\z}
|
|
299
|
+
"0.0.0.0:#$1"
|
|
300
|
+
when %r{\A(.*):(\d+)\z}
|
|
301
|
+
# canonicalize the name
|
|
302
|
+
packed = Socket.pack_sockaddr_in($2.to_i, $1)
|
|
303
|
+
Socket.unpack_sockaddr_in(packed).reverse!.join(':')
|
|
304
|
+
else
|
|
305
|
+
address
|
|
265
306
|
end
|
|
266
|
-
address
|
|
267
307
|
end
|
|
268
308
|
|
|
269
309
|
end
|
data/lib/unicorn/const.rb
CHANGED
|
@@ -48,27 +48,17 @@ module Unicorn
|
|
|
48
48
|
# the constant just refers to a string with the same contents. Using these constants
|
|
49
49
|
# gave about a 3% to 10% performance improvement over using the strings directly.
|
|
50
50
|
# Symbols did not really improve things much compared to constants.
|
|
51
|
-
#
|
|
52
|
-
# While Unicorn does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
|
|
53
|
-
# REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
|
|
54
|
-
# too taxing on performance.
|
|
55
51
|
module Const
|
|
56
52
|
DATE="Date".freeze
|
|
57
53
|
|
|
58
54
|
# This is the part of the path after the SCRIPT_NAME.
|
|
59
55
|
PATH_INFO="PATH_INFO".freeze
|
|
60
56
|
|
|
61
|
-
#
|
|
62
|
-
HTTP_BODY="HTTP_BODY".freeze
|
|
63
|
-
|
|
64
|
-
# This is the initial part that your handler is identified as by URIClassifier.
|
|
65
|
-
SCRIPT_NAME="SCRIPT_NAME".freeze
|
|
66
|
-
|
|
67
|
-
# The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
|
|
57
|
+
# The original URI requested by the client.
|
|
68
58
|
REQUEST_URI='REQUEST_URI'.freeze
|
|
69
59
|
REQUEST_PATH='REQUEST_PATH'.freeze
|
|
70
60
|
|
|
71
|
-
UNICORN_VERSION="0.
|
|
61
|
+
UNICORN_VERSION="0.4.1".freeze
|
|
72
62
|
|
|
73
63
|
UNICORN_TMP_BASE="unicorn".freeze
|
|
74
64
|
|
|
@@ -76,14 +66,6 @@ module Unicorn
|
|
|
76
66
|
DEFAULT_PORT = "8080".freeze # default TCP listen port
|
|
77
67
|
DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}".freeze
|
|
78
68
|
|
|
79
|
-
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
|
80
|
-
ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Unicorn #{UNICORN_VERSION}\r\n\r\nNOT FOUND".freeze
|
|
81
|
-
|
|
82
|
-
CONTENT_LENGTH="CONTENT_LENGTH".freeze
|
|
83
|
-
|
|
84
|
-
# A common header for indicating the server is too busy. Not used yet.
|
|
85
|
-
ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
|
86
|
-
|
|
87
69
|
# The basic max request size we'll try to read.
|
|
88
70
|
CHUNK_SIZE=(16 * 1024)
|
|
89
71
|
|
|
@@ -94,23 +76,16 @@ module Unicorn
|
|
|
94
76
|
# Maximum request body size before it is moved out of memory and into a tempfile for reading.
|
|
95
77
|
MAX_BODY=MAX_HEADER
|
|
96
78
|
|
|
79
|
+
# common errors we'll send back
|
|
80
|
+
ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
|
|
81
|
+
ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
|
|
82
|
+
|
|
97
83
|
# A frozen format for this is about 15% faster
|
|
98
|
-
|
|
99
|
-
LAST_MODIFIED = "Last-Modified".freeze
|
|
100
|
-
ETAG = "ETag".freeze
|
|
101
|
-
REQUEST_METHOD="REQUEST_METHOD".freeze
|
|
102
|
-
GET="GET".freeze
|
|
103
|
-
HEAD="HEAD".freeze
|
|
104
|
-
# ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
|
|
105
|
-
ETAG_FORMAT="\"%x-%x-%x\"".freeze
|
|
106
|
-
LINE_END="\r\n".freeze
|
|
84
|
+
CONTENT_LENGTH="CONTENT_LENGTH".freeze
|
|
107
85
|
REMOTE_ADDR="REMOTE_ADDR".freeze
|
|
108
86
|
HTTP_X_FORWARDED_FOR="HTTP_X_FORWARDED_FOR".freeze
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze
|
|
112
|
-
HOST = "HOST".freeze
|
|
113
|
-
CONNECTION = "Connection".freeze
|
|
87
|
+
QUERY_STRING="QUERY_STRING".freeze
|
|
88
|
+
RACK_INPUT="rack.input".freeze
|
|
114
89
|
end
|
|
115
90
|
|
|
116
91
|
end
|
data/lib/unicorn/http_request.rb
CHANGED
|
@@ -13,6 +13,20 @@ module Unicorn
|
|
|
13
13
|
#
|
|
14
14
|
class HttpRequest
|
|
15
15
|
|
|
16
|
+
# default parameters we merge into the request env for Rack handlers
|
|
17
|
+
DEF_PARAMS = {
|
|
18
|
+
"rack.errors" => $stderr,
|
|
19
|
+
"rack.multiprocess" => true,
|
|
20
|
+
"rack.multithread" => false,
|
|
21
|
+
"rack.run_once" => false,
|
|
22
|
+
"rack.url_scheme" => "http",
|
|
23
|
+
"rack.version" => [0, 1],
|
|
24
|
+
"SCRIPT_NAME" => "",
|
|
25
|
+
|
|
26
|
+
# this is not in the Rack spec, but some apps may rely on it
|
|
27
|
+
"SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}"
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
16
30
|
def initialize(logger)
|
|
17
31
|
@logger = logger
|
|
18
32
|
@body = nil
|
|
@@ -29,68 +43,47 @@ module Unicorn
|
|
|
29
43
|
@body = nil
|
|
30
44
|
end
|
|
31
45
|
|
|
32
|
-
#
|
|
33
46
|
# Does the majority of the IO processing. It has been written in
|
|
34
|
-
# Ruby using about
|
|
35
|
-
#
|
|
36
|
-
# currently carefully constructed to make sure that it gets
|
|
37
|
-
# possible performance
|
|
38
|
-
#
|
|
47
|
+
# Ruby using about 8 different IO processing strategies.
|
|
48
|
+
#
|
|
49
|
+
# It is currently carefully constructed to make sure that it gets
|
|
50
|
+
# the best possible performance for the common case: GET requests
|
|
51
|
+
# that are fully complete after a single read(2)
|
|
52
|
+
#
|
|
53
|
+
# Anyone who thinks they can make it faster is more than welcome to
|
|
54
|
+
# take a crack at it.
|
|
39
55
|
#
|
|
40
56
|
# returns an environment hash suitable for Rack if successful
|
|
41
57
|
# This does minimal exception trapping and it is up to the caller
|
|
42
58
|
# to handle any socket errors (e.g. user aborted upload).
|
|
43
59
|
def read(socket)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if @parser.finished?
|
|
56
|
-
# From http://www.ietf.org/rfc/rfc3875:
|
|
57
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
|
58
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
59
|
-
# may not identify the ultimate source of the request. They
|
|
60
|
-
# identify the client for the immediate request to the server;
|
|
61
|
-
# that client may be a proxy, gateway, or other intermediary
|
|
62
|
-
# acting on behalf of the actual source client."
|
|
63
|
-
@params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr
|
|
64
|
-
|
|
65
|
-
handle_body(socket) and return rack_env # success!
|
|
66
|
-
return nil # fail
|
|
67
|
-
else
|
|
68
|
-
# Parser is not done, queue up more data to read and continue
|
|
69
|
-
# parsing
|
|
70
|
-
data << read_socket(socket)
|
|
71
|
-
if data.length >= Const::MAX_HEADER
|
|
72
|
-
raise HttpParserError.new("HEADER is longer than allowed, " \
|
|
73
|
-
"aborting client early.")
|
|
74
|
-
end
|
|
75
|
-
end
|
|
60
|
+
# short circuit the common case with small GET requests first
|
|
61
|
+
@parser.execute(@params, read_socket(socket)) and
|
|
62
|
+
return handle_body(socket)
|
|
63
|
+
|
|
64
|
+
data = @buffer.dup # read_socket will clobber @buffer
|
|
65
|
+
|
|
66
|
+
# Parser is not done, queue up more data to read and continue parsing
|
|
67
|
+
# an Exception thrown from the @parser will throw us out of the loop
|
|
68
|
+
loop do
|
|
69
|
+
data << read_socket(socket)
|
|
70
|
+
@parser.execute(@params, data) and return handle_body(socket)
|
|
76
71
|
end
|
|
77
|
-
nil # XXX bug?
|
|
78
72
|
rescue HttpParserError => e
|
|
79
73
|
@logger.error "HTTP parse error, malformed request " \
|
|
80
74
|
"(#{@params[Const::HTTP_X_FORWARDED_FOR] ||
|
|
81
75
|
socket.unicorn_peeraddr}): #{e.inspect}"
|
|
82
76
|
@logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
|
|
83
77
|
"PARAMS: #{@params.inspect}\n---\n"
|
|
84
|
-
|
|
85
|
-
nil
|
|
78
|
+
raise e
|
|
86
79
|
end
|
|
87
80
|
|
|
88
81
|
private
|
|
89
82
|
|
|
90
83
|
# Handles dealing with the rest of the request
|
|
91
|
-
# returns
|
|
84
|
+
# returns a Rack environment if successful, raises an exception if not
|
|
92
85
|
def handle_body(socket)
|
|
93
|
-
http_body = @params
|
|
86
|
+
http_body = @params.delete(:http_body)
|
|
94
87
|
content_length = @params[Const::CONTENT_LENGTH].to_i
|
|
95
88
|
remain = content_length - http_body.length
|
|
96
89
|
|
|
@@ -108,9 +101,7 @@ module Unicorn
|
|
|
108
101
|
# Some clients (like FF1.0) report 0 for body and then send a body.
|
|
109
102
|
# This will probably truncate them but at least the request goes through
|
|
110
103
|
# usually.
|
|
111
|
-
if remain > 0
|
|
112
|
-
read_body(socket, remain) or return false # fail!
|
|
113
|
-
end
|
|
104
|
+
read_body(socket, remain) if remain > 0
|
|
114
105
|
@body.rewind
|
|
115
106
|
@body.sysseek(0) if @body.respond_to?(:sysseek)
|
|
116
107
|
|
|
@@ -118,29 +109,37 @@ module Unicorn
|
|
|
118
109
|
# another request, we'll truncate it. Again, we don't do pipelining
|
|
119
110
|
# or keepalive
|
|
120
111
|
@body.truncate(content_length)
|
|
121
|
-
|
|
112
|
+
rack_env(socket)
|
|
122
113
|
end
|
|
123
114
|
|
|
124
115
|
# Returns an environment which is rackable:
|
|
125
116
|
# http://rack.rubyforge.org/doc/files/SPEC.html
|
|
126
117
|
# Based on Rack's old Mongrel handler.
|
|
127
|
-
def rack_env
|
|
118
|
+
def rack_env(socket)
|
|
119
|
+
# I'm considering enabling "unicorn.client". It gives
|
|
120
|
+
# applications some rope to do some "interesting" things like
|
|
121
|
+
# replacing a worker with another process that has full control
|
|
122
|
+
# over the HTTP response.
|
|
123
|
+
# @params["unicorn.client"] = socket
|
|
124
|
+
|
|
125
|
+
# From http://www.ietf.org/rfc/rfc3875:
|
|
126
|
+
# "Script authors should be aware that the REMOTE_ADDR and
|
|
127
|
+
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
128
|
+
# may not identify the ultimate source of the request. They
|
|
129
|
+
# identify the client for the immediate request to the server;
|
|
130
|
+
# that client may be a proxy, gateway, or other intermediary
|
|
131
|
+
# acting on behalf of the actual source client."
|
|
132
|
+
@params[Const::REMOTE_ADDR] = socket.unicorn_peeraddr
|
|
133
|
+
|
|
128
134
|
# It might be a dumbass full host request header
|
|
129
|
-
@params[Const::
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@params
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
"rack.multithread" => false,
|
|
138
|
-
"rack.multiprocess" => true,
|
|
139
|
-
"rack.run_once" => false,
|
|
140
|
-
"rack.url_scheme" => "http",
|
|
141
|
-
Const::PATH_INFO => @params[Const::REQUEST_PATH],
|
|
142
|
-
Const::SCRIPT_NAME => "",
|
|
143
|
-
})
|
|
135
|
+
@params[Const::PATH_INFO] = (
|
|
136
|
+
@params[Const::REQUEST_PATH] ||=
|
|
137
|
+
URI.parse(@params[Const::REQUEST_URI]).path) or
|
|
138
|
+
raise "No REQUEST_PATH"
|
|
139
|
+
|
|
140
|
+
@params[Const::QUERY_STRING] ||= ''
|
|
141
|
+
@params[Const::RACK_INPUT] = @body
|
|
142
|
+
@params.update(DEF_PARAMS)
|
|
144
143
|
end
|
|
145
144
|
|
|
146
145
|
# Does the heavy lifting of properly reading the larger body requests in
|
|
@@ -152,16 +151,14 @@ module Unicorn
|
|
|
152
151
|
# writes always write the requested amount on a POSIX filesystem
|
|
153
152
|
remain -= @body.syswrite(read_socket(socket))
|
|
154
153
|
end
|
|
155
|
-
true # success!
|
|
156
154
|
rescue Object => e
|
|
157
155
|
@logger.error "Error reading HTTP body: #{e.inspect}"
|
|
158
|
-
socket.closed? or socket.close rescue nil
|
|
159
156
|
|
|
160
157
|
# Any errors means we should delete the file, including if the file
|
|
161
158
|
# is dumped. Truncate it ASAP to help avoid page flushes to disk.
|
|
162
159
|
@body.truncate(0) rescue nil
|
|
163
160
|
reset
|
|
164
|
-
|
|
161
|
+
raise e
|
|
165
162
|
end
|
|
166
163
|
|
|
167
164
|
# read(2) on "slow" devices like sockets can be interrupted by signals
|
|
@@ -35,7 +35,11 @@ module Unicorn
|
|
|
35
35
|
# the time anyways so just hope our app knows what it's doing
|
|
36
36
|
headers.each do |key, value|
|
|
37
37
|
next if SKIP.include?(key.downcase)
|
|
38
|
-
value
|
|
38
|
+
if value =~ /\n/
|
|
39
|
+
value.split(/\n/).each { |v| out << "#{key}: #{v}" }
|
|
40
|
+
else
|
|
41
|
+
out << "#{key}: #{value}"
|
|
42
|
+
end
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
# Rack should enforce Content-Length or chunked transfer encoding,
|
|
@@ -45,6 +49,7 @@ module Unicorn
|
|
|
45
49
|
"Connection: close\r\n" \
|
|
46
50
|
"#{out.join("\r\n")}\r\n\r\n")
|
|
47
51
|
body.each { |chunk| socket_write(socket, chunk) }
|
|
52
|
+
socket.close # uncorks the socket immediately
|
|
48
53
|
ensure
|
|
49
54
|
body.respond_to?(:close) and body.close rescue nil
|
|
50
55
|
end
|
data/lib/unicorn/socket.rb
CHANGED
|
@@ -62,10 +62,17 @@ module Unicorn
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def log_buffer_sizes(sock, pfx = '')
|
|
66
|
+
respond_to?(:logger) or return
|
|
67
|
+
rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
|
|
68
|
+
sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
|
|
69
|
+
logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
|
|
70
|
+
end
|
|
71
|
+
|
|
65
72
|
# creates a new server, socket. address may be a HOST:PORT or
|
|
66
73
|
# an absolute path to a UNIX socket. address can even be a Socket
|
|
67
74
|
# object in which case it is immediately returned
|
|
68
|
-
def bind_listen(address = '0.0.0.0:8080',
|
|
75
|
+
def bind_listen(address = '0.0.0.0:8080', opt = { :backlog => 1024 })
|
|
69
76
|
return address unless String === address
|
|
70
77
|
|
|
71
78
|
domain, bind_addr = if address[0..0] == "/"
|
|
@@ -95,7 +102,13 @@ module Unicorn
|
|
|
95
102
|
sock.close rescue nil
|
|
96
103
|
return nil
|
|
97
104
|
end
|
|
98
|
-
|
|
105
|
+
if opt[:rcvbuf] || opt[:sndbuf]
|
|
106
|
+
log_buffer_sizes(sock, "before: ")
|
|
107
|
+
sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
|
|
108
|
+
sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
|
|
109
|
+
log_buffer_sizes(sock, " after: ")
|
|
110
|
+
end
|
|
111
|
+
sock.listen(opt[:backlog] || 1024)
|
|
99
112
|
set_server_sockopt(sock) if domain == AF_INET
|
|
100
113
|
sock
|
|
101
114
|
end
|