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.
Files changed (95) hide show
  1. data/.document +1 -1
  2. data/.gitignore +1 -0
  3. data/CHANGELOG +1 -0
  4. data/DESIGN +4 -0
  5. data/GNUmakefile +30 -6
  6. data/Manifest +62 -3
  7. data/README +52 -42
  8. data/SIGNALS +17 -17
  9. data/TODO +27 -5
  10. data/bin/unicorn +15 -13
  11. data/bin/unicorn_rails +59 -22
  12. data/ext/unicorn/http11/http11.c +25 -104
  13. data/ext/unicorn/http11/http11_parser.c +24 -23
  14. data/ext/unicorn/http11/http11_parser.h +1 -3
  15. data/ext/unicorn/http11/http11_parser.rl +2 -1
  16. data/lib/unicorn.rb +58 -44
  17. data/lib/unicorn/app/old_rails.rb +23 -0
  18. data/lib/unicorn/app/old_rails/static.rb +58 -0
  19. data/lib/unicorn/cgi_wrapper.rb +151 -0
  20. data/lib/unicorn/configurator.rb +71 -31
  21. data/lib/unicorn/const.rb +9 -34
  22. data/lib/unicorn/http_request.rb +63 -66
  23. data/lib/unicorn/http_response.rb +6 -1
  24. data/lib/unicorn/socket.rb +15 -2
  25. data/test/benchmark/README +55 -0
  26. data/test/benchmark/big_request.rb +35 -0
  27. data/test/benchmark/dd.ru +18 -0
  28. data/test/benchmark/request.rb +47 -0
  29. data/test/benchmark/response.rb +29 -0
  30. data/test/exec/test_exec.rb +41 -157
  31. data/test/rails/app-1.2.3/.gitignore +2 -0
  32. data/test/rails/app-1.2.3/app/controllers/application.rb +4 -0
  33. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +34 -0
  34. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +2 -0
  35. data/test/rails/app-1.2.3/config/boot.rb +9 -0
  36. data/test/rails/app-1.2.3/config/database.yml +12 -0
  37. data/test/rails/app-1.2.3/config/environment.rb +10 -0
  38. data/test/rails/app-1.2.3/config/environments/development.rb +7 -0
  39. data/test/rails/app-1.2.3/config/environments/production.rb +3 -0
  40. data/test/rails/app-1.2.3/config/routes.rb +4 -0
  41. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  42. data/test/rails/app-1.2.3/public/404.html +1 -0
  43. data/test/rails/app-1.2.3/public/500.html +1 -0
  44. data/test/rails/app-2.0.2/.gitignore +2 -0
  45. data/test/rails/app-2.0.2/app/controllers/application.rb +2 -0
  46. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +34 -0
  47. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +2 -0
  48. data/test/rails/app-2.0.2/config/boot.rb +9 -0
  49. data/test/rails/app-2.0.2/config/database.yml +12 -0
  50. data/test/rails/app-2.0.2/config/environment.rb +14 -0
  51. data/test/rails/app-2.0.2/config/environments/development.rb +6 -0
  52. data/test/rails/app-2.0.2/config/environments/production.rb +3 -0
  53. data/test/rails/app-2.0.2/config/routes.rb +4 -0
  54. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  55. data/test/rails/app-2.0.2/public/404.html +1 -0
  56. data/test/rails/app-2.0.2/public/500.html +1 -0
  57. data/test/rails/app-2.2.2/.gitignore +2 -0
  58. data/test/rails/app-2.2.2/app/controllers/application.rb +2 -0
  59. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +34 -0
  60. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +2 -0
  61. data/test/rails/app-2.2.2/config/boot.rb +109 -0
  62. data/test/rails/app-2.2.2/config/database.yml +12 -0
  63. data/test/rails/app-2.2.2/config/environment.rb +14 -0
  64. data/test/rails/app-2.2.2/config/environments/development.rb +5 -0
  65. data/test/rails/app-2.2.2/config/environments/production.rb +3 -0
  66. data/test/rails/app-2.2.2/config/routes.rb +4 -0
  67. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  68. data/test/rails/app-2.2.2/public/404.html +1 -0
  69. data/test/rails/app-2.2.2/public/500.html +1 -0
  70. data/test/rails/app-2.3.2.1/.gitignore +2 -0
  71. data/test/rails/app-2.3.2.1/app/controllers/application_controller.rb +3 -0
  72. data/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb +34 -0
  73. data/test/rails/app-2.3.2.1/app/helpers/application_helper.rb +2 -0
  74. data/test/rails/app-2.3.2.1/config/boot.rb +107 -0
  75. data/test/rails/app-2.3.2.1/config/database.yml +12 -0
  76. data/test/rails/app-2.3.2.1/config/environment.rb +14 -0
  77. data/test/rails/app-2.3.2.1/config/environments/development.rb +5 -0
  78. data/test/rails/app-2.3.2.1/config/environments/production.rb +4 -0
  79. data/test/rails/app-2.3.2.1/config/routes.rb +4 -0
  80. data/test/rails/app-2.3.2.1/db/.gitignore +0 -0
  81. data/test/rails/app-2.3.2.1/public/404.html +1 -0
  82. data/test/rails/app-2.3.2.1/public/500.html +1 -0
  83. data/test/rails/test_rails.rb +243 -0
  84. data/test/test_helper.rb +149 -2
  85. data/test/unit/test_configurator.rb +46 -0
  86. data/test/unit/test_http_parser.rb +77 -36
  87. data/test/unit/test_request.rb +2 -0
  88. data/test/unit/test_response.rb +20 -4
  89. data/test/unit/test_server.rb +30 -1
  90. data/test/unit/test_socket_helper.rb +159 -0
  91. data/unicorn.gemspec +5 -5
  92. metadata +68 -5
  93. data/test/benchmark/previous.rb +0 -11
  94. data/test/benchmark/simple.rb +0 -11
  95. data/test/benchmark/utils.rb +0 -82
