puma 5.0.4-java → 5.2.2-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.

Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +109 -48
  3. data/README.md +48 -18
  4. data/docs/architecture.md +21 -18
  5. data/docs/compile_options.md +19 -0
  6. data/docs/deployment.md +1 -1
  7. data/docs/fork_worker.md +2 -0
  8. data/docs/kubernetes.md +66 -0
  9. data/docs/plugins.md +1 -1
  10. data/docs/rails_dev_mode.md +29 -0
  11. data/docs/stats.md +142 -0
  12. data/docs/systemd.md +24 -2
  13. data/ext/puma_http11/extconf.rb +18 -5
  14. data/ext/puma_http11/http11_parser.c +45 -47
  15. data/ext/puma_http11/http11_parser.java.rl +1 -1
  16. data/ext/puma_http11/http11_parser.rl +1 -1
  17. data/ext/puma_http11/mini_ssl.c +162 -84
  18. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
  19. data/ext/puma_http11/puma_http11.c +8 -2
  20. data/lib/puma.rb +20 -10
  21. data/lib/puma/app/status.rb +4 -7
  22. data/lib/puma/binder.rb +60 -24
  23. data/lib/puma/cli.rb +4 -0
  24. data/lib/puma/client.rb +4 -9
  25. data/lib/puma/cluster.rb +10 -6
  26. data/lib/puma/cluster/worker.rb +8 -2
  27. data/lib/puma/cluster/worker_handle.rb +5 -2
  28. data/lib/puma/configuration.rb +14 -1
  29. data/lib/puma/const.rb +11 -3
  30. data/lib/puma/control_cli.rb +73 -70
  31. data/lib/puma/detect.rb +14 -10
  32. data/lib/puma/dsl.rb +104 -22
  33. data/lib/puma/error_logger.rb +10 -3
  34. data/lib/puma/events.rb +18 -3
  35. data/lib/puma/json.rb +96 -0
  36. data/lib/puma/launcher.rb +52 -6
  37. data/lib/puma/minissl.rb +48 -17
  38. data/lib/puma/minissl/context_builder.rb +6 -0
  39. data/lib/puma/null_io.rb +12 -0
  40. data/lib/puma/puma_http11.jar +0 -0
  41. data/lib/puma/reactor.rb +19 -12
  42. data/lib/puma/request.rb +20 -7
  43. data/lib/puma/runner.rb +19 -7
  44. data/lib/puma/server.rb +20 -75
  45. data/lib/puma/state_file.rb +5 -3
  46. data/lib/puma/systemd.rb +46 -0
  47. data/lib/rack/handler/puma.rb +1 -0
  48. metadata +8 -2
data/lib/puma/minissl.rb CHANGED
@@ -73,7 +73,6 @@ module Puma
73
73
 
74
74
  def engine_read_all
75
75
  output = @engine.read
76
- raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
77
76
  while output and additional_output = @engine.read
78
77
  output << additional_output
79
78
  end
@@ -100,6 +99,7 @@ module Puma
100
99
  # ourselves.
101
100
  raise IO::EAGAINWaitReadable
102
101
  elsif data.nil?
102
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
103
103
  return nil
104
104
  end
105
105
 
@@ -117,22 +117,23 @@ module Puma
117
117
  def write(data)
118
118
  return 0 if data.empty?
119
119
 
120
- need = data.bytesize
120
+ data_size = data.bytesize
121
+ need = data_size
121
122
 
122
123
  while true
123
124
  wrote = @engine.write data
124
- enc = @engine.extract
125
125
 
126
- while enc
127
- @socket.write enc
128
- enc = @engine.extract
126
+ enc_wr = ''.dup
127
+ while (enc = @engine.extract)
128
+ enc_wr << enc
129
129
  end
130
+ @socket.write enc_wr unless enc_wr.empty?
130
131
 
131
132
  need -= wrote
132
133
 
133
- return data.bytesize if need == 0
134
+ return data_size if need == 0
134
135
 
135
- data = data[wrote..-1]
136
+ data = data.byteslice(wrote..-1)
136
137
  end
