puma 2.7.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (79) hide show
  1. checksums.yaml +5 -13
  2. data/DEPLOYMENT.md +91 -0
  3. data/Gemfile +3 -2
  4. data/History.txt +624 -1
  5. data/Manifest.txt +15 -3
  6. data/README.md +129 -14
  7. data/Rakefile +3 -3
  8. data/bin/puma-wild +31 -0
  9. data/bin/pumactl +1 -1
  10. data/docs/nginx.md +1 -1
  11. data/docs/signals.md +43 -0
  12. data/ext/puma_http11/extconf.rb +7 -2
  13. data/ext/puma_http11/http11_parser.java.rl +5 -5
  14. data/ext/puma_http11/io_buffer.c +1 -1
  15. data/ext/puma_http11/mini_ssl.c +233 -18
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +12 -3
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -39
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +245 -195
  19. data/ext/puma_http11/puma_http11.c +12 -4
  20. data/lib/puma.rb +1 -0
  21. data/lib/puma/app/status.rb +7 -0
  22. data/lib/puma/binder.rb +108 -39
  23. data/lib/puma/capistrano.rb +23 -6
  24. data/lib/puma/cli.rb +141 -446
  25. data/lib/puma/client.rb +48 -1
  26. data/lib/puma/cluster.rb +207 -58
  27. data/lib/puma/commonlogger.rb +107 -0
  28. data/lib/puma/configuration.rb +262 -235
  29. data/lib/puma/const.rb +97 -14
  30. data/lib/puma/control_cli.rb +85 -77
  31. data/lib/puma/convenient.rb +23 -0
  32. data/lib/puma/daemon_ext.rb +11 -4
  33. data/lib/puma/detect.rb +8 -1
  34. data/lib/puma/dsl.rb +456 -0
  35. data/lib/puma/events.rb +35 -18
  36. data/lib/puma/jruby_restart.rb +1 -1
  37. data/lib/puma/launcher.rb +399 -0
  38. data/lib/puma/minissl.rb +49 -20
  39. data/lib/puma/null_io.rb +15 -0
  40. data/lib/puma/plugin.rb +104 -0
  41. data/lib/puma/plugin/tmp_restart.rb +35 -0
  42. data/lib/puma/rack/backports/uri/common_18.rb +56 -0
  43. data/lib/puma/rack/backports/uri/common_192.rb +52 -0
  44. data/lib/puma/rack/backports/uri/common_193.rb +29 -0
  45. data/lib/puma/rack/builder.rb +295 -0
  46. data/lib/puma/rack/urlmap.rb +90 -0
  47. data/lib/puma/reactor.rb +14 -1
  48. data/lib/puma/runner.rb +35 -17
  49. data/lib/puma/server.rb +161 -58
  50. data/lib/puma/single.rb +15 -10
  51. data/lib/puma/state_file.rb +29 -0
  52. data/lib/puma/thread_pool.rb +88 -13
  53. data/lib/puma/util.rb +123 -0
  54. data/lib/rack/handler/puma.rb +35 -29
  55. data/puma.gemspec +2 -4
  56. data/tools/jungle/init.d/README.md +2 -2
  57. data/tools/jungle/init.d/puma +69 -7
  58. data/tools/jungle/upstart/puma.conf +8 -2
  59. metadata +51 -71
  60. data/COPYING +0 -55
  61. data/TODO +0 -5
  62. data/lib/puma/rack_patch.rb +0 -45
  63. data/test/test_app_status.rb +0 -92
  64. data/test/test_cli.rb +0 -173
  65. data/test/test_config.rb +0 -16
  66. data/test/test_http10.rb +0 -27
  67. data/test/test_http11.rb +0 -145
  68. data/test/test_integration.rb +0 -165
  69. data/test/test_iobuffer.rb +0 -38
  70. data/test/test_minissl.rb +0 -25
  71. data/test/test_null_io.rb +0 -31
  72. data/test/test_persistent.rb +0 -238
  73. data/test/test_puma_server.rb +0 -292
  74. data/test/test_rack_handler.rb +0 -10
  75. data/test/test_rack_server.rb +0 -141
  76. data/test/test_tcp_rack.rb +0 -42
  77. data/test/test_thread_pool.rb +0 -156
  78. data/test/test_unix_socket.rb +0 -39
  79. data/test/test_ws.rb +0 -89
