unicorn 0.2.3 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|