137
138
  end
138
139
 
@@ -245,6 +246,7 @@ module Puma
245
246
  attr_reader :cert
246
247
  attr_reader :ca
247
248
  attr_accessor :ssl_cipher_filter
249
+ attr_accessor :verification_flags
248
250
 
249
251
  def key=(key)
250
252
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -287,33 +289,58 @@ module Puma
287
289
  VERIFY_PEER = 1
288
290
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
289
291
 
292
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
293
+ # /* Certificate verify flags */
294
+ VERIFICATION_FLAGS = {
295
+ "USE_CHECK_TIME" => 0x2,
296
+ "CRL_CHECK" => 0x4,
297
+ "CRL_CHECK_ALL" => 0x8,
298
+ "IGNORE_CRITICAL" => 0x10,
299
+ "X509_STRICT" => 0x20,
300
+ "ALLOW_PROXY_CERTS" => 0x40,
301
+ "POLICY_CHECK" => 0x80,
302
+ "EXPLICIT_POLICY" => 0x100,
303
+ "INHIBIT_ANY" => 0x200,
304
+ "INHIBIT_MAP" => 0x400,
305
+ "NOTIFY_POLICY" => 0x800,
306
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
307
+ "USE_DELTAS" => 0x2000,
308
+ "CHECK_SS_SIGNATURE" => 0x4000,
309
+ "TRUSTED_FIRST" => 0x8000,
310
+ "SUITEB_128_LOS_ONLY" => 0x10000,
311
+ "SUITEB_192_LOS" => 0x20000,
312
+ "SUITEB_128_LOS" => 0x30000,
313
+ "PARTIAL_CHAIN" => 0x80000,
314
+ "NO_ALT_CHAINS" => 0x100000,
315
+ "NO_CHECK_TIME" => 0x200000
316
+ }.freeze
317
+
290
318
  class Server
291
319
  def initialize(socket, ctx)
292
320
  @socket = socket
293
321
  @ctx = ctx
294
- end
295
-
296
- # @!attribute [r] to_io
297
- def to_io
298
- @socket
322
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
299
323
  end
300
324
 
301
325
  def accept
302
326
  @ctx.check
303
327
  io = @socket.accept
304
- engine = Engine.server @ctx
305
-
328
+ engine = Engine.server @eng_ctx
306
329
  Socket.new io, engine
307
330
  end
308
331
 
309
332
  def accept_nonblock
310
333
  @ctx.check
311
334
  io = @socket.accept_nonblock
312
- engine = Engine.server @ctx
313
-
335
+ engine = Engine.server @eng_ctx
314
336
  Socket.new io, engine
315
337
  end
316
338
 
339
+ # @!attribute [r] to_io
340
+ def to_io
341
+ @socket
342
+ end
343
+
317
344
  # @!attribute [r] addr
318
345
  # @version 5.0.0
319
346
  def addr
@@ -323,6 +350,10 @@ module Puma
323
350
  def close
324
351
  @socket.close unless @socket.closed? # closed? call is for Windows
325
352
  end
353
+
354
+ def closed?
355
+ @socket.closed?
356
+ end
326
357
  end
327
358
  end
328
359
  end
@@ -62,6 +62,12 @@ module Puma
62
62
  end
63
63
  end
64
64
 
65
+ if params['verification_flags']
66
+ ctx.verification_flags = params['verification_flags'].split(',').
67
+ map { |flag| MiniSSL::VERIFICATION_FLAGS.fetch(flag) }.
68
+ inject { |sum, flag| sum ? sum | flag : flag }
69
+ end
70
+
65
71
  ctx
66
72
  end
67
73
 
data/lib/puma/null_io.rb CHANGED
@@ -9,6 +9,10 @@ module Puma
9
9
  nil
10
10
  end
11
11
 
12
+ def string
13
+ ""
14
+ end
15
+
12
16
  def each
13
17
  end
14
18
 
@@ -32,6 +36,10 @@ module Puma
32
36
  true
33
37
  end
