puma 4.1.1 → 4.3.6
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 +71 -10
- data/README.md +8 -37
- data/docs/plugins.md +20 -10
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/extconf.rb +5 -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/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma.rb +6 -0
- data/lib/puma/app/status.rb +33 -30
- data/lib/puma/binder.rb +38 -70
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +202 -206
- data/lib/puma/cluster.rb +13 -12
- data/lib/puma/const.rb +23 -18
- data/lib/puma/control_cli.rb +20 -3
- data/lib/puma/dsl.rb +19 -1
- data/lib/puma/launcher.rb +87 -46
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/reactor.rb +7 -5
- data/lib/puma/runner.rb +10 -3
- data/lib/puma/server.rb +68 -12
- data/lib/puma/thread_pool.rb +10 -32
- data/lib/rack/handler/puma.rb +0 -2
- data/tools/docker/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +10 -9
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
@@ -0,0 +1,76 @@
|
|
1
|
+
module Puma
|
2
|
+
module MiniSSL
|
3
|
+
class ContextBuilder
|
4
|
+
def initialize(params, events)
|
5
|
+
require 'puma/minissl'
|
6
|
+
MiniSSL.check
|
7
|
+
|
8
|
+
@params = params
|
9
|
+
@events = events
|
10
|
+
end
|
11
|
+
|
12
|
+
def context
|
13
|
+
ctx = MiniSSL::Context.new
|
14
|
+
|
15
|
+
if defined?(JRUBY_VERSION)
|
16
|
+
unless params['keystore']
|
17
|
+
events.error "Please specify the Java keystore via 'keystore='"
|
18
|
+
end
|
19
|
+
|
20
|
+
ctx.keystore = params['keystore']
|
21
|
+
|
22
|
+
unless params['keystore-pass']
|
23
|
+
events.error "Please specify the Java keystore password via 'keystore-pass='"
|
24
|
+
end
|
25
|
+
|
26
|
+
ctx.keystore_pass = params['keystore-pass']
|
27
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
28
|
+
else
|
29
|
+
unless params['key']
|
30
|
+
events.error "Please specify the SSL key via 'key='"
|
31
|
+
end
|
32
|
+
|
33
|
+
ctx.key = params['key']
|
34
|
+
|
35
|
+
unless params['cert']
|
36
|
+
events.error "Please specify the SSL cert via 'cert='"
|
37
|
+
end
|
38
|
+
|
39
|
+
ctx.cert = params['cert']
|
40
|
+
|
41
|
+
if ['peer', 'force_peer'].include?(params['verify_mode'])
|
42
|
+
unless params['ca']
|
43
|
+
events.error "Please specify the SSL ca via 'ca='"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ctx.ca = params['ca'] if params['ca']
|
48
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
49
|
+
end
|
50
|
+
|
51
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
52
|
+
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
53
|
+
|
54
|
+
if params['verify_mode']
|
55
|
+
ctx.verify_mode = case params['verify_mode']
|
56
|
+
when "peer"
|
57
|
+
MiniSSL::VERIFY_PEER
|
58
|
+
when "force_peer"
|
59
|
+
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
60
|
+
when "none"
|
61
|
+
MiniSSL::VERIFY_NONE
|
62
|
+
else
|
63
|
+
events.error "Please specify a valid verify_mode="
|
64
|
+
MiniSSL::VERIFY_NONE
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
ctx
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
attr_reader :params, :events
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/puma/plugin.rb
CHANGED
data/lib/puma/reactor.rb
CHANGED
@@ -225,7 +225,7 @@ module Puma
|
|
225
225
|
# will be flooding them with errors when persistent connections
|
226
226
|
# are closed.
|
227
227
|
rescue ConnectionError
|
228
|
-
c.
|
228
|
+
c.write_error(500)
|
229
229
|
c.close
|
230
230
|
|
231
231
|
clear_monitor mon
|
@@ -237,7 +237,8 @@ module Puma
|
|
237
237
|
ssl_socket = c.io
|
238
238
|
begin
|
239
239
|
addr = ssl_socket.peeraddr.last
|
240
|
-
|
240
|
+
# EINVAL can happen when browser closes socket w/security exception
|
241
|
+
rescue IOError, Errno::EINVAL
|
241
242
|
addr = "<unknown>"
|
242
243
|
end
|
243
244
|
|
@@ -252,7 +253,7 @@ module Puma
|
|
252
253
|
rescue HttpParserError => e
|
253
254
|
@server.lowlevel_error(e, c.env)
|
254
255
|
|
255
|
-
c.
|
256
|
+
c.write_error(400)
|
256
257
|
c.close
|
257
258
|
|
258
259
|
clear_monitor mon
|
@@ -261,7 +262,7 @@ module Puma
|
|
261
262
|
rescue StandardError => e
|
262
263
|
@server.lowlevel_error(e, c.env)
|
263
264
|
|
264
|
-
c.
|
265
|
+
c.write_error(500)
|
265
266
|
c.close
|
266
267
|
|
267
268
|
clear_monitor mon
|
@@ -277,7 +278,7 @@ module Puma
|
|
277
278
|
while @timeouts.first.value.timeout_at < now
|
278
279
|
mon = @timeouts.shift
|
279
280
|
c = mon.value
|
280
|
-
c.
|
281
|
+
c.write_error(408) if c.in_data_phase
|
281
282
|
c.close
|
282
283
|
|
283
284
|
clear_monitor mon
|
@@ -307,6 +308,7 @@ module Puma
|
|
307
308
|
|
308
309
|
def run_in_thread
|
309
310
|
@thread = Thread.new do
|
311
|
+
Puma.set_thread_name "reactor"
|
310
312
|
begin
|
311
313
|
run_internal
|
312
314
|
rescue StandardError => e
|
data/lib/puma/runner.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'puma/server'
|
4
4
|
require 'puma/const'
|
5
|
+
require 'puma/minissl/context_builder'
|
5
6
|
|
6
7
|
module Puma
|
7
8
|
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
@@ -53,17 +54,23 @@ module Puma
|
|
53
54
|
|
54
55
|
uri = URI.parse str
|
55
56
|
|
56
|
-
app = Puma::App::Status.new @launcher
|
57
|
-
|
58
57
|
if token = @options[:control_auth_token]
|
59
|
-
|
58
|
+
token = nil if token.empty? || token == 'none'
|
60
59
|
end
|
61
60
|
|
61
|
+
app = Puma::App::Status.new @launcher, token
|
62
|
+
|
62
63
|
control = Puma::Server.new app, @launcher.events
|
63
64
|
control.min_threads = 0
|
64
65
|
control.max_threads = 1
|
65
66
|
|
66
67
|
case uri.scheme
|
68
|
+
when "ssl"
|
69
|
+
log "* Starting control server on #{str}"
|
70
|
+
params = Util.parse_query uri.query
|
71
|
+
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
72
|
+
|
73
|
+
control.add_ssl_listener uri.host, uri.port, ctx
|
67
74
|
when "tcp"
|
68
75
|
log "* Starting control server on #{str}"
|
69
76
|
control.add_tcp_listener uri.host, uri.port
|
data/lib/puma/server.rb
CHANGED
@@ -9,13 +9,13 @@ require 'puma/null_io'
|
|
9
9
|
require 'puma/reactor'
|
10
10
|
require 'puma/client'
|
11
11
|
require 'puma/binder'
|
12
|
-
require 'puma/delegation'
|
13
12
|
require 'puma/accept_nonblock'
|
14
13
|
require 'puma/util'
|
15
14
|
|
16
15
|
require 'puma/puma_http11'
|
17
16
|
|
18
17
|
require 'socket'
|
18
|
+
require 'forwardable'
|
19
19
|
|
20
20
|
module Puma
|
21
21
|
|
@@ -32,7 +32,7 @@ module Puma
|
|
32
32
|
class Server
|
33
33
|
|
34
34
|
include Puma::Const
|
35
|
-
extend
|
35
|
+
extend Forwardable
|
36
36
|
|
37
37
|
attr_reader :thread
|
38
38
|
attr_reader :events
|
@@ -89,10 +89,7 @@ module Puma
|
|
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_port
|
96
93
|
|
97
94
|
def inherit_binder(bind)
|
98
95
|
@binder = bind
|
@@ -207,7 +204,10 @@ module Puma
|
|
207
204
|
@events.fire :state, :running
|
208
205
|
|
209
206
|
if background
|
210
|
-
@thread = Thread.new
|
207
|
+
@thread = Thread.new do
|
208
|
+
Puma.set_thread_name "server"
|
209
|
+
handle_servers_lopez_mode
|
210
|
+
end
|
211
211
|
return @thread
|
212
212
|
else
|
213
213
|
handle_servers_lopez_mode
|
@@ -317,7 +317,7 @@ module Puma
|
|
317
317
|
|
318
318
|
@events.ssl_error self, addr, cert, e
|
319
319
|
rescue HttpParserError => e
|
320
|
-
client.
|
320
|
+
client.write_error(400)
|
321
321
|
client.close
|
322
322
|
|
323
323
|
@events.parse_error self, client.env, e
|
@@ -351,7 +351,10 @@ module Puma
|
|
351
351
|
@events.fire :state, :running
|
352
352
|
|
353
353
|
if background
|
354
|
-
@thread = Thread.new
|
354
|
+
@thread = Thread.new do
|
355
|
+
Puma.set_thread_name "server"
|
356
|
+
handle_servers
|
357
|
+
end
|
355
358
|
return @thread
|
356
359
|
else
|
357
360
|
handle_servers
|
@@ -463,6 +466,8 @@ module Puma
|
|
463
466
|
clean_thread_locals = @options[:clean_thread_locals]
|
464
467
|
close_socket = true
|
465
468
|
|
469
|
+
requests = 0
|
470
|
+
|
466
471
|
while true
|
467
472
|
case handle_request(client, buffer)
|
468
473
|
when false
|
@@ -476,7 +481,19 @@ module Puma
|
|
476
481
|
|
477
482
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
478
483
|
|
479
|
-
|
484
|
+
requests += 1
|
485
|
+
|
486
|
+
check_for_more_data = @status == :run
|
487
|
+
|
488
|
+
if requests >= MAX_FAST_INLINE
|
489
|
+
# This will mean that reset will only try to use the data it already
|
490
|
+
# has buffered and won't try to read more data. What this means is that
|
491
|
+
# every client, independent of their request speed, gets treated like a slow
|
492
|
+
# one once every MAX_FAST_INLINE requests.
|
493
|
+
check_for_more_data = false
|
494
|
+
end
|
495
|
+
|
496
|
+
unless client.reset(check_for_more_data)
|
480
497
|
close_socket = false
|
481
498
|
client.set_timeout @persistent_timeout
|
482
499
|
@reactor.add client
|
@@ -505,7 +522,7 @@ module Puma
|
|
505
522
|
rescue HttpParserError => e
|
506
523
|
lowlevel_error(e, client.env)
|
507
524
|
|
508
|
-
client.
|
525
|
+
client.write_error(400)
|
509
526
|
|
510
527
|
@events.parse_error self, client.env, e
|
511
528
|
|
@@ -513,7 +530,7 @@ module Puma
|
|
513
530
|
rescue StandardError => e
|
514
531
|
lowlevel_error(e, client.env)
|
515
532
|
|
516
|
-
client.
|
533
|
+
client.write_error(500)
|
517
534
|
|
518
535
|
@events.unknown_error self, e, "Read"
|
519
536
|
|
@@ -640,6 +657,7 @@ module Puma
|
|
640
657
|
headers.each_pair do |k, vs|
|
641
658
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
642
659
|
vs.to_s.split(NEWLINE).each do |v|
|
660
|
+
next if possible_header_injection?(v)
|
643
661
|
fast_write client, "#{k}: #{v}\r\n"
|
644
662
|
end
|
645
663
|
else
|
@@ -654,6 +672,37 @@ module Puma
|
|
654
672
|
}
|
655
673
|
end
|
656
674
|
|
675
|
+
# Fixup any headers with , in the name to have _ now. We emit
|
676
|
+
# headers with , in them during the parse phase to avoid ambiguity
|
677
|
+
# with the - to _ conversion for critical headers. But here for
|
678
|
+
# compatibility, we'll convert them back. This code is written to
|
679
|
+
# avoid allocation in the common case (ie there are no headers
|
680
|
+
# with , in their names), that's why it has the extra conditionals.
|
681
|
+
|
682
|
+
to_delete = nil
|
683
|
+
to_add = nil
|
684
|
+
|
685
|
+
env.each do |k,v|
|
686
|
+
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
687
|
+
if to_delete
|
688
|
+
to_delete << k
|
689
|
+
else
|
690
|
+
to_delete = [k]
|
691
|
+
end
|
692
|
+
|
693
|
+
unless to_add
|
694
|
+
to_add = {}
|
695
|
+
end
|
696
|
+
|
697
|
+
to_add[k.gsub(",", "_")] = v
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
if to_delete
|
702
|
+
to_delete.each { |k| env.delete(k) }
|
703
|
+
env.merge! to_add
|
704
|
+
end
|
705
|
+
|
657
706
|
# A rack extension. If the app writes #call'ables to this
|
658
707
|
# array, we will invoke them when the request is done.
|
659
708
|
#
|
@@ -741,6 +790,7 @@ module Puma
|
|
741
790
|
headers.each do |k, vs|
|
742
791
|
case k.downcase
|
743
792
|
when CONTENT_LENGTH2
|
793
|
+
next if possible_header_injection?(vs)
|
744
794
|
content_length = vs
|
745
795
|
next
|
746
796
|
when TRANSFER_ENCODING
|
@@ -753,6 +803,7 @@ module Puma
|
|
753
803
|
|
754
804
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
755
805
|
vs.to_s.split(NEWLINE).each do |v|
|
806
|
+
next if possible_header_injection?(v)
|
756
807
|
lines.append k, colon, v, line_ending
|
757
808
|
end
|
758
809
|
else
|
@@ -1023,5 +1074,10 @@ module Puma
|
|
1023
1074
|
def shutting_down?
|
1024
1075
|
@status == :stop || @status == :restart
|
1025
1076
|
end
|
1077
|
+
|
1078
|
+
def possible_header_injection?(header_value)
|
1079
|
+
HTTP_INJECTION_REGEX =~ header_value.to_s
|
1080
|
+
end
|
1081
|
+
private :possible_header_injection?
|
1026
1082
|
end
|
1027
1083
|
end
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -87,8 +87,7 @@ module Puma
|
|
87
87
|
@spawned += 1
|
88
88
|
|
89
89
|
th = Thread.new(@spawned) do |spawned|
|
90
|
-
|
91
|
-
Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
|
90
|
+
Puma.set_thread_name 'threadpool %03i' % spawned
|
92
91
|
todo = @todo
|
93
92
|
block = @block
|
94
93
|
mutex = @mutex
|
@@ -190,7 +189,7 @@ module Puma
|
|
190
189
|
# request, it might not be added to the `@todo` array right away.
|
191
190
|
# For example if a slow client has only sent a header, but not a body
|
192
191
|
# then the `@todo` array would stay the same size as the reactor works
|
193
|
-
# to try to buffer the request. In
|
192
|
+
# to try to buffer the request. In that scenario the next call to this
|
194
193
|
# method would not block and another request would be added into the reactor
|
195
194
|
# by the server. This would continue until a fully bufferend request
|
196
195
|
# makes it through the reactor and can then be processed by the thread pool.
|
@@ -244,10 +243,12 @@ module Puma
|
|
244
243
|
end
|
245
244
|
end
|
246
245
|
|
247
|
-
class
|
248
|
-
def initialize(pool, timeout)
|
246
|
+
class Automaton
|
247
|
+
def initialize(pool, timeout, thread_name, message)
|
249
248
|
@pool = pool
|
250
249
|
@timeout = timeout
|
250
|
+
@thread_name = thread_name
|
251
|
+
@message = message
|
251
252
|
@running = false
|
252
253
|
end
|
253
254
|
|
@@ -255,8 +256,9 @@ module Puma
|
|
255
256
|
@running = true
|
256
257
|
|
257
258
|
@thread = Thread.new do
|
259
|
+
Puma.set_thread_name @thread_name
|
258
260
|
while @running
|
259
|
-
@pool.
|
261
|
+
@pool.public_send(@message)
|
260
262
|
sleep @timeout
|
261
263
|
end
|
262
264
|
end
|
@@ -269,36 +271,12 @@ module Puma
|
|
269
271
|
end
|
270
272
|
|
271
273
|
def auto_trim!(timeout=30)
|
272
|
-
@auto_trim =
|
274
|
+
@auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
|
273
275
|
@auto_trim.start!
|
274
276
|
end
|
275
277
|
|
276
|
-
class Reaper
|
277
|
-
def initialize(pool, timeout)
|
278
|
-
@pool = pool
|
279
|
-
@timeout = timeout
|
280
|
-
@running = false
|
281
|
-
end
|
282
|
-
|
283
|
-
def start!
|
284
|
-
@running = true
|
285
|
-
|
286
|
-
@thread = Thread.new do
|
287
|
-
while @running
|
288
|
-
@pool.reap
|
289
|
-
sleep @timeout
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
def stop
|
295
|
-
@running = false
|
296
|
-
@thread.wakeup
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
278
|
def auto_reap!(timeout=5)
|
301
|
-
@reaper =
|
279
|
+
@reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
|
302
280
|
@reaper.start!
|
303
281
|
end
|
304
282
|
|