puma 6.1.1 → 6.3.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +76 -1
  3. data/LICENSE +0 -0
  4. data/README.md +39 -4
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +0 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +0 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +0 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +0 -0
  24. data/docs/testing_benchmarks_local_files.md +0 -0
  25. data/docs/testing_test_rackup_ci_files.md +0 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +0 -0
  29. data/ext/puma_http11/http11_parser.c +0 -0
  30. data/ext/puma_http11/http11_parser.h +0 -0
  31. data/ext/puma_http11/http11_parser.java.rl +0 -0
  32. data/ext/puma_http11/http11_parser.rl +0 -0
  33. data/ext/puma_http11/http11_parser_common.rl +0 -0
  34. data/ext/puma_http11/mini_ssl.c +30 -2
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +0 -0
  39. data/ext/puma_http11/puma_http11.c +0 -0
  40. data/lib/puma/app/status.rb +1 -1
  41. data/lib/puma/binder.rb +8 -6
  42. data/lib/puma/cli.rb +1 -1
  43. data/lib/puma/client.rb +18 -10
  44. data/lib/puma/cluster/worker.rb +0 -0
  45. data/lib/puma/cluster/worker_handle.rb +0 -0
  46. data/lib/puma/cluster.rb +0 -0
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +1 -0
  49. data/lib/puma/const.rb +58 -9
  50. data/lib/puma/control_cli.rb +0 -0
  51. data/lib/puma/detect.rb +0 -0
  52. data/lib/puma/dsl.rb +78 -2
  53. data/lib/puma/error_logger.rb +2 -1
  54. data/lib/puma/events.rb +0 -0
  55. data/lib/puma/io_buffer.rb +0 -0
  56. data/lib/puma/jruby_restart.rb +0 -0
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  59. data/lib/puma/launcher.rb +2 -0
  60. data/lib/puma/log_writer.rb +10 -4
  61. data/lib/puma/minissl/context_builder.rb +1 -0
  62. data/lib/puma/minissl.rb +17 -0
  63. data/lib/puma/plugin/systemd.rb +0 -0
  64. data/lib/puma/plugin/tmp_restart.rb +0 -0
  65. data/lib/puma/plugin.rb +0 -0
  66. data/lib/puma/rack/builder.rb +2 -2
  67. data/lib/puma/rack/urlmap.rb +0 -0
  68. data/lib/puma/rack_default.rb +1 -1
  69. data/lib/puma/reactor.rb +16 -7
  70. data/lib/puma/request.rb +64 -48
  71. data/lib/puma/runner.rb +0 -0
  72. data/lib/puma/sd_notify.rb +0 -0
  73. data/lib/puma/server.rb +16 -4
  74. data/lib/puma/single.rb +0 -0
  75. data/lib/puma/state_file.rb +0 -0
  76. data/lib/puma/thread_pool.rb +7 -3
  77. data/lib/puma/util.rb +0 -0
  78. data/lib/puma.rb +0 -0
  79. data/lib/rack/handler/puma.rb +12 -6
  80. data/tools/Dockerfile +0 -0
  81. data/tools/trickletest.rb +0 -0
  82. metadata +2 -2
data/lib/puma/request.rb CHANGED
@@ -77,7 +77,9 @@ module Puma
77
77
  if @early_hints