34
38
 
39
+ def sync
40
+ true
41
+ end
42
+
35
43
  def sync=(v)
36
44
  end
37
45
 
@@ -40,5 +48,9 @@ module Puma
40
48
 
41
49
  def write(*ary)
42
50
  end
51
+
52
+ def flush
53
+ self
54
+ end
43
55
  end
44
56
  end
Binary file
data/lib/puma/reactor.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'puma/queue_close' unless ::Queue.instance_methods.include? :close
4
4
 
5
5
  module Puma
6
+ class UnsupportedBackend < StandardError; end
7
+
6
8
  # Monitors a collection of IO objects, calling a block whenever
7
9
  # any monitored object either receives data or times out, or when the Reactor shuts down.
8
10
  #
@@ -18,9 +20,12 @@ module Puma
18
20
  # Create a new Reactor to monitor IO objects added by #add.
19
21
  # The provided block will be invoked when an IO has data available to read,
20
22
  # its timeout elapses, or when the Reactor shuts down.
21
- def initialize(&block)
23
+ def initialize(backend, &block)
22
24
  require 'nio'
23
- @selector = NIO::Selector.new
25
+ unless backend == :auto || NIO::Selector.backends.include?(backend)
26
+ raise "unsupported IO selector backend: #{backend} (available backends: #{NIO::Selector.backends.join(', ')})"
27
+ end
28
+ @selector = backend == :auto ? NIO::Selector.new : NIO::Selector.new(backend)
24
29
  @input = Queue.new
25
30
  @timeouts = []
26
31
  @block = block
@@ -38,11 +43,11 @@ module Puma
38
43
  end
39
44
  end
40
45
 
41
- # Add a new IO object to monitor.
46
+ # Add a new client to monitor.
42
47
  # The object must respond to #timeout and #timeout_at.
43
48
  # Returns false if the reactor is already shut down.
44
- def add(io)
45
- @input << io
49
+ def add(client)
50
+ @input << client
46
51
  @selector.wakeup
47
52
  true
48
53
  rescue ClosedQueueError
@@ -92,17 +97,19 @@ module Puma
92
97
  end
93
98
 
94
99
  # Start monitoring the object.
95
- def register(io)
96
- @selector.register(io, :r).value = io
97
- @timeouts << io
100
+ def register(client)
101
+ @selector.register(client.to_io, :r).value = client
102
+ @timeouts << client
103
+ rescue ArgumentError
104
+ # unreadable clients raise error when processed by NIO
98
105
  end
99
106
 
100
107
  # 'Wake up' a monitored object by calling the provided block.
101
108
  # Stop monitoring the object if the block returns `true`.
102
- def wakeup!(io)
103
- if @block.call(io)
104
- @selector.deregister(io)
105
- @timeouts.delete(io)
109
+ def wakeup!(client)
110
+ if @block.call client
111
+ @selector.deregister client.to_io
112
+ @timeouts.delete client
106
113
  end
107
114
  end
108
115
  end
data/lib/puma/request.rb CHANGED
@@ -30,7 +30,7 @@ module Puma
30
30
  #
31
31
  def handle_request(client, lines)
32
32
  env = client.env
33
- io = client.io
33
+ io = client.io # io may be a MiniSSL::Socket
34
34
 
35
35
  return false if closed_socket?(io)
36
36
 
@@ -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 possible_header_injection?(header_value)
289
- !!(HTTP_INJECTION_REGEX =~ header_value.to_s)
295
+ def illegal_header_value?(header_value)
296
+ !!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
290
297
  end
291
- private :possible_header_injection?
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 possible_header_injection?(v)
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 possible_header_injection?(vs)
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 possible_header_injection?(v)
430
+ next if illegal_header_value?(v)
418
431
  lines.append k, colon, v, line_ending
419
432
  end
420
433
  else
data/lib/puma/runner.rb CHANGED
@@ -55,11 +55,11 @@ module Puma
55
55
  app = Puma::App::Status.new @launcher, token
56
56
 