@@ -8,22 +8,21 @@ module Unicorn
8
8
  #
9
9
  # Example (when used with the unicorn config file):
10
10
  # worker_processes 4
11
- # listeners %w(0.0.0.0:9292 /tmp/my_app.sock)
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 => [ Const::DEFAULT_LISTEN ],
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
- def listeners(addresses)
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
- def listen(address)
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] << expand_addr(address)
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
- if address[0..0] == '~'
262
- return File.expand_path(address)
263
- elsif address =~ %r{\A\*?:(\d+)\z}
264
- return "0.0.0.0:#$1"
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
@@ -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
- # Request body
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.2.3".freeze
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
- CONTENT_TYPE = "Content-Type".freeze
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
- HTTP_IF_MODIFIED_SINCE="HTTP_IF_MODIFIED_SINCE".freeze
110
- HTTP_IF_NONE_MATCH="HTTP_IF_NONE_MATCH".freeze
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
@@ -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 7 different IO processing strategies and no
35
- # matter how it's done the performance just does not improve. It is
36
- # currently carefully constructed to make sure that it gets the best
37
- # possible performance, but anyone who thinks they can make it
38
- # faster is more than welcome to take a crack at it.
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
- data = String.new(read_socket(socket))
45
- nparsed = 0
46
-
47
- # Assumption: nparsed will always be less since data will get
48
- # filled with more after each parsing. If it doesn't get more
49
- # then there was a problem with the read operation on the client
50
- # socket. Effect is to stop processing when the socket can't
51
- # fill the buffer for further parsing.
52
- while nparsed < data.length
53
- nparsed = @parser.execute(@params, data, nparsed)
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
- socket.closed? or socket.close rescue nil
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 true if successful, false if not
84
+ # returns a Rack environment if successful, raises an exception if not
92
85
  def handle_body(socket)
93
- http_body = @params[Const::HTTP_BODY]
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
- true
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::REQUEST_PATH] ||=
130
- URI.parse(@params[Const::REQUEST_URI]).path
131
- raise "No REQUEST PATH" unless @params[Const::REQUEST_PATH]
132
-
133
- @params["QUERY_STRING"] ||= ''
134
- @params.update({ "rack.version" => [0,1],
135
- "rack.input" => @body,
136
- "rack.errors" => $stderr,
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
- false
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.split(/\n/).each { |v| out << "#{key}: #{v}" }
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
@@ -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', backlog = 1024)
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
- sock.listen(backlog)
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