78
78
  env[EARLY_HINTS] = lambda { |headers|
79
79
  begin
80
- fast_write_str socket, str_early_hints(headers)
80
+ unless (str = str_early_hints headers).empty?
81
+ fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
82
+ end
81
83
  rescue ConnectionError => e
82
84
  @log_writer.debug_error e
83
85
  # noop, if we lost the socket we just won't send the early hints
@@ -93,7 +95,7 @@ module Puma
93
95
  env[RACK_AFTER_REPLY] ||= []
94
96
 
95
97
  begin
96
- if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
98
+ if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
97
99
  status, headers, app_body = @thread_pool.with_force_shutdown do
98
100
  @app.call(env)
99
101
  end
@@ -106,6 +108,7 @@ module Puma
106
108
  # is called
107
109
  res_body = app_body
108
110
 
111
+ # full hijack, app called env['rack.hijack']
109
112
  return :async if client.hijacked
110
113
 
111
114
  status = status.to_i
@@ -169,54 +172,55 @@ module Puma
169
172
  resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
170
173
 
171
174
  close_body = false
175
+ response_hijack = nil
176
+ content_length = resp_info[:content_length]
177
+ keep_alive = resp_info[:keep_alive]
172
178
 
173
- # below converts app_body into body, dependent on app_body's characteristics, and
174
- # resp_info[:content_length] will be set if it can be determined
175
- if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
176
- if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) && array_body.is_a?(Array)
177
- body = array_body
178
- resp_info[:content_length] = body.sum(&:bytesize)
179
- elsif res_body.is_a?(File) && res_body.respond_to?(:size)
180
- body = res_body
181
- resp_info[:content_length] = body.size
182
- elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
179
+ if res_body.respond_to?(:each) && !resp_info[:response_hijack]
180
+ # below converts app_body into body, dependent on app_body's characteristics, and
181
+ # content_length will be set if it can be determined
182
+ if !content_length && !resp_info[:transfer_encoding] && status != 204
183
+ if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
184
+ array_body.is_a?(Array)
185
+ body = array_body.compact
186
+ content_length = body.sum(&:bytesize)
187
+ elsif res_body.is_a?(File) && res_body.respond_to?(:size)
188
+ body = res_body
189
+ content_length = body.size
190
+ elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
191
+ body = File.open fn, 'rb'
192
+ content_length = body.size
193
+ close_body = true
194
+ else
195
+ body = res_body
196
+ end
197
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
183
198
  File.readable?(fn = res_body.to_path)
184
199
  body = File.open fn, 'rb'
185
- resp_info[:content_length] = body.size
200
+ content_length = body.size
186
201
  close_body = true
202
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
203
+ res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
204
+ # Sprockets::Asset
205
+ content_length = res_body.bytesize unless content_length
206
+ if (body_str = res_body.to_hash[:source])
207
+ body = [body_str]
208
+ else # avoid each and use a File object
209
+ body = File.open fn, 'rb'
210
+ close_body = true
211
+ end
187
212
  else
188
213
  body = res_body
189
214
  end
190
- elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
191
- File.readable?(fn = res_body.to_path)
192
- body = File.open fn, 'rb'
193
- resp_info[:content_length] = body.size
194
- close_body = true
195
- elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
196
- res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
197
- # Sprockets::Asset
198
- resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
199
- if res_body.to_hash[:source] # use each to return @source
200
- body = res_body
201
- else # avoid each and use a File object
202
- body = File.open fn, 'rb'
203
- close_body = true
204
- end
205
215
  else
206
- body = res_body
216
+ # partial hijack, from Rack spec:
217
+ # Servers must ignore the body part of the response tuple when the
218
+ # rack.hijack response header is present.
219
+ response_hijack = resp_info[:response_hijack] || res_body
207
220
  end
208
221
 
209
222
  line_ending = LINE_END
210
223
 
211
- content_length = resp_info[:content_length]
212
- keep_alive = resp_info[:keep_alive]
213
-
214
- if res_body && !res_body.respond_to?(:each)
215
- response_hijack = res_body
216
- else
217
- response_hijack = resp_info[:response_hijack]
218
- end
219
-
220
224
  cork_socket socket
221
225
 
222
226
  if resp_info[:no_body]
@@ -244,6 +248,8 @@ module Puma
244
248
 
245
249
  io_buffer << line_ending
246
250
 
251
+ # partial hijack, we write headers, then hand the socket to the app via
252
+ # response_hijack.call
247
253
  if response_hijack
248
254
  fast_write_str socket, io_buffer.read_and_reset
249
255
  uncork_socket socket
@@ -358,16 +364,22 @@ module Puma
358
364
  fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
359
365
  else
360
366
  # for enum bodies
361
- fast_write_str socket, io_buffer.read_and_reset
362
367
  if chunked
368
+ empty_body = true
363
369
  body.each do |part|