57
57
  control = Puma::Server.new app, @launcher.events,
58
- { min_threads: 0, max_threads: 1 }
58
+ { min_threads: 0, max_threads: 1, queue_requests: false }
59
59
 
60
60
  control.binder.parse [str], self, 'Starting control server'
61
61
 
62
- control.run
62
+ control.run thread_name: 'control'
63
63
  @control = control
64
64
  end
65
65
 
@@ -86,9 +86,16 @@ module Puma
86
86
  max_t = @options[:max_threads]
87
87
 
88
88
  log "Puma starting in #{mode} mode..."
89
- log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
90
- log "* Min threads: #{min_t}, max threads: #{max_t}"
91
- log "* Environment: #{ENV['RACK_ENV']}"
89
+ log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
90
+ log "* Min threads: #{min_t}"
91
+ log "* Max threads: #{max_t}"
92
+ log "* Environment: #{ENV['RACK_ENV']}"
93
+
94
+ if mode == "cluster"
95
+ log "* Master PID: #{Process.pid}"
96
+ else
97
+ log "* PID: #{Process.pid}"
98
+ end
92
99
  end
93
100
 
94
101
  def redirected_io?
@@ -106,8 +113,8 @@ module Puma
106
113
  end
107
114
 
108
115
  STDOUT.reopen stdout, (append ? "a" : "w")
109
- STDOUT.sync = true
110
116
  STDOUT.puts "=== puma startup: #{Time.now} ==="
117
+ STDOUT.flush unless STDOUT.sync
111
118
  end
112
119
 
113
120
  if stderr
@@ -116,8 +123,13 @@ module Puma
116
123
  end
117
124
 
118
125
  STDERR.reopen stderr, (append ? "a" : "w")
119
- STDERR.sync = true
120
126
  STDERR.puts "=== puma startup: #{Time.now} ==="
127
+ STDERR.flush unless STDERR.sync
128
+ end
129
+
130
+ if @options[:mutate_stdout_and_stderr_to_sync_on_write]
131
+ STDOUT.sync = true
132
+ STDERR.sync = true
121
133
  end
122
134
  end
123
135
 
data/lib/puma/server.rb CHANGED
@@ -84,12 +84,14 @@ module Puma
84
84
 
85
85
  @options = options
86
86
 
87
- @early_hints = options.fetch :early_hints, nil
88
- @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
89
- @min_threads = options.fetch :min_threads, 0
90
- @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
91
- @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
92
- @queue_requests = options.fetch :queue_requests, true
87
+ @early_hints = options.fetch :early_hints, nil
88
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
89
+ @min_threads = options.fetch :min_threads, 0
90
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
91
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
92
+ @queue_requests = options.fetch :queue_requests, true
93
+ @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
94
+ @io_selector_backend = options.fetch :io_selector_backend, :auto
93
95
 
94
96
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
95
97
  @leak_stack_on_error = @options[:environment] ? temp : true
@@ -118,17 +120,13 @@ module Puma
118
120
  # :nodoc:
119
121
  # @version 5.0.0
120
122
  def tcp_cork_supported?
121
- RbConfig::CONFIG['host_os'] =~ /linux/ &&
122
- Socket.const_defined?(:IPPROTO_TCP) &&
123
- Socket.const_defined?(:TCP_CORK)
123
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
124
124
  end
125
125
 
126
126
  # :nodoc:
127
127
  # @version 5.0.0
128
128
  def closed_socket_supported?
129
- RbConfig::CONFIG['host_os'] =~ /linux/ &&
130
- Socket.const_defined?(:IPPROTO_TCP) &&
131
- Socket.const_defined?(:TCP_INFO)
129
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
132
130
  end
133
131
  private :tcp_cork_supported?
134
132
  private :closed_socket_supported?
@@ -136,6 +134,7 @@ module Puma
136
134
 
137
135
  # On Linux, use TCP_CORK to better control how the TCP stack
138
136
  # packetizes our stream. This improves both latency and throughput.
137
+ # socket parameter may be an MiniSSL::Socket, so use to_io
139
138
  #