@@ -0,0 +1,90 @@
1
+ module Puma::Rack
2
+ # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
+ # dispatches accordingly. Support for HTTP/1.1 host names exists if
4
+ # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
5
+ #
6
+ # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
7
+ # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
8
+ # PATH_INFO. This should be taken care of when you need to
9
+ # reconstruct the URL in order to create links.
10
+ #
11
+ # URLMap dispatches in such a way that the longest paths are tried
12
+ # first, since they are most specific.
13
+
14
+ class URLMap
15
+ NEGATIVE_INFINITY = -1.0 / 0.0
16
+ INFINITY = 1.0 / 0.0
17
+
18
+ def initialize(map = {})
19
+ remap(map)
20
+ end
21
+
22
+ def remap(map)
23
+ @mapping = map.map { |location, app|
24
+ if location =~ %r{\Ahttps?://(.*?)(/.*)}
25
+ host, location = $1, $2
26
+ else
27
+ host = nil
28
+ end
29
+
30
+ unless location[0] == ?/
31
+ raise ArgumentError, "paths need to start with /"
32
+ end
33
+
34
+ location = location.chomp('/')
35
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
36
+
37
+ [host, location, match, app]
38
+ }.sort_by do |(host, location, _, _)|
39
+ [host ? -host.size : INFINITY, -location.size]
40
+ end
41
+ end
42
+
43
+ def call(env)
44
+ path = env['PATH_INFO']
45
+ script_name = env['SCRIPT_NAME']
46
+ hHost = env['HTTP_HOST']
47
+ sName = env['SERVER_NAME']
48
+ sPort = env['SERVER_PORT']
49
+
50
+ @mapping.each do |host, location, match, app|
51
+ unless casecmp?(hHost, host) \
52
+ || casecmp?(sName, host) \
53
+ || (!host && (casecmp?(hHost, sName) ||
54
+ casecmp?(hHost, sName+':'+sPort)))
55
+ next
56
+ end
57
+
58
+ next unless m = match.match(path.to_s)
59
+
60
+ rest = m[1]
61
+ next unless !rest || rest.empty? || rest[0] == ?/
62
+
63
+ env['SCRIPT_NAME'] = (script_name + location)
64
+ env['PATH_INFO'] = rest
65
+
66
+ return app.call(env)
67
+ end
68
+
69
+ [404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
70
+
71
+ ensure
72
+ env['PATH_INFO'] = path
73
+ env['SCRIPT_NAME'] = script_name
74
+ end
75
+
76
+ private
77
+ def casecmp?(v1, v2)
78
+ # if both nil, or they're the same string
79
+ return true if v1 == v2
80
+
81
+ # if either are nil... (but they're not the same)
82
+ return false if v1.nil?
83
+ return false if v2.nil?
84
+
85
+ # otherwise check they're not case-insensitive the same
86
+ v1.casecmp(v2).zero?
87
+ end
88
+ end
89
+ end
90
+
data/lib/puma/reactor.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'puma/util'
2
+ require 'puma/minissl'
2
3
 
3
4
  module Puma
4
5
  class Reactor
@@ -74,6 +75,17 @@ module Puma
74
75
  sockets.delete c
75
76
  end
76
77
 
78
+ # SSL handshake failure
79
+ rescue MiniSSL::SSLError => e
80
+ ssl_socket = c.io
81
+ addr = ssl_socket.peeraddr.last
82
+ cert = ssl_socket.peercert
83
+
84
+ c.close
85
+ sockets.delete c
86
+
87
+ @events.ssl_error @server, addr, cert, e
88
+
77
89
  # The client doesn't know HTTP well
78
90
  rescue HttpParserError => e
79
91
  c.write_400
@@ -98,8 +110,9 @@ module Puma
98
110
 
99
111
  while @timeouts.first.timeout_at < now
100
112
  c = @timeouts.shift
101
- sockets.delete c
113
+ c.write_408 if c.in_data_phase
102
114
  c.close
115
+ sockets.delete c
103
116
 
104
117
  break if @timeouts.empty?
105
118
  end
data/lib/puma/runner.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  module Puma
2
2
  class Runner
3
- def initialize(cli)
4
- @cli = cli
3
+ def initialize(cli, events)
4
+ @launcher = cli
5
+ @events = events
5
6
  @options = cli.options
6
7
  @app = nil
7
8
  @control = nil
@@ -16,15 +17,19 @@ module Puma
16
17
  end
17
18
 
18
19
  def log(str)
19
- @cli.log str
20
+ @events.log str
21
+ end
22
+
23
+ def before_restart
24
+ @control.stop(true) if @control
20
25
  end
21
26
 
22
27
  def error(str)
23
- @cli.error str
28
+ @events.error str
24
29
  end
25
30
 
26
- def before_restart
27
- @control.stop(true) if @control
31
+ def debug(str)
32
+ @events.log "- #{str}" if @options[:debug]
28
33
  end
29
34
 
30
35
  def start_control
@@ -35,13 +40,13 @@ module Puma
35
40
 
36
41
  uri = URI.parse str
37
42
 
38
- app = Puma::App::Status.new @cli
43
+ app = Puma::App::Status.new @launcher
39
44
 
40
45
  if token = @options[:control_auth_token]
41
46
  app.auth_token = token unless token.empty? or token == :none
42
47
  end
43
48
 
44
- control = Puma::Server.new app, @cli.events
49
+ control = Puma::Server.new app, @launcher.events
45
50
  control.min_threads = 0
46
51
  control.max_threads = 1
47
52
 
@@ -52,8 +57,9 @@ module Puma
52
57
  when "unix"
53
58
  log "* Starting control server on #{str}"
54
59
  path = "#{uri.host}#{uri.path}"
60
+ mask = @options[:control_url_umask]
55
61
 
56
- control.add_unix_listener path
62
+ control.add_unix_listener path, mask
57
63
  else
58
64
  error "Invalid control URI: #{str}"
59
65
  end
@@ -62,12 +68,24 @@ module Puma
62
68
  @control = control
63
69
  end
64
70
 
71
+ def ruby_engine
72
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
73
+ "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
74
+ else
75
+ if defined?(RUBY_ENGINE_VERSION)
76
+ "#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
77
+ else
78
+ "#{RUBY_ENGINE} #{RUBY_VERSION}"
79
+ end
80
+ end
81
+ end
82
+
65
83
  def output_header(mode)
66
84
  min_t = @options[:min_threads]
67
85
  max_t = @options[:max_threads]
68
86
 
69
87
  log "Puma starting in #{mode} mode..."
70
- log "* Version #{Puma::Const::PUMA_VERSION}, codename: #{Puma::Const::CODE_NAME}"
88
+ log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
71
89
  log "* Min threads: #{min_t}, max threads: #{max_t}"
72
90
  log "* Environment: #{ENV['RACK_ENV']}"
73
91
 
@@ -95,34 +113,34 @@ module Puma
95
113
  end
96
114
 
97
115
  def load_and_bind
98
- unless @cli.config.app_configured?
116
+ unless @launcher.config.app_configured?
99
117
  error "No application configured, nothing to run"
100
118
  exit 1
101
119
  end
102
120
 
103
121
  # Load the app before we daemonize.
104
122
  begin
105
- @app = @cli.config.app
123
+ @app = @launcher.config.app
106
124
  rescue Exception => e
107
- log "! Unable to load application"
125
+ log "! Unable to load application: #{e.class}: #{e.message}"
108
126
  raise e
109
127
  end
110
128
 
111
- @cli.binder.parse @options[:binds], self
129
+ @launcher.binder.parse @options[:binds], self
112
130
  end
113
131
 
114
132
  def app
115
- @app ||= @cli.config.app
133
+ @app ||= @launcher.config.app
116
134
  end
117
135
 
118
136
  def start_server
119
137
  min_t = @options[:min_threads]
120
138
  max_t = @options[:max_threads]
121
139
 
122
- server = Puma::Server.new app, @cli.events, @options
140
+ server = Puma::Server.new app, @launcher.events, @options
123
141
  server.min_threads = min_t
124
142
  server.max_threads = max_t
125
- server.inherit_binder @cli.binder
143
+ server.inherit_binder @launcher.binder
126
144
 
127
145
  if @options[:mode] == :tcp
128
146
  server.tcp_mode!
data/lib/puma/server.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'rack'
2
1
  require 'stringio'
3
2
 
4
3
  require 'puma/thread_pool'
@@ -13,11 +12,9 @@ require 'puma/delegation'
13
12
  require 'puma/accept_nonblock'
14
13
  require 'puma/util'
15
14
 
16
- require 'puma/rack_patch'
17
-
18
15
  require 'puma/puma_http11'
19
16
 
20
- unless Puma.const_defined?("IOBuffer", false)
17
+ unless Puma.const_defined? "IOBuffer"
21
18
  require 'puma/io_buffer'
22
19
  end
23
20
 
@@ -39,6 +36,8 @@ module Puma
39
36
  attr_accessor :max_threads
40
37
  attr_accessor :persistent_timeout
41
38
  attr_accessor :auto_trim_time
39
+ attr_accessor :reaping_time
40
+ attr_accessor :first_data_timeout
42
41
 
43
42
  # Create a server for the rack app +app+.
44
43
  #
@@ -46,7 +45,7 @@ module Puma
46
45
  # to be handled. See Puma::Events for the list of current methods to implement.
47
46
  #
48
47
  # Server#run returns a thread that you can join on to wait for the server
49
- # to do it's work.
48
+ # to do its work.
50
49
  #
51
50
  def initialize(app, events=Events.stdio, options={})
52
51
  @app = app
@@ -59,6 +58,7 @@ module Puma
59
58
  @min_threads = 0
60
59
  @max_threads = 16
61
60
  @auto_trim_time = 1
61
+ @reaping_time = 1
62
62
 
63
63
  @thread = nil
64
64
  @thread_pool = nil
@@ -73,6 +73,7 @@ module Puma
73
73
  @leak_stack_on_error = true
74
74
 
75
75
  @options = options
76
+ @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
76
77
 
77
78
  ENV['RACK_ENV'] ||= "development"
78
79
 
@@ -84,6 +85,7 @@ module Puma
84
85
  forward :add_tcp_listener, :@binder
85
86
  forward :add_ssl_listener, :@binder
86
87
  forward :add_unix_listener, :@binder
88
+ forward :connected_port, :@binder
87
89
 
88
90
  def inherit_binder(bind)
89
91
  @binder = bind
@@ -102,13 +104,16 @@ module Puma
102
104
  # 3 == TCP_CORK
103
105
  # 1/0 == turn on/off
104
106
  def cork_socket(socket)
105
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
107
+ begin
108
+ socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
109
+ rescue IOError, SystemCallError
110
+ end
106
111
  end
107
112
 
108
113
  def uncork_socket(socket)
109
114
  begin
110
115
  socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
111
- rescue IOError
116
+ rescue IOError, SystemCallError
112
117
  end
113
118
  end
114
119
  else
@@ -181,16 +186,17 @@ module Puma
181
186
  else
182
187
  begin
183
188
  if io = sock.accept_nonblock
184
- c = Client.new io, nil
185
- pool << c
189
+ client = Client.new io, nil
190
+ pool << client
186
191
  end
187
192
  rescue SystemCallError
193
+ # nothing
194
+ rescue Errno::ECONNABORTED
195
+ # client closed the socket even before accept
196
+ io.close rescue nil
188
197
  end
189
198
  end
190
199
  end
191
- rescue Errno::ECONNABORTED
192
- # client closed the socket even before accept
193
- client.close rescue nil
194
200
  rescue Object => e
195
201
  @events.unknown_error self, e, "Listen loop"
196
202
  end
@@ -231,13 +237,28 @@ module Puma
231
237
  return run_lopez_mode(background)
232
238
  end
233
239
 
240
+ queue_requests = @queue_requests
241
+
234
242
  @thread_pool = ThreadPool.new(@min_threads,
235
243
  @max_threads,
236
244
  IOBuffer) do |client, buffer|
237
245
  process_now = false
238
246
 
239
247
  begin
240
- process_now = client.eagerly_finish
248
+ if queue_requests
249
+ process_now = client.eagerly_finish
250
+ else
251
+ client.finish
252
+ process_now = true
253
+ end
254
+ rescue MiniSSL::SSLError => e
255
+ ssl_socket = client.io
256
+ addr = ssl_socket.peeraddr.last
257
+ cert = ssl_socket.peercert
258
+
259
+ client.close
260
+
261
+ @events.ssl_error self, addr, cert, e
241
262
  rescue HttpParserError => e
242
263
  client.write_400
243
264
  client.close
@@ -255,9 +276,16 @@ module Puma
255
276
  end
256
277
  end
257
278
 
258
- @reactor = Reactor.new self, @thread_pool
279
+ @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
259
280
 
260
- @reactor.run_in_thread
281
+ if queue_requests
282
+ @reactor = Reactor.new self, @thread_pool
283
+ @reactor.run_in_thread
284
+ end
285
+
286
+ if @reaping_time
287
+ @thread_pool.auto_reap!(@reaping_time)
288
+ end
261
289
 
262
290
  if @auto_trim_time
263
291
  @thread_pool.auto_trim!(@auto_trim_time)
@@ -278,6 +306,17 @@ module Puma
278
306
  check = @check
279
307
  sockets = [check] + @binder.ios
280
308
  pool = @thread_pool
309
+ queue_requests = @queue_requests
310
+
311
+ remote_addr_value = nil
312
+ remote_addr_header = nil
313
+
314
+ case @options[:remote_address]
315
+ when :value
316
+ remote_addr_value = @options[:remote_address_value]
317
+ when :header
318
+ remote_addr_header = @options[:remote_address_header]
319
+ end
281
320
 
282
321
  while @status == :run
283
322
  begin
@@ -288,16 +327,24 @@ module Puma
288
327
  else
289
328
  begin
290
329
  if io = sock.accept_nonblock
291
- c = Client.new io, @binder.env(sock)
292
- pool << c
330
+ client = Client.new io, @binder.env(sock)
331
+ if remote_addr_value
332
+ client.peerip = remote_addr_value
333
+ elsif remote_addr_header
334
+ client.remote_addr_header = remote_addr_header
335
+ end
336
+
337
+ pool << client
338
+ pool.wait_until_not_full unless queue_requests
293
339
  end
294
340
  rescue SystemCallError
341
+ # nothing
342
+ rescue Errno::ECONNABORTED
343
+ # client closed the socket even before accept
344
+ io.close rescue nil
295
345
  end
296
346
  end
297
347
  end
298
- rescue Errno::ECONNABORTED
299
- # client closed the socket even before accept
300
- client.close rescue nil
301
348
  rescue Object => e
302
349
  @events.unknown_error self, e, "Listen loop"
303
350
  end
@@ -306,9 +353,10 @@ module Puma
306
353
  @events.fire :state, @status
307
354
 
308
355
  graceful_shutdown if @status == :stop || @status == :restart
309
- @reactor.clear! if @status == :restart
310
-
311
- @reactor.shutdown
356
+ if queue_requests
357
+ @reactor.clear! if @status == :restart
358
+ @reactor.shutdown
359
+ end
312
360
  rescue Exception => e
313
361
  STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
314
362
  STDERR.puts e.backtrace
@@ -326,7 +374,7 @@ module Puma
326
374
 
327
375
  # :nodoc:
328
376
  def handle_check
329
- cmd = @check.read(1)
377
+ cmd = @check.read(1)
330
378
 
331
379
  case cmd
332
380
  when STOP_COMMAND
@@ -351,6 +399,7 @@ module Puma
351
399
  #
352
400
  def process_client(client, buffer)
353
401
  begin
402
+ clean_thread_locals = @options[:clean_thread_locals]
354
403
  close_socket = true
355
404
 
356
405
  while true
@@ -361,8 +410,11 @@ module Puma
361
410
  close_socket = false
362
411
  return
363
412
  when true
413
+ return unless @queue_requests
364
414
  buffer.reset
365
415
 
416
+ ThreadPool.clean_thread_locals if clean_thread_locals
417
+
366
418
  unless client.reset(@status == :run)
367
419
  close_socket = false
368
420
  client.set_timeout @persistent_timeout
@@ -376,6 +428,16 @@ module Puma
376
428
  rescue ConnectionError
377
429
  # Swallow them. The ensure tries to close +client+ down
378
430
 
431
+ # SSL handshake error
432
+ rescue MiniSSL::SSLError => e
433
+ ssl_socket = client.io
434
+ addr = ssl_socket.peeraddr.last
435
+ cert = ssl_socket.peercert
436
+
437
+ close_socket = true
438
+
439
+ @events.ssl_error self, addr, cert, e
440
+
379
441
  # The client doesn't know HTTP well
380
442
  rescue HttpParserError => e
381
443
  client.write_400
@@ -437,15 +499,24 @@ module Puma
437
499
  # intermediary acting on behalf of the actual source client."
438
500
  #
439
501
 
440
- addr = client.peeraddr.last
502
+ unless env.key?(REMOTE_ADDR)
503
+ begin
504
+ addr = client.peerip
505
+ rescue Errno::ENOTCONN
506
+ # Client disconnects can result in an inability to get the
507
+ # peeraddr from the socket; default to localhost.
508
+ addr = LOCALHOST_IP
509
+ end
441
510
 
442
- # Set unix socket addrs to localhost
443
- addr = "127.0.0.1" if addr.empty?
511
+ # Set unix socket addrs to localhost
512
+ addr = LOCALHOST_IP if addr.empty?
444
513
 
445
- env[REMOTE_ADDR] = addr
514
+ env[REMOTE_ADDR] = addr
515
+ end
446
516
  end
447
517
 
448
518
  def default_server_port(env)
519
+ return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
449
520
  env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
450
521
  end
451
522
 
@@ -462,10 +533,14 @@ module Puma
462
533
  env = req.env
463
534
  client = req.io
464
535
 
465
- normalize_env env, client
536
+ normalize_env env, req
466
537
 
467
538
  env[PUMA_SOCKET] = client
468
539
 
540
+ if env[HTTPS_KEY] && client.peercert
541
+ env[PUMA_PEERCERT] = client.peercert
542
+ end
543
+
469
544
  env[HIJACK_P] = true
470
545
  env[HIJACK] = req
471
546
 
@@ -499,7 +574,7 @@ module Puma
499
574
  rescue StandardError => e
500
575
  @events.unknown_error self, e, "Rack app"
501
576
 
502
- status, headers, res_body = lowlevel_error(e)
577
+ status, headers, res_body = lowlevel_error(e, env)
503
578
  end
504
579
 
505
580
  content_length = nil
@@ -514,9 +589,9 @@ module Puma
514
589
  line_ending = LINE_END
515
590
  colon = COLON
516
591
 
517
- if env[HTTP_VERSION] == HTTP_11
592
+ http_11 = if env[HTTP_VERSION] == HTTP_11
518
593
  allow_chunked = true
519
- keep_alive = env[HTTP_CONNECTION] != CLOSE
594
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
520
595
  include_keepalive_header = false
521
596
 
522
597
  # An optimization. The most common response is 200, so we can
@@ -531,9 +606,10 @@ module Puma
531
606
 
532
607
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
533
608
  end
609
+ true
534
610
  else
535
611
  allow_chunked = false
536
- keep_alive = env[HTTP_CONNECTION] == KEEP_ALIVE
612
+ keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
537
613
  include_keepalive_header = keep_alive
538
614
 
539
615
  # Same optimization as above for HTTP/1.1
@@ -546,12 +622,13 @@ module Puma
546
622
 
547
623
  no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
548
624
  end
625
+ false
549
626
  end
550
627
 
551
628
  response_hijack = nil
552
629
 
553
630
  headers.each do |k, vs|
554
- case k
631
+ case k.downcase
555
632
  when CONTENT_LENGTH2
556
633
  content_length = vs
557
634
  next
@@ -572,6 +649,12 @@ module Puma
572
649
  end
573
650
  end
574
651
 
652
+ if include_keepalive_header
653
+ lines << CONNECTION_KEEP_ALIVE
654
+ elsif http_11 && !keep_alive
655
+ lines << CONNECTION_CLOSE
656
+ end
657
+
575
658
  if no_body
576
659
  if content_length and status != 204
577
660
  lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
@@ -582,20 +665,12 @@ module Puma
582
665
  return keep_alive
583
666
  end
584
667
 
585
- if include_keepalive_header
586
- lines << CONNECTION_KEEP_ALIVE
587
- elsif !keep_alive
588
- lines << CONNECTION_CLOSE
589
- end
590
-
591
- unless response_hijack
592
- if content_length
593
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
594
- chunked = false
595
- elsif allow_chunked
596
- lines << TRANSFER_ENCODING_CHUNKED
597
- chunked = true
598
- end
668
+ if content_length
669
+ lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
670
+ chunked = false
671
+ elsif !response_hijack and allow_chunked
672
+ lines << TRANSFER_ENCODING_CHUNKED
673
+ chunked = true
599
674
  end
600
675
 
601
676
  lines << line_ending
@@ -610,10 +685,11 @@ module Puma
610
685
  begin
611
686
  res_body.each do |part|
612
687
  if chunked
613
- client.syswrite part.bytesize.to_s(16)
614
- client.syswrite line_ending
688
+ next if part.bytesize.zero?
689
+ fast_write client, part.bytesize.to_s(16)
690
+ fast_write client, line_ending
615
691
  fast_write client, part
616
- client.syswrite line_ending
692
+ fast_write client, line_ending
617
693
  else
618
694
  fast_write client, part
619
695
  end
@@ -622,7 +698,7 @@ module Puma
622
698
  end
623
699
 
624
700
  if chunked
625
- client.syswrite CLOSE_CHUNKED
701
+ fast_write client, CLOSE_CHUNKED
626
702
  client.flush
627
703
  end
628
704
  rescue SystemCallError, IOError
@@ -633,6 +709,7 @@ module Puma
633
709
  uncork_socket client
634
710
 
635
711
  body.close
712
+ req.tempfile.unlink if req.tempfile
636
713
  res_body.close if res_body.respond_to? :close
637
714
 
638
715
  after_reply.each { |o| o.call }
@@ -702,17 +779,40 @@ module Puma
702
779
 
703
780
  # A fallback rack response if +@app+ raises as exception.
704
781
  #
705
- def lowlevel_error(e)
782
+ def lowlevel_error(e, env)
783
+ if handler = @options[:lowlevel_error_handler]
784
+ if handler.arity == 1
785
+ return handler.call(e)
786
+ else
787
+ return handler.call(e, env)
788
+ end
789
+ end
790
+
706
791
  if @leak_stack_on_error
707
792
  [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
708
793
  else
709
- [500, {}, ["A really lowlevel plumbing error occured. Please contact your local Maytag(tm) repair man.\n"]]
794
+ [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
710
795
  end
711
796
  end
712
797
 
713
798
  # Wait for all outstanding requests to finish.
714
799
  #
715
800
  def graceful_shutdown
801
+ if @options[:shutdown_debug]
802
+ threads = Thread.list
803
+ total = threads.size
804
+
805
+ pid = Process.pid
806
+
807
+ $stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
808
+
809
+ threads.each_with_index do |t,i|
810
+ $stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
811
+ $stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
812
+ end
813
+ $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
814
+ end
815
+
716
816
  if @options[:drain_on_shutdown]
717
817
  count = 0
718
818
 
@@ -724,8 +824,8 @@ module Puma
724
824
  begin
725
825
  if io = sock.accept_nonblock
726
826
  count += 1
727
- c = Client.new io, @binder.env(sock)
728
- @thread_pool << c
827
+ client = Client.new io, @binder.env(sock)
828
+ @thread_pool << client
729
829
  end
730
830
  rescue SystemCallError
731
831
  end
@@ -775,10 +875,13 @@ module Puma
775
875
  begin
776
876
  n = io.syswrite str
777
877
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
778
- IO.select(nil, [io], nil, 1)
878
+ if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
879
+ raise ConnectionError, "Socket timeout writing data"
880
+ end
881
+
779
882
  retry
780
883
  rescue Errno::EPIPE, SystemCallError, IOError
781
- return false
884
+ raise ConnectionError, "Socket timeout writing data"
782
885
  end
783
886
 
784
887
  return if n == str.bytesize