364
- next if (byte_size = part.bytesize).zero?
365
- fast_write_str socket, (byte_size.to_s(16) << LINE_END)
366
- fast_write_str socket, part
367
- fast_write_str socket, LINE_END
370
+ next if part.nil? || (byte_size = part.bytesize).zero?
371
+ empty_body = false
372
+ io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
373
+ fast_write_str socket, io_buffer.read_and_reset
374
+ end
375
+ if empty_body
376
+ io_buffer << CLOSE_CHUNKED
377
+ fast_write_str socket, io_buffer.read_and_reset
378
+ else
379
+ fast_write_str socket, CLOSE_CHUNKED
368
380
  end
369
- fast_write_str socket, CLOSE_CHUNKED
370
381
  else
382
+ fast_write_str socket, io_buffer.read_and_reset
371
383
  body.each do |part|
372
384
  next if part.bytesize.zero?
373
385
  fast_write_str socket, part
@@ -408,7 +420,11 @@ module Puma
408
420
 
409
421
  unless env[REQUEST_PATH]
410
422
  # it might be a dumbass full host request header
411
- uri = URI.parse(env[REQUEST_URI])
423
+ uri = begin
424
+ URI.parse(env[REQUEST_URI])
425
+ rescue URI::InvalidURIError
426
+ raise Puma::HttpParserError
427
+ end
412
428
  env[REQUEST_PATH] = uri.path
413
429
 
414
430
  # A nil env value will cause a LintError (and fatal errors elsewhere),
@@ -515,7 +531,7 @@ module Puma
515
531
  # @version 5.0.3
516
532
  #
517
533
  def str_early_hints(headers)
518
- eh_str = +"HTTP/1.1 103 Early Hints\r\n"
534
+ eh_str = +""
519
535
  headers.each_pair do |k, vs|
520
536
  next if illegal_header_key?(k)
521
537
 
@@ -524,11 +540,11 @@ module Puma
524
540
  next if illegal_header_value?(v)
525
541
  eh_str << "#{k}: #{v}\r\n"
526
542
  end
527
- else
543
+ elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
528
544
  eh_str << "#{k}: #{vs}\r\n"
529
545
  end
530
546
  end
531
- "#{eh_str}\r\n".freeze
547
+ eh_str.freeze
532
548
  end
533
549
  private :str_early_hints
534
550
 
data/lib/puma/runner.rb CHANGED
File without changes
File without changes
data/lib/puma/server.rb CHANGED
@@ -51,7 +51,7 @@ module Puma
51
51
  def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
52
52
  :add_unix_listener, :connected_ports
53
53
 
54
- ThreadLocalKey = :puma_server
54
+ THREAD_LOCAL_KEY = :puma_server
55
55
 
56
56
  # Create a server for the rack app +app+.
57
57
  #
@@ -97,6 +97,18 @@ module Puma
97
97
  @io_selector_backend = @options[:io_selector_backend]
98
98
  @http_content_length_limit = @options[:http_content_length_limit]
99
99
 
100
+ # make this a hash, since we prefer `key?` over `include?`
101
+ @supported_http_methods =
102
+ if @options[:supported_http_methods] == :any
103
+ :any
104
+ else
105
+ if (ary = @options[:supported_http_methods])
106
+ ary
107
+ else
108
+ SUPPORTED_HTTP_METHODS
109
+ end.sort.product([nil]).to_h.freeze
110
+ end
111
+
100
112
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
101
113
  @leak_stack_on_error = @options[:environment] ? temp : true
102
114
 
@@ -118,7 +130,7 @@ module Puma
118
130
  class << self
119
131
  # @!attribute [r] current
120
132
  def current
121
- Thread.current[ThreadLocalKey]
133
+ Thread.current[THREAD_LOCAL_KEY]
122
134
  end
123
135
 
124
136
  # :nodoc:
@@ -404,7 +416,7 @@ module Puma
404
416
  # Return true if one or more requests were processed.
405
417
  def process_client(client)
406
418
  # Advertise this server into the thread
