puma 5.0.4-java → 5.1.0-java
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 +46 -46
- data/README.md +25 -17
- data/docs/compile_options.md +19 -0
- data/docs/fork_worker.md +2 -0
- data/docs/systemd.md +24 -2
- data/ext/puma_http11/extconf.rb +4 -5
- data/ext/puma_http11/http11_parser.c +64 -64
- data/ext/puma_http11/puma_http11.c +8 -2
- data/lib/puma.rb +2 -2
- data/lib/puma/app/status.rb +4 -7
- data/lib/puma/binder.rb +37 -0
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +2 -7
- data/lib/puma/cluster.rb +10 -6
- data/lib/puma/cluster/worker.rb +8 -2
- data/lib/puma/cluster/worker_handle.rb +5 -2
- data/lib/puma/configuration.rb +12 -1
- data/lib/puma/const.rb +11 -3
- data/lib/puma/control_cli.rb +71 -70
- data/lib/puma/dsl.rb +67 -19
- data/lib/puma/events.rb +16 -0
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +49 -4
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/reactor.rb +12 -10
- data/lib/puma/request.rb +19 -6
- data/lib/puma/runner.rb +11 -4
- data/lib/puma/server.rb +5 -4
- data/lib/puma/state_file.rb +5 -3
- data/lib/puma/systemd.rb +46 -0
- metadata +5 -2
data/lib/puma/dsl.rb
CHANGED
@@ -34,6 +34,34 @@ module Puma
|
|
34
34
|
class DSL
|
35
35
|
include ConfigDefault
|
36
36
|
|
37
|
+
# convenience method so logic can be used in CI
|
38
|
+
# @see ssl_bind
|
39
|
+
#
|
40
|
+
def self.ssl_bind_str(host, port, opts)
|
41
|
+
verify = opts.fetch(:verify_mode, 'none').to_s
|
42
|
+
|
43
|
+
tls_str =
|
44
|
+
if opts[:no_tlsv1_1] then '&no_tlsv1_1=true'
|
45
|
+
elsif opts[:no_tlsv1] then '&no_tlsv1=true'
|
46
|
+
else ''
|
47
|
+
end
|
48
|
+
|
49
|
+
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
50
|
+
|
51
|
+
if defined?(JRUBY_VERSION)
|
52
|
+
ssl_cipher_list = opts[:ssl_cipher_list] ?
|
53
|
+
"&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil
|
54
|
+
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
55
|
+
"ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
|
56
|
+
"&verify_mode=#{verify}#{tls_str}#{ca_additions}"
|
57
|
+
else
|
58
|
+
ssl_cipher_filter = opts[:ssl_cipher_filter] ?
|
59
|
+
"&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil
|
60
|
+
"ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
|
61
|
+
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
37
65
|
def initialize(options, config)
|
38
66
|
@config = config
|
39
67
|
@options = options
|
@@ -191,6 +219,32 @@ module Puma
|
|
191
219
|
@options[:binds] = []
|
192
220
|
end
|
193
221
|
|
222
|
+
# Bind to (systemd) activated sockets, regardless of configured binds.
|
223
|
+
#
|
224
|
+
# Systemd can present sockets as file descriptors that are already opened.
|
225
|
+
# By default Puma will use these but only if it was explicitly told to bind
|
226
|
+
# to the socket. If not, it will close the activated sockets. This means
|
227
|
+
# all configuration is duplicated.
|
228
|
+
#
|
229
|
+
# Binds can contain additional configuration, but only SSL config is really
|
230
|
+
# relevant since the unix and TCP socket options are ignored.
|
231
|
+
#
|
232
|
+
# This means there is a lot of duplicated configuration for no additional
|
233
|
+
# value in most setups. This method tells the launcher to bind to all
|
234
|
+
# activated sockets, regardless of existing bind.
|
235
|
+
#
|
236
|
+
# To clear configured binds, the value only can be passed. This will clear
|
237
|
+
# out any binds that may have been configured.
|
238
|
+
#
|
239
|
+
# @example Use any systemd activated sockets as well as configured binds
|
240
|
+
# bind_to_activated_sockets
|
241
|
+
#
|
242
|
+
# @example Only bind to systemd activated sockets, ignoring other binds
|
243
|
+
# bind_to_activated_sockets 'only'
|
244
|
+
def bind_to_activated_sockets(bind=true)
|
245
|
+
@options[:bind_to_activated_sockets] = bind
|
246
|
+
end
|
247
|
+
|
194
248
|
# Define the TCP port to bind to. Use +bind+ for more advanced options.
|
195
249
|
#
|
196
250
|
# @example
|
@@ -376,28 +430,15 @@ module Puma
|
|
376
430
|
# ssl_cipher_filter: cipher_filter, # optional
|
377
431
|
# verify_mode: verify_mode, # default 'none'
|
378
432
|
# }
|
379
|
-
# @example For JRuby
|
433
|
+
# @example For JRuby, two keys are required: keystore & keystore_pass.
|
380
434
|
# ssl_bind '127.0.0.1', '9292', {
|
381
|
-
# cert: path_to_cert,
|
382
|
-
# key: path_to_key,
|
383
|
-
# ssl_cipher_filter: cipher_filter, # optional
|
384
|
-
# verify_mode: verify_mode, # default 'none'
|
385
435
|
# keystore: path_to_keystore,
|
386
|
-
# keystore_pass: password
|
436
|
+
# keystore_pass: password,
|
437
|
+
# ssl_cipher_list: cipher_list, # optional
|
438
|
+
# verify_mode: verify_mode # default 'none'
|
387
439
|
# }
|
388
440
|
def ssl_bind(host, port, opts)
|
389
|
-
|
390
|
-
no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
|
391
|
-
no_tlsv1_1 = opts.fetch(:no_tlsv1_1, 'false')
|
392
|
-
ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
|
393
|
-
|
394
|
-
if defined?(JRUBY_VERSION)
|
395
|
-
keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"
|
396
|
-
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}&#{keystore_additions}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
|
397
|
-
else
|
398
|
-
ssl_cipher_filter = "&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" if opts[:ssl_cipher_filter]
|
399
|
-
bind "ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}#{ssl_cipher_filter}&verify_mode=#{verify}&no_tlsv1=#{no_tlsv1}&no_tlsv1_1=#{no_tlsv1_1}#{ca_additions}"
|
400
|
-
end
|
441
|
+
bind self.class.ssl_bind_str(host, port, opts)
|
401
442
|
end
|
402
443
|
|
403
444
|
# Use +path+ as the file to store the server info state. This is
|
@@ -561,7 +602,7 @@ module Puma
|
|
561
602
|
end
|
562
603
|
|
563
604
|
# Preload the application before starting the workers; this conflicts with
|
564
|
-
# phased restart feature.
|
605
|
+
# phased restart feature. On by default if your app uses more than 1 worker.
|
565
606
|
#
|
566
607
|
# @note Cluster mode only.
|
567
608
|
# @example
|
@@ -810,5 +851,12 @@ module Puma
|
|
810
851
|
def nakayoshi_fork(enabled=true)
|
811
852
|
@options[:nakayoshi_fork] = enabled
|
812
853
|
end
|
854
|
+
|
855
|
+
# The number of requests to attempt inline before sending a client back to
|
856
|
+
# the reactor to be subject to normal ordering.
|
857
|
+
#
|
858
|
+
def max_fast_inline(num_of_requests)
|
859
|
+
@options[:max_fast_inline] = Float(num_of_requests)
|
860
|
+
end
|
813
861
|
end
|
814
862
|
end
|
data/lib/puma/events.rb
CHANGED
@@ -137,10 +137,26 @@ module Puma
|
|
137
137
|
register(:on_booted, &block)
|
138
138
|
end
|
139
139
|
|
140
|
+
def on_restart(&block)
|
141
|
+
register(:on_restart, &block)
|
142
|
+
end
|
143
|
+
|
144
|
+
def on_stopped(&block)
|
145
|
+
register(:on_stopped, &block)
|
146
|
+
end
|
147
|
+
|
140
148
|
def fire_on_booted!
|
141
149
|
fire(:on_booted)
|
142
150
|
end
|
143
151
|
|
152
|
+
def fire_on_restart!
|
153
|
+
fire(:on_restart)
|
154
|
+
end
|
155
|
+
|
156
|
+
def fire_on_stopped!
|
157
|
+
fire(:on_stopped)
|
158
|
+
end
|
159
|
+
|
144
160
|
DEFAULT = new(STDOUT, STDERR)
|
145
161
|
|
146
162
|
# Returns an Events object which writes its status to 2 StringIO
|
data/lib/puma/json.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Puma
|
5
|
+
|
6
|
+
# Puma deliberately avoids the use of the json gem and instead performs JSON
|
7
|
+
# serialization without any external dependencies. In a puma cluster, loading
|
8
|
+
# any gem into the puma master process means that operators cannot use a
|
9
|
+
# phased restart to upgrade their application if the new version of that
|
10
|
+
# application uses a different version of that gem. The json gem in
|
11
|
+
# particular is additionally problematic because it leverages native
|
12
|
+
# extensions. If the puma master process relies on a gem with native
|
13
|
+
# extensions and operators remove gems from disk related to old releases,
|
14
|
+
# subsequent phased restarts can fail.
|
15
|
+
#
|
16
|
+
# The implementation of JSON serialization in this module is not designed to
|
17
|
+
# be particularly full-featured or fast. It just has to handle the few places
|
18
|
+
# where Puma relies on JSON serialization internally.
|
19
|
+
|
20
|
+
module JSON
|
21
|
+
QUOTE = /"/
|
22
|
+
BACKSLASH = /\\/
|
23
|
+
CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
|
24
|
+
CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
|
25
|
+
|
26
|
+
class SerializationError < StandardError; end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def generate(value)
|
30
|
+
StringIO.open do |io|
|
31
|
+
serialize_value io, value
|
32
|
+
io.string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def serialize_value(output, value)
|
39
|
+
case value
|
40
|
+
when Hash
|
41
|
+
output << '{'
|
42
|
+
value.each_with_index do |(k, v), index|
|
43
|
+
output << ',' if index != 0
|
44
|
+
serialize_object_key output, k
|
45
|
+
output << ':'
|
46
|
+
serialize_value output, v
|
47
|
+
end
|
48
|
+
output << '}'
|
49
|
+
when Array
|
50
|
+
output << '['
|
51
|
+
value.each_with_index do |member, index|
|
52
|
+
output << ',' if index != 0
|
53
|
+
serialize_value output, member
|
54
|
+
end
|
55
|
+
output << ']'
|
56
|
+
when Integer, Float
|
57
|
+
output << value.to_s
|
58
|
+
when String
|
59
|
+
serialize_string output, value
|
60
|
+
when true
|
61
|
+
output << 'true'
|
62
|
+
when false
|
63
|
+
output << 'false'
|
64
|
+
when nil
|
65
|
+
output << 'null'
|
66
|
+
else
|
67
|
+
raise SerializationError, "Unexpected value of type #{value.class}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def serialize_string(output, value)
|
72
|
+
output << '"'
|
73
|
+
output << value.gsub(CHAR_TO_ESCAPE) do |character|
|
74
|
+
case character
|
75
|
+
when BACKSLASH
|
76
|
+
'\\\\'
|
77
|
+
when QUOTE
|
78
|
+
'\\"'
|
79
|
+
when CONTROL_CHAR_TO_ESCAPE
|
80
|
+
'\u%.4X' % character.ord
|
81
|
+
end
|
82
|
+
end
|
83
|
+
output << '"'
|
84
|
+
end
|
85
|
+
|
86
|
+
def serialize_object_key(output, value)
|
87
|
+
case value
|
88
|
+
when Symbol, String
|
89
|
+
serialize_string output, value.to_s
|
90
|
+
else
|
91
|
+
raise SerializationError, "Could not serialize object of type #{value.class} as object key"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/puma/launcher.rb
CHANGED
@@ -58,6 +58,13 @@ module Puma
|
|
58
58
|
|
59
59
|
@config.load
|
60
60
|
|
61
|
+
if @config.options[:bind_to_activated_sockets]
|
62
|
+
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
63
|
+
@config.options[:binds],
|
64
|
+
@config.options[:bind_to_activated_sockets] == 'only'
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
61
68
|
@options = @config.options
|
62
69
|
@config.clamp
|
63
70
|
|
@@ -87,6 +94,8 @@ module Puma
|
|
87
94
|
Puma.stats_object = @runner
|
88
95
|
|
89
96
|
@status = :run
|
97
|
+
|
98
|
+
log_config if ENV['PUMA_LOG_CONFIG']
|
90
99
|
end
|
91
100
|
|
92
101
|
attr_reader :binder, :events, :config, :options, :restart_dir
|
@@ -130,6 +139,7 @@ module Puma
|
|
130
139
|
|
131
140
|
# Begin async shutdown of the server gracefully
|
132
141
|
def stop
|
142
|
+
@events.fire_on_stopped!
|
133
143
|
@status = :stop
|
134
144
|
@runner.stop
|
135
145
|
end
|
@@ -168,6 +178,7 @@ module Puma
|
|
168
178
|
|
169
179
|
setup_signals
|
170
180
|
set_process_title
|
181
|
+
integrate_with_systemd
|
171
182
|
@runner.run
|
172
183
|
|
173
184
|
case @status
|
@@ -229,11 +240,10 @@ module Puma
|
|
229
240
|
def write_pid
|
230
241
|
path = @options[:pidfile]
|
231
242
|
return unless path
|
232
|
-
|
233
|
-
File.
|
234
|
-
cur = Process.pid
|
243
|
+
cur_pid = Process.pid
|
244
|
+
File.write path, cur_pid, mode: 'wb:UTF-8'
|
235
245
|
at_exit do
|
236
|
-
delete_pidfile if
|
246
|
+
delete_pidfile if cur_pid == Process.pid
|
237
247
|
end
|
238
248
|
end
|
239
249
|
|
@@ -242,6 +252,7 @@ module Puma
|
|
242
252
|
end
|
243
253
|
|
244
254
|
def restart!
|
255
|
+
@events.fire_on_restart!
|
245
256
|
@config.run_hooks :on_restart, self, @events
|
246
257
|
|
247
258
|
if Puma.jruby?
|
@@ -316,6 +327,30 @@ module Puma
|
|
316
327
|
end
|
317
328
|
end
|
318
329
|
|
330
|
+
#
|
331
|
+
# Puma's systemd integration allows Puma to inform systemd:
|
332
|
+
# 1. when it has successfully started
|
333
|
+
# 2. when it is starting shutdown
|
334
|
+
# 3. periodically for a liveness check with a watchdog thread
|
335
|
+
#
|
336
|
+
|
337
|
+
def integrate_with_systemd
|
338
|
+
return unless ENV["NOTIFY_SOCKET"]
|
339
|
+
|
340
|
+
begin
|
341
|
+
require 'puma/systemd'
|
342
|
+
rescue LoadError
|
343
|
+
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
344
|
+
return
|
345
|
+
end
|
346
|
+
|
347
|
+
log "* Enabling systemd notification integration"
|
348
|
+
|
349
|
+
systemd = Systemd.new(@events)
|
350
|
+
systemd.hook_events
|
351
|
+
systemd.start_watchdog
|
352
|
+
end
|
353
|
+
|
319
354
|
def spec_for_gem(gem_name)
|
320
355
|
Bundler.rubygems.loaded_specs(gem_name)
|
321
356
|
end
|
@@ -338,6 +373,7 @@ module Puma
|
|
338
373
|
end
|
339
374
|
|
340
375
|
def graceful_stop
|
376
|
+
@events.fire_on_stopped!
|
341
377
|
@runner.stop_blocked
|
342
378
|
log "=== puma shutdown: #{Time.now} ==="
|
343
379
|
log "- Goodbye!"
|
@@ -493,5 +529,14 @@ module Puma
|
|
493
529
|
Bundler.with_unbundled_env { yield }
|
494
530
|
end
|
495
531
|
end
|
532
|
+
|
533
|
+
def log_config
|
534
|
+
log "Configuration:"
|
535
|
+
|
536
|
+
@config.final_options
|
537
|
+
.each { |config_key, value| log "- #{config_key}: #{value}" }
|
538
|
+
|
539
|
+
log "\n"
|
540
|
+
end
|
496
541
|
end
|
497
542
|
end
|
data/lib/puma/puma_http11.jar
CHANGED
Binary file
|
data/lib/puma/reactor.rb
CHANGED
@@ -38,11 +38,11 @@ module Puma
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
# Add a new
|
41
|
+
# Add a new client to monitor.
|
42
42
|
# The object must respond to #timeout and #timeout_at.
|
43
43
|
# Returns false if the reactor is already shut down.
|
44
|
-
def add(
|
45
|
-
@input <<
|
44
|
+
def add(client)
|
45
|
+
@input << client
|
46
46
|
@selector.wakeup
|
47
47
|
true
|
48
48
|
rescue ClosedQueueError
|
@@ -92,17 +92,19 @@ module Puma
|
|
92
92
|
end
|
93
93
|
|
94
94
|
# Start monitoring the object.
|
95
|
-
def register(
|
96
|
-
@selector.register(
|
97
|
-
@timeouts <<
|
95
|
+
def register(client)
|
96
|
+
@selector.register(client.to_io, :r).value = client
|
97
|
+
@timeouts << client
|
98
|
+
rescue ArgumentError
|
99
|
+
# unreadable clients raise error when processed by NIO
|
98
100
|
end
|
99
101
|
|
100
102
|
# 'Wake up' a monitored object by calling the provided block.
|
101
103
|
# Stop monitoring the object if the block returns `true`.
|
102
|
-
def wakeup!(
|
103
|
-
if @block.call
|
104
|
-
@selector.deregister
|
105
|
-
@timeouts.delete
|
104
|
+
def wakeup!(client)
|
105
|
+
if @block.call client
|
106
|
+
@selector.deregister client.to_io
|
107
|
+
@timeouts.delete client
|
106
108
|
end
|
107
109
|
end
|
108
110
|
end
|
data/lib/puma/request.rb
CHANGED
@@ -282,13 +282,20 @@ module Puma
|
|
282
282
|
end
|
283
283
|
# private :normalize_env
|
284
284
|
|
285
|
+
# @param header_key [#to_s]
|
286
|
+
# @return [Boolean]
|
287
|
+
#
|
288
|
+
def illegal_header_key?(header_key)
|
289
|
+
!!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
|
290
|
+
end
|
291
|
+
|
285
292
|
# @param header_value [#to_s]
|
286
293
|
# @return [Boolean]
|
287
294
|
#
|
288
|
-
def
|
289
|
-
!!(
|
295
|
+
def illegal_header_value?(header_value)
|
296
|
+
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
290
297
|
end
|
291
|
-
private :
|
298
|
+
private :illegal_header_key?, :illegal_header_value?
|
292
299
|
|
293
300
|
# Fixup any headers with `,` in the name to have `_` now. We emit
|
294
301
|
# headers with `,` in them during the parse phase to avoid ambiguity
|
@@ -334,9 +341,11 @@ module Puma
|
|
334
341
|
def str_early_hints(headers)
|
335
342
|
eh_str = "HTTP/1.1 103 Early Hints\r\n".dup
|
336
343
|
headers.each_pair do |k, vs|
|
344
|
+
next if illegal_header_key?(k)
|
345
|
+
|
337
346
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
338
347
|
vs.to_s.split(NEWLINE).each do |v|
|
339
|
-
next if
|
348
|
+
next if illegal_header_value?(v)
|
340
349
|
eh_str << "#{k}: #{v}\r\n"
|
341
350
|
end
|
342
351
|
else
|
@@ -399,9 +408,11 @@ module Puma
|
|
399
408
|
res_info[:response_hijack] = nil
|
400
409
|
|
401
410
|
headers.each do |k, vs|
|
411
|
+
next if illegal_header_key?(k)
|
412
|
+
|
402
413
|
case k.downcase
|
403
414
|
when CONTENT_LENGTH2
|
404
|
-
next if
|
415
|
+
next if illegal_header_value?(vs)
|
405
416
|
res_info[:content_length] = vs
|
406
417
|
next
|
407
418
|
when TRANSFER_ENCODING
|
@@ -410,11 +421,13 @@ module Puma
|
|
410
421
|
when HIJACK
|
411
422
|
res_info[:response_hijack] = vs
|
412
423
|
next
|
424
|
+
when BANNED_HEADER_KEY
|
425
|
+
next
|
413
426
|
end
|
414
427
|
|
415
428
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
416
429
|
vs.to_s.split(NEWLINE).each do |v|
|
417
|
-
next if
|
430
|
+
next if illegal_header_value?(v)
|
418
431
|
lines.append k, colon, v, line_ending
|
419
432
|
end
|
420
433
|
else
|