puma 4.1.1 → 5.0.0
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.
- checksums.yaml +4 -4
- data/History.md +149 -10
- data/LICENSE +23 -20
- data/README.md +30 -46
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/plugins.md +20 -10
- data/docs/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +6 -0
- data/ext/puma_http11/http11_parser.c +40 -63
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +15 -2
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +9 -38
- data/lib/puma.rb +23 -0
- data/lib/puma/app/status.rb +46 -30
- data/lib/puma/binder.rb +112 -124
- data/lib/puma/cli.rb +11 -15
- data/lib/puma/client.rb +250 -209
- data/lib/puma/cluster.rb +203 -85
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +24 -19
- data/lib/puma/control_cli.rb +46 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +162 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +35 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +117 -58
- data/lib/puma/minissl.rb +60 -18
- data/lib/puma/minissl/context_builder.rb +73 -0
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +6 -12
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +16 -9
- data/lib/puma/runner.rb +11 -32
- data/lib/puma/server.rb +173 -193
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +104 -81
- data/lib/rack/handler/puma.rb +1 -5
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +23 -24
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
data/lib/puma/server.rb
CHANGED
@@ -9,13 +9,11 @@ require 'puma/null_io'
|
|
9
9
|
require 'puma/reactor'
|
10
10
|
require 'puma/client'
|
11
11
|
require 'puma/binder'
|
12
|
-
require 'puma/delegation'
|
13
|
-
require 'puma/accept_nonblock'
|
14
12
|
require 'puma/util'
|
15
|
-
|
16
|
-
require 'puma/puma_http11'
|
13
|
+
require 'puma/io_buffer'
|
17
14
|
|
18
15
|
require 'socket'
|
16
|
+
require 'forwardable'
|
19
17
|
|
20
18
|
module Puma
|
21
19
|
|
@@ -32,10 +30,11 @@ module Puma
|
|
32
30
|
class Server
|
33
31
|
|
34
32
|
include Puma::Const
|
35
|
-
extend
|
33
|
+
extend Forwardable
|
36
34
|
|
37
35
|
attr_reader :thread
|
38
36
|
attr_reader :events
|
37
|
+
attr_reader :requests_count # @version 5.0.0
|
39
38
|
attr_accessor :app
|
40
39
|
|
41
40
|
attr_accessor :min_threads
|
@@ -57,8 +56,7 @@ module Puma
|
|
57
56
|
@app = app
|
58
57
|
@events = events
|
59
58
|
|
60
|
-
@check, @notify =
|
61
|
-
|
59
|
+
@check, @notify = nil
|
62
60
|
@status = :stop
|
63
61
|
|
64
62
|
@min_threads = 0
|
@@ -85,27 +83,34 @@ module Puma
|
|
85
83
|
@mode = :http
|
86
84
|
|
87
85
|
@precheck_closing = true
|
86
|
+
|
87
|
+
@requests_count = 0
|
88
88
|
end
|
89
89
|
|
90
90
|
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
91
91
|
|
92
|
-
|
93
|
-
forward :add_ssl_listener, :@binder
|
94
|
-
forward :add_unix_listener, :@binder
|
95
|
-
forward :connected_port, :@binder
|
92
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
|
96
93
|
|
97
94
|
def inherit_binder(bind)
|
98
95
|
@binder = bind
|
99
96
|
end
|
100
97
|
|
101
|
-
|
102
|
-
|
98
|
+
class << self
|
99
|
+
# :nodoc:
|
100
|
+
# @version 5.0.0
|
101
|
+
def tcp_cork_supported?
|
102
|
+
RbConfig::CONFIG['host_os'] =~ /linux/ &&
|
103
|
+
Socket.const_defined?(:IPPROTO_TCP) &&
|
104
|
+
Socket.const_defined?(:TCP_CORK) &&
|
105
|
+
Socket.const_defined?(:TCP_INFO)
|
106
|
+
end
|
107
|
+
private :tcp_cork_supported?
|
103
108
|
end
|
104
109
|
|
105
110
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
106
111
|
# packetizes our stream. This improves both latency and throughput.
|
107
112
|
#
|
108
|
-
if
|
113
|
+
if tcp_cork_supported?
|
109
114
|
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
110
115
|
|
111
116
|
# 6 == Socket::IPPROTO_TCP
|
@@ -113,7 +118,7 @@ module Puma
|
|
113
118
|
# 1/0 == turn on/off
|
114
119
|
def cork_socket(socket)
|
115
120
|
begin
|
116
|
-
socket.setsockopt(
|
121
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
|
117
122
|
rescue IOError, SystemCallError
|
118
123
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
119
124
|
end
|
@@ -121,7 +126,7 @@ module Puma
|
|
121
126
|
|
122
127
|
def uncork_socket(socket)
|
123
128
|
begin
|
124
|
-
socket.setsockopt(
|
129
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
|
125
130
|
rescue IOError, SystemCallError
|
126
131
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
127
132
|
end
|
@@ -132,7 +137,7 @@ module Puma
|
|
132
137
|
return false unless @precheck_closing
|
133
138
|
|
134
139
|
begin
|
135
|
-
tcp_info = socket.getsockopt(Socket::
|
140
|
+
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
136
141
|
rescue IOError, SystemCallError
|
137
142
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
138
143
|
@precheck_closing = false
|
@@ -175,104 +180,6 @@ module Puma
|
|
175
180
|
@thread_pool and @thread_pool.pool_capacity
|
176
181
|
end
|
177
182
|
|
178
|
-
# Lopez Mode == raw tcp apps
|
179
|
-
|
180
|
-
def run_lopez_mode(background=true)
|
181
|
-
@thread_pool = ThreadPool.new(@min_threads,
|
182
|
-
@max_threads,
|
183
|
-
Hash) do |client, tl|
|
184
|
-
|
185
|
-
io = client.to_io
|
186
|
-
addr = io.peeraddr.last
|
187
|
-
|
188
|
-
if addr.empty?
|
189
|
-
# Set unix socket addrs to localhost
|
190
|
-
addr = "127.0.0.1:0"
|
191
|
-
else
|
192
|
-
addr = "#{addr}:#{io.peeraddr[1]}"
|
193
|
-
end
|
194
|
-
|
195
|
-
env = { 'thread' => tl, REMOTE_ADDR => addr }
|
196
|
-
|
197
|
-
begin
|
198
|
-
@app.call env, client.to_io
|
199
|
-
rescue Object => e
|
200
|
-
STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
|
201
|
-
STDERR.puts e.backtrace
|
202
|
-
end
|
203
|
-
|
204
|
-
client.close unless env['detach']
|
205
|
-
end
|
206
|
-
|
207
|
-
@events.fire :state, :running
|
208
|
-
|
209
|
-
if background
|
210
|
-
@thread = Thread.new { handle_servers_lopez_mode }
|
211
|
-
return @thread
|
212
|
-
else
|
213
|
-
handle_servers_lopez_mode
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def handle_servers_lopez_mode
|
218
|
-
begin
|
219
|
-
check = @check
|
220
|
-
sockets = [check] + @binder.ios
|
221
|
-
pool = @thread_pool
|
222
|
-
|
223
|
-
while @status == :run
|
224
|
-
begin
|
225
|
-
ios = IO.select sockets
|
226
|
-
ios.first.each do |sock|
|
227
|
-
if sock == check
|
228
|
-
break if handle_check
|
229
|
-
else
|
230
|
-
begin
|
231
|
-
if io = sock.accept_nonblock
|
232
|
-
client = Client.new io, nil
|
233
|
-
pool << client
|
234
|
-
end
|
235
|
-
rescue SystemCallError
|
236
|
-
# nothing
|
237
|
-
rescue Errno::ECONNABORTED
|
238
|
-
# client closed the socket even before accept
|
239
|
-
begin
|
240
|
-
io.close
|
241
|
-
rescue
|
242
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
rescue Object => e
|
248
|
-
@events.unknown_error self, e, "Listen loop"
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
@events.fire :state, @status
|
253
|
-
|
254
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
255
|
-
|
256
|
-
rescue Exception => e
|
257
|
-
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
258
|
-
STDERR.puts e.backtrace
|
259
|
-
ensure
|
260
|
-
begin
|
261
|
-
@check.close
|
262
|
-
rescue
|
263
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
264
|
-
end
|
265
|
-
|
266
|
-
# Prevent can't modify frozen IOError (RuntimeError)
|
267
|
-
begin
|
268
|
-
@notify.close
|
269
|
-
rescue IOError
|
270
|
-
# no biggy
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
@events.fire :state, :done
|
275
|
-
end
|
276
183
|
# Runs the server.
|
277
184
|
#
|
278
185
|
# If +background+ is true (the default) then a thread is spun
|
@@ -286,15 +193,9 @@ module Puma
|
|
286
193
|
|
287
194
|
@status = :run
|
288
195
|
|
289
|
-
if @mode == :tcp
|
290
|
-
return run_lopez_mode(background)
|
291
|
-
end
|
292
|
-
|
293
|
-
queue_requests = @queue_requests
|
294
|
-
|
295
196
|
@thread_pool = ThreadPool.new(@min_threads,
|
296
197
|
@max_threads,
|
297
|
-
IOBuffer) do |client, buffer|
|
198
|
+
::Puma::IOBuffer) do |client, buffer|
|
298
199
|
|
299
200
|
# Advertise this server into the thread
|
300
201
|
Thread.current[ThreadLocalKey] = self
|
@@ -302,10 +203,10 @@ module Puma
|
|
302
203
|
process_now = false
|
303
204
|
|
304
205
|
begin
|
305
|
-
if queue_requests
|
206
|
+
if @queue_requests
|
306
207
|
process_now = client.eagerly_finish
|
307
208
|
else
|
308
|
-
client.finish
|
209
|
+
client.finish(@first_data_timeout)
|
309
210
|
process_now = true
|
310
211
|
end
|
311
212
|
rescue MiniSSL::SSLError => e
|
@@ -315,14 +216,16 @@ module Puma
|
|
315
216
|
|
316
217
|
client.close
|
317
218
|
|
318
|
-
@events.ssl_error
|
219
|
+
@events.ssl_error e, addr, cert
|
319
220
|
rescue HttpParserError => e
|
320
|
-
client.
|
221
|
+
client.write_error(400)
|
321
222
|
client.close
|
322
223
|
|
323
|
-
@events.parse_error
|
324
|
-
rescue ConnectionError, EOFError
|
224
|
+
@events.parse_error e, client
|
225
|
+
rescue ConnectionError, EOFError => e
|
325
226
|
client.close
|
227
|
+
|
228
|
+
@events.connection_error e, client
|
326
229
|
else
|
327
230
|
if process_now
|
328
231
|
process_client client, buffer
|
@@ -331,11 +234,14 @@ module Puma
|
|
331
234
|
@reactor.add client
|
332
235
|
end
|
333
236
|
end
|
237
|
+
|
238
|
+
process_now
|
334
239
|
end
|
335
240
|
|
241
|
+
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
336
242
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
337
243
|
|
338
|
-
if queue_requests
|
244
|
+
if @queue_requests
|
339
245
|
@reactor = Reactor.new self, @thread_pool
|
340
246
|
@reactor.run_in_thread
|
341
247
|
end
|
@@ -351,7 +257,10 @@ module Puma
|
|
351
257
|
@events.fire :state, :running
|
352
258
|
|
353
259
|
if background
|
354
|
-
@thread = Thread.new
|
260
|
+
@thread = Thread.new do
|
261
|
+
Puma.set_thread_name "server"
|
262
|
+
handle_servers
|
263
|
+
end
|
355
264
|
return @thread
|
356
265
|
else
|
357
266
|
handle_servers
|
@@ -359,6 +268,7 @@ module Puma
|
|
359
268
|
end
|
360
269
|
|
361
270
|
def handle_servers
|
271
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
362
272
|
begin
|
363
273
|
check = @check
|
364
274
|
sockets = [check] + @binder.ios
|
@@ -382,51 +292,49 @@ module Puma
|
|
382
292
|
if sock == check
|
383
293
|
break if handle_check
|
384
294
|
else
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
end
|
400
|
-
rescue SystemCallError
|
401
|
-
# nothing
|
402
|
-
rescue Errno::ECONNABORTED
|
403
|
-
# client closed the socket even before accept
|
404
|
-
begin
|
405
|
-
io.close
|
406
|
-
rescue
|
407
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
408
|
-
end
|
295
|
+
pool.wait_until_not_full
|
296
|
+
pool.wait_for_less_busy_worker(
|
297
|
+
@options[:wait_for_less_busy_worker].to_f)
|
298
|
+
|
299
|
+
io = begin
|
300
|
+
sock.accept_nonblock
|
301
|
+
rescue IO::WaitReadable
|
302
|
+
next
|
303
|
+
end
|
304
|
+
client = Client.new io, @binder.env(sock)
|
305
|
+
if remote_addr_value
|
306
|
+
client.peerip = remote_addr_value
|
307
|
+
elsif remote_addr_header
|
308
|
+
client.remote_addr_header = remote_addr_header
|
409
309
|
end
|
310
|
+
pool << client
|
410
311
|
end
|
411
312
|
end
|
412
313
|
rescue Object => e
|
413
|
-
@events.unknown_error
|
314
|
+
@events.unknown_error e, nil, "Listen loop"
|
414
315
|
end
|
415
316
|
end
|
416
317
|
|
417
318
|
@events.fire :state, @status
|
418
319
|
|
419
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
420
320
|
if queue_requests
|
321
|
+
@queue_requests = false
|
421
322
|
@reactor.clear!
|
422
323
|
@reactor.shutdown
|
423
324
|
end
|
325
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
424
326
|
rescue Exception => e
|
425
|
-
|
426
|
-
STDERR.puts e.backtrace
|
327
|
+
@events.unknown_error e, nil, "Exception handling servers"
|
427
328
|
ensure
|
428
|
-
|
329
|
+
begin
|
330
|
+
@check.close unless @check.closed?
|
331
|
+
rescue Errno::EBADF, RuntimeError
|
332
|
+
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
333
|
+
# Errno::EBADF is infrequently raised
|
334
|
+
end
|
429
335
|
@notify.close
|
336
|
+
@notify = nil
|
337
|
+
@check = nil
|
430
338
|
end
|
431
339
|
|
432
340
|
@events.fire :state, :done
|
@@ -463,6 +371,8 @@ module Puma
|
|
463
371
|
clean_thread_locals = @options[:clean_thread_locals]
|
464
372
|
close_socket = true
|
465
373
|
|
374
|
+
requests = 0
|
375
|
+
|
466
376
|
while true
|
467
377
|
case handle_request(client, buffer)
|
468
378
|
when false
|
@@ -471,12 +381,24 @@ module Puma
|
|
471
381
|
close_socket = false
|
472
382
|
return
|
473
383
|
when true
|
474
|
-
return unless @queue_requests
|
475
384
|
buffer.reset
|
476
385
|
|
477
386
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
478
387
|
|
479
|
-
|
388
|
+
requests += 1
|
389
|
+
|
390
|
+
check_for_more_data = @status == :run
|
391
|
+
|
392
|
+
if requests >= MAX_FAST_INLINE
|
393
|
+
# This will mean that reset will only try to use the data it already
|
394
|
+
# has buffered and won't try to read more data. What this means is that
|
395
|
+
# every client, independent of their request speed, gets treated like a slow
|
396
|
+
# one once every MAX_FAST_INLINE requests.
|
397
|
+
check_for_more_data = false
|
398
|
+
end
|
399
|
+
|
400
|
+
unless client.reset(check_for_more_data)
|
401
|
+
return unless @queue_requests
|
480
402
|
close_socket = false
|
481
403
|
client.set_timeout @persistent_timeout
|
482
404
|
@reactor.add client
|
@@ -499,24 +421,23 @@ module Puma
|
|
499
421
|
|
500
422
|
close_socket = true
|
501
423
|
|
502
|
-
@events.ssl_error
|
424
|
+
@events.ssl_error e, addr, cert
|
503
425
|
|
504
426
|
# The client doesn't know HTTP well
|
505
427
|
rescue HttpParserError => e
|
506
428
|
lowlevel_error(e, client.env)
|
507
429
|
|
508
|
-
client.
|
430
|
+
client.write_error(400)
|
509
431
|
|
510
|
-
@events.parse_error
|
432
|
+
@events.parse_error e, client
|
511
433
|
|
512
434
|
# Server error
|
513
435
|
rescue StandardError => e
|
514
436
|
lowlevel_error(e, client.env)
|
515
437
|
|
516
|
-
client.
|
517
|
-
|
518
|
-
@events.unknown_error self, e, "Read"
|
438
|
+
client.write_error(500)
|
519
439
|
|
440
|
+
@events.unknown_error e, nil, "Read"
|
520
441
|
ensure
|
521
442
|
buffer.reset
|
522
443
|
|
@@ -526,7 +447,7 @@ module Puma
|
|
526
447
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
527
448
|
# Already closed
|
528
449
|
rescue StandardError => e
|
529
|
-
@events.unknown_error
|
450
|
+
@events.unknown_error e, nil, "Client"
|
530
451
|
end
|
531
452
|
end
|
532
453
|
end
|
@@ -562,7 +483,7 @@ module Puma
|
|
562
483
|
|
563
484
|
env[PATH_INFO] = env[REQUEST_PATH]
|
564
485
|
|
565
|
-
# From
|
486
|
+
# From https://www.ietf.org/rfc/rfc3875 :
|
566
487
|
# "Script authors should be aware that the REMOTE_ADDR and
|
567
488
|
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
568
489
|
# may not identify the ultimate source of the request.
|
@@ -609,6 +530,8 @@ module Puma
|
|
609
530
|
#
|
610
531
|
# Finally, it'll return +true+ on keep-alive connections.
|
611
532
|
def handle_request(req, lines)
|
533
|
+
@requests_count +=1
|
534
|
+
|
612
535
|
env = req.env
|
613
536
|
client = req.io
|
614
537
|
|
@@ -640,6 +563,7 @@ module Puma
|
|
640
563
|
headers.each_pair do |k, vs|
|
641
564
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
642
565
|
vs.to_s.split(NEWLINE).each do |v|
|
566
|
+
next if possible_header_injection?(v)
|
643
567
|
fast_write client, "#{k}: #{v}\r\n"
|
644
568
|
end
|
645
569
|
else
|
@@ -648,12 +572,44 @@ module Puma
|
|
648
572
|
end
|
649
573
|
|
650
574
|
fast_write client, "\r\n".freeze
|
651
|
-
rescue ConnectionError
|
575
|
+
rescue ConnectionError => e
|
576
|
+
@events.debug_error e
|
652
577
|
# noop, if we lost the socket we just won't send the early hints
|
653
578
|
end
|
654
579
|
}
|
655
580
|
end
|
656
581
|
|
582
|
+
# Fixup any headers with , in the name to have _ now. We emit
|
583
|
+
# headers with , in them during the parse phase to avoid ambiguity
|
584
|
+
# with the - to _ conversion for critical headers. But here for
|
585
|
+
# compatibility, we'll convert them back. This code is written to
|
586
|
+
# avoid allocation in the common case (ie there are no headers
|
587
|
+
# with , in their names), that's why it has the extra conditionals.
|
588
|
+
|
589
|
+
to_delete = nil
|
590
|
+
to_add = nil
|
591
|
+
|
592
|
+
env.each do |k,v|
|
593
|
+
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
594
|
+
if to_delete
|
595
|
+
to_delete << k
|
596
|
+
else
|
597
|
+
to_delete = [k]
|
598
|
+
end
|
599
|
+
|
600
|
+
unless to_add
|
601
|
+
to_add = {}
|
602
|
+
end
|
603
|
+
|
604
|
+
to_add[k.tr(",", "_")] = v
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
if to_delete
|
609
|
+
to_delete.each { |k| env.delete(k) }
|
610
|
+
env.merge! to_add
|
611
|
+
end
|
612
|
+
|
657
613
|
# A rack extension. If the app writes #call'ables to this
|
658
614
|
# array, we will invoke them when the request is done.
|
659
615
|
#
|
@@ -675,17 +631,14 @@ module Puma
|
|
675
631
|
return :async
|
676
632
|
end
|
677
633
|
rescue ThreadPool::ForceShutdown => e
|
678
|
-
@events.
|
679
|
-
@events.
|
680
|
-
|
681
|
-
status = 503
|
682
|
-
headers = {}
|
683
|
-
res_body = ["Request was internally terminated early\n"]
|
634
|
+
@events.unknown_error e, req, "Rack app"
|
635
|
+
@events.log "Detected force shutdown of a thread"
|
684
636
|
|
637
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
685
638
|
rescue Exception => e
|
686
|
-
@events.unknown_error
|
639
|
+
@events.unknown_error e, req, "Rack app"
|
687
640
|
|
688
|
-
status, headers, res_body = lowlevel_error(e, env)
|
641
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
689
642
|
end
|
690
643
|
|
691
644
|
content_length = nil
|
@@ -700,10 +653,10 @@ module Puma
|
|
700
653
|
line_ending = LINE_END
|
701
654
|
colon = COLON
|
702
655
|
|
703
|
-
http_11 =
|
656
|
+
http_11 = env[HTTP_VERSION] == HTTP_11
|
657
|
+
if http_11
|
704
658
|
allow_chunked = true
|
705
659
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
706
|
-
include_keepalive_header = false
|
707
660
|
|
708
661
|
# An optimization. The most common response is 200, so we can
|
709
662
|
# reply with the proper 200 status without having to compute
|
@@ -717,11 +670,9 @@ module Puma
|
|
717
670
|
|
718
671
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
719
672
|
end
|
720
|
-
true
|
721
673
|
else
|
722
674
|
allow_chunked = false
|
723
675
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
724
|
-
include_keepalive_header = keep_alive
|
725
676
|
|
726
677
|
# Same optimization as above for HTTP/1.1
|
727
678
|
#
|
@@ -733,14 +684,18 @@ module Puma
|
|
733
684
|
|
734
685
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
735
686
|
end
|
736
|
-
false
|
737
687
|
end
|
738
688
|
|
689
|
+
# regardless of what the client wants, we always close the connection
|
690
|
+
# if running without request queueing
|
691
|
+
keep_alive &&= @queue_requests
|
692
|
+
|
739
693
|
response_hijack = nil
|
740
694
|
|
741
695
|
headers.each do |k, vs|
|
742
696
|
case k.downcase
|
743
697
|
when CONTENT_LENGTH2
|
698
|
+
next if possible_header_injection?(vs)
|
744
699
|
content_length = vs
|
745
700
|
next
|
746
701
|
when TRANSFER_ENCODING
|
@@ -753,6 +708,7 @@ module Puma
|
|
753
708
|
|
754
709
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
755
710
|
vs.to_s.split(NEWLINE).each do |v|
|
711
|
+
next if possible_header_injection?(v)
|
756
712
|
lines.append k, colon, v, line_ending
|
757
713
|
end
|
758
714
|
else
|
@@ -760,10 +716,15 @@ module Puma
|
|
760
716
|
end
|
761
717
|
end
|
762
718
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
719
|
+
# HTTP/1.1 & 1.0 assume different defaults:
|
720
|
+
# - HTTP 1.0 assumes the connection will be closed if not specified
|
721
|
+
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
722
|
+
# Only set the header if we're doing something which is not the default
|
723
|
+
# for this protocol version
|
724
|
+
if http_11
|
725
|
+
lines << CONNECTION_CLOSE if !keep_alive
|
726
|
+
else
|
727
|
+
lines << CONNECTION_KEEP_ALIVE if keep_alive
|
767
728
|
end
|
768
729
|
|
769
730
|
if no_body
|
@@ -890,19 +851,21 @@ module Puma
|
|
890
851
|
|
891
852
|
# A fallback rack response if +@app+ raises as exception.
|
892
853
|
#
|
893
|
-
def lowlevel_error(e, env)
|
854
|
+
def lowlevel_error(e, env, status=500)
|
894
855
|
if handler = @options[:lowlevel_error_handler]
|
895
856
|
if handler.arity == 1
|
896
857
|
return handler.call(e)
|
897
|
-
|
858
|
+
elsif handler.arity == 2
|
898
859
|
return handler.call(e, env)
|
860
|
+
else
|
861
|
+
return handler.call(e, env, status)
|
899
862
|
end
|
900
863
|
end
|
901
864
|
|
902
865
|
if @leak_stack_on_error
|
903
|
-
[
|
866
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
904
867
|
else
|
905
|
-
[
|
868
|
+
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
906
869
|
end
|
907
870
|
end
|
908
871
|
|
@@ -960,9 +923,10 @@ module Puma
|
|
960
923
|
end
|
961
924
|
|
962
925
|
def notify_safely(message)
|
926
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
963
927
|
begin
|
964
928
|
@notify << message
|
965
|
-
rescue IOError
|
929
|
+
rescue IOError, NoMethodError, Errno::EPIPE
|
966
930
|
# The server, in another thread, is shutting down
|
967
931
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
968
932
|
rescue RuntimeError => e
|
@@ -989,8 +953,9 @@ module Puma
|
|
989
953
|
@thread.join if @thread && sync
|
990
954
|
end
|
991
955
|
|
992
|
-
def begin_restart
|
956
|
+
def begin_restart(sync=false)
|
993
957
|
notify_safely(RESTART_COMMAND)
|
958
|
+
@thread.join if @thread && sync
|
994
959
|
end
|
995
960
|
|
996
961
|
def fast_write(io, str)
|
@@ -1023,5 +988,20 @@ module Puma
|
|
1023
988
|
def shutting_down?
|
1024
989
|
@status == :stop || @status == :restart
|
1025
990
|
end
|
991
|
+
|
992
|
+
def possible_header_injection?(header_value)
|
993
|
+
HTTP_INJECTION_REGEX =~ header_value.to_s
|
994
|
+
end
|
995
|
+
private :possible_header_injection?
|
996
|
+
|
997
|
+
# List of methods invoked by #stats.
|
998
|
+
# @version 5.0.0
|
999
|
+
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
1000
|
+
|
1001
|
+
# Returns a hash of stats about the running server for reporting purposes.
|
1002
|
+
# @version 5.0.0
|
1003
|
+
def stats
|
1004
|
+
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
1005
|
+
end
|
1026
1006
|
end
|
1027
1007
|
end
|