140
139
  if tcp_cork_supported?
141
140
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
@@ -144,16 +143,18 @@ module Puma
144
143
  # 3 == TCP_CORK
145
144
  # 1/0 == turn on/off
146
145
  def cork_socket(socket)
146
+ skt = socket.to_io
147
147
  begin
148
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
148
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
149
149
  rescue IOError, SystemCallError
150
150
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
151
151
  end
152
152
  end
153
153
 
154
154
  def uncork_socket(socket)
155
+ skt = socket.to_io
155
156
  begin
156
- socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
157
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
157
158
  rescue IOError, SystemCallError
158
159
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
159
160
  end
@@ -218,7 +219,7 @@ module Puma
218
219
  # up in the background to handle requests. Otherwise requests
219
220
  # are handled synchronously.
220
221
  #
221
- def run(background=true)
222
+ def run(background=true, thread_name: 'server')
222
223
  BasicSocket.do_not_reverse_lookup = true
223
224
 
224
225
  @events.fire :state, :booting
@@ -236,7 +237,7 @@ module Puma
236
237
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
237
238
 
238
239
  if @queue_requests
239
- @reactor = Reactor.new(&method(:reactor_wakeup))
240
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
240
241
  @reactor.run
241
242
  end
242
243
 
@@ -254,7 +255,7 @@ module Puma
254
255
 
255
256
  if background
256
257
  @thread = Thread.new do
257
- Puma.set_thread_name "server"
258
+ Puma.set_thread_name thread_name
258
259
  handle_servers
259
260
  end
260
261
  return @thread
@@ -442,11 +443,11 @@ module Puma
442
443
 
443
444
  check_for_more_data = @status == :run
444
445
 
445
- if requests >= MAX_FAST_INLINE
446
+ if requests >= @max_fast_inline
446
447
  # This will mean that reset will only try to use the data it already
447
448
  # has buffered and won't try to read more data. What this means is that
448
449
  # every client, independent of their request speed, gets treated like a slow
449
- # one once every MAX_FAST_INLINE requests.
450
+ # one once every max_fast_inline requests.
450
451
  check_for_more_data = false
451
452
  end
452
453
 
@@ -493,62 +494,6 @@ module Puma
493
494
 
494
495
  # :nocov:
495
496
 
496
- # Given the request +env+ from +client+ and the partial body +body+
497
- # plus a potential Content-Length value +cl+, finish reading
498
- # the body and return it.
499
- #
500
- # If the body is larger than MAX_BODY, a Tempfile object is used
501
- # for the body, otherwise a StringIO is used.
502
- # @deprecated 6.0.0
503
- #
504
- def read_body(env, client, body, cl)
505
- content_length = cl.to_i
506
-
507
- remain = content_length - body.bytesize
508
-
509
- return StringIO.new(body) if remain <= 0
510
-
511
- # Use a Tempfile if there is a lot of data left
512
- if remain > MAX_BODY
513
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
514
- stream.binmode
515
- else
516
- # The body[0,0] trick is to get an empty string in the same
517
- # encoding as body.
518
- stream = StringIO.new body[0,0]
519
- end
520
-
521
- stream.write body
522
-
523
- # Read an odd sized chunk so we can read even sized ones
524
- # after this
525
- chunk = client.readpartial(remain % CHUNK_SIZE)
526
-
527
- # No chunk means a closed socket
528
- unless chunk
529
- stream.close
530
- return nil
531
- end
532
-
533
- remain -= stream.write(chunk)
534
-
535
- # Read the rest of the chunks
536
- while remain > 0
537
- chunk = client.readpartial(CHUNK_SIZE)
538
- unless chunk
539
- stream.close
540
- return nil
541
- end
542
-
543
- remain -= stream.write(chunk)
544
- end
545
-
546
- stream.rewind
547
-
548
- return stream
549
- end
550
- # :nocov:
551
-
552
497
  # Handle various error types thrown by Client I/O operations.
553
498
  def client_error(e, client)
554
499
  # Swallow, do not log