407
- Thread.current[ThreadLocalKey] = self
419
+ Thread.current[THREAD_LOCAL_KEY] = self
408
420
 
409
421
  clean_thread_locals = @options[:clean_thread_locals]
410
422
  close_socket = true
@@ -566,7 +578,7 @@ module Puma
566
578
 
567
579
  def notify_safely(message)
568
580
  @notify << message
569
- rescue IOError, NoMethodError, Errno::EPIPE
581
+ rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
570
582
  # The server, in another thread, is shutting down
571
583
  Puma::Util.purge_interrupt_queue
572
584
  rescue RuntimeError => e
data/lib/puma/single.rb CHANGED
File without changes
File without changes
@@ -44,6 +44,10 @@ module Puma
44
44
  @name = name
45
45
  @min = Integer(options[:min_threads])
46
46
  @max = Integer(options[:max_threads])
47
+ # Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
48
+ # to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
49
+ # makes stubbing constants difficult.
50
+ @shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
47
51
  @block = block
48
52
  @out_of_band = options[:out_of_band]
49
53
  @clean_thread_locals = options[:clean_thread_locals]
@@ -344,8 +348,8 @@ module Puma
344
348
 
345
349
  # Tell all threads in the pool to exit and wait for them to finish.
346
350
  # Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
347
- # Next, wait an extra +grace+ seconds then force-kill remaining threads.
348
- # Finally, wait +kill_grace+ seconds for remaining threads to exit.
351
+ # Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
352
+ # threads. Finally, wait 1 second for remaining threads to exit.
349
353
  #
350
354
  def shutdown(timeout=-1)
351
355
  threads = with_mutex do
@@ -382,7 +386,7 @@ module Puma
382
386
  t.raise ForceShutdown if t[:with_force_shutdown]
383
387
  end
384
388
  end
385
- join.call(SHUTDOWN_GRACE_TIME)
389
+ join.call(@shutdown_grace_time)
386
390
 
387
391
  # If threads are _still_ running, forcefully kill them and wait to finish.
388
392
  threads.each(&:kill)
data/lib/puma/util.rb CHANGED
File without changes
data/lib/puma.rb CHANGED
File without changes
@@ -27,10 +27,16 @@ module Puma
27
27
  end
28
28
  end
29
29
 
30
- conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
30
+ @events = options[:events] || ::Puma::Events.new
31
+
32
+ conf = ::Puma::Configuration.new(options, default_options.merge({events: @events})) do |user_config, file_config, default_config|
31
33
  if options.delete(:Verbose)
32
- require 'rack/common_logger'
33
- app = Rack::CommonLogger.new(app, STDOUT)
34
+ begin
35
+ require 'rack/commonlogger' # Rack 1.x
36
+ rescue LoadError
37
+ require 'rack/common_logger' # Rack 2 and later
38
+ end
39
+ app = ::Rack::CommonLogger.new(app, STDOUT)
34
40
  end
35
41
 
36
42
  if options[:environment]
@@ -59,11 +65,11 @@ module Puma
59
65
  end
60
66
 
61
67
  def run(app, **options)
62
- conf = self.config(app, options)
68
+ conf = self.config(app, options)
63
69
 
64
70
  log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
65
71
 
66
- launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)
72
+ launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer, events: @events)
67
73
 
68
74
  yield launcher if block_given?
69
75
  begin
@@ -121,7 +127,7 @@ if Object.const_defined? :Rackup
121
127
  end
122
128
  end
123
129
  else
124
- do_register = Object.const_defined?(:Rack) && Rack::RELEASE < '3'
130
+ do_register = Object.const_defined?(:Rack) && Rack.release < '3'
125
131
  module Rack
126
132
  module Handler
127
133
  module Puma
data/tools/Dockerfile CHANGED
File without changes
data/tools/trickletest.rb CHANGED
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.1
4
+ version: 6.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
@@ -145,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  requirements: []
148
- rubygems_version: 3.3.20
148
+ rubygems_version: 3.4.12
149
149
  signing_key:
150
150
  specification_version: 4
151
151
  summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for