puma 6.0.2 → 6.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83bb232727e7777f406dcacc150db80426972c1e65635f1d81d771fee3585060
4
- data.tar.gz: 16453cd88384851586d3ff6bc274393b44a6a078eaec9932cac795009fc342fb
3
+ metadata.gz: 1df130ccb43143b23de48c5dc2f0890165e078eba4f09432b0a73411b5b54a94
4
+ data.tar.gz: e562fc40625877488fd13719e9620f2401394a33a74bee46e5a4731becfab6ae
5
5
  SHA512:
6
- metadata.gz: ce357b7686a54c2d0e820d70f24bb427803ba9e7aa24a8b0511624dc4ef850e937b9261575462d6a20a0c69f73d1fefa939a242d8e3e5e9577fdd4e45a862af2
7
- data.tar.gz: 3d9fda444a65d5917872261ff4dff8f3e156aec8a1d06d72cc19201da8f820b0262baac73665bb062355966a8d9415ed5b92d01f4c8ad6e312737141c657e823
6
+ metadata.gz: e436db46a9761b73dc370a38e1e59a0e2ac1e385ecf578fc631fc4671329b9cbcfd634e9bed73e17657c13eaf53387a55726d9c986739f6f9ccdb4223fea026e
7
+ data.tar.gz: db4526766e567440a95129831ef30cea038a98e9d613dc76308c96d0d81913d4e15fbb9c1c8d111ce395af0d908268316cb65c04511dbfbd8361fab31a62634a
data/History.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 6.1.0 / 2022-02-12
2
+
3
+ * Features
4
+ * WebSocket support via partial hijack ([#3058], [#3007])
5
+ * Add built-in systemd notify support ([#3011])
6
+ * Periodically send status to systemd ([#3006], [#2604])
7
+ * Introduce the ability to return 413: payload too large for requests ([#3040])
8
+ * Log loaded extensions when `PUMA_DEBUG` is set ([#3036], [#3020])
9
+
10
+ * Bugfixes
11
+ * Fix issue with rack 3 compatibility re: rackup ([#3061], [#3057])
12
+ * Allow setting TCP low_latency with SSL listener ([#3065])
13
+
14
+ * Performance
15
+ * Reduce memory usage for large file uploads ([#3062])
16
+
1
17
  ## 6.0.2 / 2023-01-01
2
18
 
3
19
  * Refactor
@@ -1932,6 +1948,18 @@ be added back in a future date when a java Puma::MiniSSL is added.
1932
1948
  * Bugfixes
1933
1949
  * Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
1934
1950
 
1951
+ [#3058]:https://github.com/puma/puma/pull/3058 "PR by @dentarg, merged 2023-01-29"
1952
+ [#3007]:https://github.com/puma/puma/issues/3007 "Issue by @MSP-Greg, closed 2023-01-29"
1953
+ [#3011]:https://github.com/puma/puma/pull/3011 "PR by @joaomarcos96, merged 2023-01-03"
1954
+ [#3006]:https://github.com/puma/puma/pull/3006 "PR by @QWYNG, merged 2023-02-09"
1955
+ [#2604]:https://github.com/puma/puma/issues/2604 "Issue by @dgoetz, closed 2023-02-09"
1956
+ [#3040]:https://github.com/puma/puma/pull/3040 "PR by @shayonj, merged 2023-01-02"
1957
+ [#3036]:https://github.com/puma/puma/pull/3036 "PR by @MSP-Greg, merged 2023-01-13"
1958
+ [#3020]:https://github.com/puma/puma/issues/3020 "Issue by @dentarg, closed 2023-01-13"
1959
+ [#3061]:https://github.com/puma/puma/pull/3061 "PR by @MSP-Greg, merged 2023-02-12"
1960
+ [#3057]:https://github.com/puma/puma/issues/3057 "Issue by @mmarvb8h, closed 2023-02-12"
1961
+ [#3065]:https://github.com/puma/puma/pull/3065 "PR by @MSP-Greg, merged 2023-02-11"
1962
+ [#3062]:https://github.com/puma/puma/pull/3062 "PR by @willkoehler, merged 2023-01-29"
1935
1963
  [#3035]:https://github.com/puma/puma/pull/3035 "PR by @MSP-Greg, merged 2022-12-24"
1936
1964
  [#3033]:https://github.com/puma/puma/issues/3033 "Issue by @jules-w2, closed 2022-12-24"
1937
1965
  [#3016]:https://github.com/puma/puma/pull/3016 "PR by @MSP-Greg, merged 2022-12-24"
data/README.md CHANGED
@@ -365,6 +365,7 @@ Community guides:
365
365
  * [puma-metrics](https://github.com/harmjanblok/puma-metrics) — export Puma metrics to Prometheus
366
366
  * [puma-plugin-statsd](https://github.com/yob/puma-plugin-statsd) — send Puma metrics to statsd
367
367
  * [puma-plugin-systemd](https://github.com/sj26/puma-plugin-systemd) — deeper integration with systemd for notify, status and watchdog
368
+ * [puma-plugin-telemetry](https://github.com/babbel/puma-plugin-telemetry) - telemetry plugin for Puma offering various targets to publish
368
369
 
369
370
  ### Monitoring
370
371
 
data/docs/systemd.md CHANGED
@@ -24,8 +24,7 @@ After=network.target
24
24
 
25
25
  [Service]
26
26
  # Puma supports systemd's `Type=notify` and watchdog service
27
- # monitoring, if the [sd_notify](https://github.com/agis/ruby-sdnotify) gem is installed,
28
- # as of Puma 5.1 or later.
27
+ # monitoring, as of Puma 5.1 or later.
29
28
  # On earlier versions of Puma or JRuby, change this to `Type=simple` and remove
30
29
  # the `WatchdogSec` line.
31
30
  Type=notify
data/lib/puma/binder.rb CHANGED
@@ -158,10 +158,10 @@ module Puma
158
158
  ios_len = @ios.length
159
159
  params = Util.parse_query uri.query
160
160
 
161
- opt = params.key?('low_latency') && params['low_latency'] != 'false'
161
+ low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
162
162
  backlog = params.fetch('backlog', 1024).to_i
163
163
 
164
- io = add_tcp_listener uri.host, uri.port, opt, backlog
164
+ io = add_tcp_listener uri.host, uri.port, low_latency, backlog
165
165
 
166
166
  @ios[ios_len..-1].each do |i|
167
167
  addr = loc_addr_str i
@@ -251,7 +251,8 @@ module Puma
251
251
  else
252
252
  ios_len = @ios.length
253
253
  backlog = params.fetch('backlog', 1024).to_i
254
- io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
254
+ low_latency = params['low_latency'] != 'false'
255
+ io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
255
256
 
256
257
  @ios[ios_len..-1].each do |i|
257
258
  addr = loc_addr_str i
data/lib/puma/client.rb CHANGED
@@ -86,6 +86,9 @@ module Puma
86
86
  @requests_served = 0
87
87
  @hijacked = false
88
88
 
89
+ @http_content_length_limit = nil
90
+ @http_content_length_limit_exceeded = false
91
+
89
92
  @peerip = nil
90
93
  @peer_family = nil
91
94
  @listener = nil
@@ -95,12 +98,14 @@ module Puma
95
98
  @body_remain = 0
96
99
 
97
100
  @in_last_chunk = false
101
+
102
+ @read_buffer = +""
98
103
  end
99
104
 
100
105
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
101
- :tempfile, :io_buffer
106
+ :tempfile, :io_buffer, :http_content_length_limit_exceeded
102
107
 
103
- attr_writer :peerip
108
+ attr_writer :peerip, :http_content_length_limit
104
109
 
105
110
  attr_accessor :remote_addr_header, :listener
106
111
 
@@ -151,6 +156,7 @@ module Puma
151
156
  @body_remain = 0
152
157
  @peerip = nil if @remote_addr_header
153
158
  @in_last_chunk = false
159
+ @http_content_length_limit_exceeded = false
154
160
 
155
161
  if @buffer
156
162
  return false unless try_to_parse_proxy_protocol
@@ -210,6 +216,17 @@ module Puma
210
216
  end
211
217
 
212
218
  def try_to_finish
219
+ if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
220
+ @http_content_length_limit_exceeded = true
221
+ end
222
+
223
+ if @http_content_length_limit_exceeded
224
+ @buffer = nil
225
+ @body = EmptyBody
226
+ set_ready
227
+ return true
228
+ end
229
+
213
230
  return read_body if in_data_phase
214
231
 
215
232
  begin
@@ -239,6 +256,10 @@ module Puma
239
256
 
240
257
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
241
258
 
259
+ if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
260
+ @http_content_length_limit_exceeded = true
261
+ end
262
+
242
263
  if @parser.finished?
243
264
  return setup_body
244
265
  elsif @parsed_bytes >= MAX_HEADER
@@ -414,7 +435,7 @@ module Puma
414
435
  end
415
436
 
416
437
  begin
417
- chunk = @io.read_nonblock(want)
438
+ chunk = @io.read_nonblock(want, @read_buffer)
418
439
  rescue IO::WaitReadable
419
440
  return false
420
441
  rescue SystemCallError, IOError
@@ -446,7 +467,7 @@ module Puma
446
467
  def read_chunked_body
447
468
  while true
448
469
  begin
449
- chunk = @io.read_nonblock(4096)
470
+ chunk = @io.read_nonblock(4096, @read_buffer)
450
471
  rescue IO::WaitReadable
451
472
  return false
452
473
  rescue SystemCallError, IOError
@@ -594,5 +615,9 @@ module Puma
594
615
  @requests_served += 1
595
616
  @ready = true
596
617
  end
618
+
619
+ def above_http_content_limit(value)
620
+ @http_content_length_limit&.< value
621
+ end
597
622
  end
598
623
  end
@@ -115,6 +115,11 @@ module Puma
115
115
 
116
116
  while restart_server.pop
117
117
  server_thread = server.run
118
+
119
+ if @log_writer.debug? && index == 0
120
+ debug_loaded_extensions "Loaded Extensions - worker 0:"
121
+ end
122
+
118
123
  stat_thread ||= Thread.new(@worker_write) do |io|
119
124
  Puma.set_thread_name "stat pld"
120
125
  base_payload = "p#{Process.pid}"
data/lib/puma/cluster.rb CHANGED
@@ -467,6 +467,7 @@ module Puma
467
467
  @events.fire(:ping!, w)
468
468
  if !booted && @workers.none? {|worker| worker.last_status.empty?}
469
469
  @events.fire_on_booted!
470
+ debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
470
471
  booted = true
471
472
  end
472
473
  end
@@ -476,6 +477,7 @@ module Puma
476
477
  end
477
478
  if in_phased_restart && workers_not_booted.zero?
478
479
  @events.fire_on_booted!
480
+ debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
479
481
  in_phased_restart = false
480
482
  end
481
483
 
@@ -167,6 +167,7 @@ module Puma
167
167
  worker_shutdown_timeout: 30,
168
168
  worker_timeout: 60,
169
169
  workers: 0,
170
+ http_content_length_limit: nil
170
171
  }
171
172
 
172
173
  def initialize(user_options={}, default_options = {}, &block)
data/lib/puma/const.rb CHANGED
@@ -99,8 +99,8 @@ module Puma
99
99
  # too taxing on performance.
100
100
  module Const
101
101
 
102
- PUMA_VERSION = VERSION = "6.0.2"
103
- CODE_NAME = "Sunflower"
102
+ PUMA_VERSION = VERSION = "6.1.0"
103
+ CODE_NAME = "The Way Up"
104
104
 
105
105
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
106
106
 
data/lib/puma/dsl.rb CHANGED
@@ -65,6 +65,7 @@ module Puma
65
65
 
66
66
  ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
67
67
 
68
+ low_latency_str = opts.key?(:low_latency) ? "&low_latency=#{opts[:low_latency]}" : ''
68
69
  backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
69
70
 
70
71
  if defined?(JRUBY_VERSION)
@@ -114,7 +115,7 @@ module Puma
114
115
  end
115
116
 
116
117
  "ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
117
- "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
118
+ "#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}#{low_latency_str}"
118
119
  end
119
120
  end
120
121
 
@@ -1022,6 +1023,19 @@ module Puma
1022
1023
  @options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
1023
1024
  end
1024
1025
 
1026
+ # Specify how big the request payload should be, in bytes.
1027
+ # This limit is compared against Content-Length HTTP header.
1028
+ # If the payload size (CONTENT_LENGTH) is larger than http_content_length_limit,
1029
+ # HTTP 413 status code is returned.
1030
+ #
1031
+ # When no Content-Length http header is present, it is compared against the
1032
+ # size of the body of the request.
1033
+ #
1034
+ # The default value for http_content_length_limit is nil.
1035
+ def http_content_length_limit(limit)
1036
+ @options[:http_content_length_limit] = limit
1037
+ end
1038
+
1025
1039
  private
1026
1040
 
1027
1041
  # To avoid adding cert_pem and key_pem as URI params, we store them on the
data/lib/puma/launcher.rb CHANGED
@@ -59,6 +59,10 @@ module Puma
59
59
 
60
60
  @environment = conf.environment
61
61
 
62
+ if ENV["NOTIFY_SOCKET"]
63
+ @config.plugins.create('systemd')
64
+ end
65
+
62
66
  if @config.options[:bind_to_activated_sockets]
63
67
  @config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
64
68
  @config.options[:binds],
@@ -180,7 +184,6 @@ module Puma
180
184
 
181
185
  setup_signals
182
186
  set_process_title
183
- integrate_with_systemd
184
187
 
185
188
  # This blocks until the server is stopped
186
189
  @runner.run
@@ -311,27 +314,6 @@ module Puma
311
314
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
312
315
  end
313
316
 
314
- # Puma's systemd integration allows Puma to inform systemd:
315
- # 1. when it has successfully started
316
- # 2. when it is starting shutdown
317
- # 3. periodically for a liveness check with a watchdog thread
318
- def integrate_with_systemd
319
- return unless ENV["NOTIFY_SOCKET"]
320
-
321
- begin
322
- require_relative 'systemd'
323
- rescue LoadError
324
- log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
325
- return
326
- end
327
-
328
- log "* Enabling systemd notification integration"
329
-
330
- systemd = Systemd.new(@log_writer, @events)
331
- systemd.hook_events
332
- systemd.start_watchdog
333
- end
334
-
335
317
  def log(str)
336
318
  @log_writer.log(str)
337
319
  end
@@ -80,6 +80,10 @@ module Puma
80
80
  end
81
81
  private :internal_write
82
82
 
83
+ def debug?
84
+ @debug
85
+ end
86
+
83
87
  def debug(str)
84
88
  log("% #{str}") if @debug
85
89
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../plugin'
4
+
5
+ # Puma's systemd integration allows Puma to inform systemd:
6
+ # 1. when it has successfully started
7
+ # 2. when it is starting shutdown
8
+ # 3. periodically for a liveness check with a watchdog thread
9
+ # 4. periodically set the status
10
+ Puma::Plugin.create do
11
+ def start(launcher)
12
+ require_relative '../sd_notify'
13
+
14
+ launcher.log_writer.log "* Enabling systemd notification integration"
15
+
16
+ # hook_events
17
+ launcher.events.on_booted { Puma::SdNotify.ready }
18
+ launcher.events.on_stopped { Puma::SdNotify.stopping }
19
+ launcher.events.on_restart { Puma::SdNotify.reloading }
20
+
21
+ # start watchdog
22
+ if Puma::SdNotify.watchdog?
23
+ ping_f = watchdog_sleep_time
24
+
25
+ in_background do
26
+ launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
27
+ loop do
28
+ sleep ping_f
29
+ Puma::SdNotify.watchdog
30
+ end
31
+ end
32
+ end
33
+
34
+ # start status loop
35
+ instance = self
36
+ sleep_time = 1.0
37
+ in_background do
38
+ launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
39
+
40
+ loop do
41
+ sleep sleep_time
42
+ # TODO: error handling?
43
+ Puma::SdNotify.status(instance.status)
44
+ end
45
+ end
46
+ end
47
+
48
+ def status
49
+ if clustered?
50
+ messages = stats[:worker_status].map do |worker|
51
+ common_message(worker[:last_status])
52
+ end.join(',')
53
+
54
+ "Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
55
+ else
56
+ "Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def watchdog_sleep_time
63
+ usec = Integer(ENV["WATCHDOG_USEC"])
64
+
65
+ sec_f = usec / 1_000_000.0
66
+ # "It is recommended that a daemon sends a keep-alive notification message
67
+ # to the service manager every half of the time returned here."
68
+ sec_f / 2
69
+ end
70
+
71
+ def stats
72
+ Puma.stats_hash
73
+ end
74
+
75
+ def clustered?
76
+ stats.has_key?(:workers)
77
+ end
78
+
79
+ def workers
80
+ stats.fetch(:workers, 1)
81
+ end
82
+
83
+ def booted_workers
84
+ stats.fetch(:booted_workers, 1)
85
+ end
86
+
87
+ def common_message(stats)
88
+ "{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
89
+ end
90
+ end
@@ -2,8 +2,23 @@
2
2
 
3
3
  require_relative '../rack/handler/puma'
4
4
 
5
- module Rack::Handler
6
- def self.default(options = {})
7
- Rack::Handler::Puma
5
+ # rackup was removed in Rack 3, it is now a separate gem
6
+ if Object.const_defined? :Rackup
7
+ module Rackup
8
+ module Handler
9
+ def self.default(options = {})
10
+ ::Rackup::Handler::Puma
11
+ end
12
+ end
8
13
  end
14
+ elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
15
+ module Rack
16
+ module Handler
17
+ def self.default(options = {})
18
+ ::Rack::Handler::Puma
19
+ end
20
+ end
21
+ end
22
+ else
23
+ raise "Rack 3 must be used with the Rackup gem"
9
24
  end
data/lib/puma/request.rb CHANGED
@@ -53,8 +53,13 @@ module Puma
53
53
  socket = client.io # io may be a MiniSSL::Socket
54
54
  app_body = nil
55
55
 
56
+
56
57
  return false if closed_socket?(socket)
57
58
 
59
+ if client.http_content_length_limit_exceeded
60
+ return prepare_response(413, {}, ["Payload Too Large"], requests, client)
61
+ end
62
+
58
63
  normalize_env env, client
59
64
 
60
65
  env[PUMA_SOCKET] = socket
@@ -168,7 +173,7 @@ module Puma
168
173
  # below converts app_body into body, dependent on app_body's characteristics, and
169
174
  # resp_info[:content_length] will be set if it can be determined
170
175
  if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
171
- if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary)
176
+ if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) && array_body.is_a?(Array)
172
177
  body = array_body
173
178
  resp_info[:content_length] = body.sum(&:bytesize)
174
179
  elsif res_body.is_a?(File) && res_body.respond_to?(:size)
@@ -215,27 +220,33 @@ module Puma
215
220
  cork_socket socket
216
221
 
217
222
  if resp_info[:no_body]
218
- if content_length and status != 204
223
+ # 101 (Switching Protocols) doesn't return here or have content_length,
224
+ # it should be using `response_hijack`
225
+ unless status == 101
226
+ if content_length && status != 204
227
+ io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
228
+ end
229
+
230
+ io_buffer << LINE_END
231
+ fast_write_str socket, io_buffer.read_and_reset
232
+ socket.flush
233
+ return keep_alive
234
+ end
235
+ else
236
+ if content_length
219
237
  io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
238
+ chunked = false
239
+ elsif !response_hijack && resp_info[:allow_chunked]
240
+ io_buffer << TRANSFER_ENCODING_CHUNKED
241
+ chunked = true
220
242
  end
221
-
222
- io_buffer << LINE_END
223
- fast_write_str socket, io_buffer.read_and_reset
224
- socket.flush
225
- return keep_alive
226
- end
227
- if content_length
228
- io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
229
- chunked = false
230
- elsif !response_hijack and resp_info[:allow_chunked]
231
- io_buffer << TRANSFER_ENCODING_CHUNKED
232
- chunked = true
233
243
  end
234
244
 
235
245
  io_buffer << line_ending
236
246
 
237
247
  if response_hijack
238
248
  fast_write_str socket, io_buffer.read_and_reset
249
+ uncork_socket socket
239
250
  response_hijack.call socket
240
251
  return :async
241
252
  end
@@ -295,8 +306,8 @@ module Puma
295
306
  def fast_write_response(socket, body, io_buffer, chunked, content_length)
296
307
  if body.is_a?(::File) && body.respond_to?(:read)
297
308
  if chunked # would this ever happen?
298
- while part = body.read(BODY_LEN_MAX)
299
- io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
309
+ while chunk = body.read(BODY_LEN_MAX)
310
+ io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
300
311
  end
301
312
  fast_write_str socket, CLOSE_CHUNKED
302
313
  else
@@ -476,7 +487,7 @@ module Puma
476
487
  to_add = nil
477
488
 
478
489
  env.each do |k,v|
479
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
490
+ if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
480
491
  if to_delete
481
492
  to_delete << k
482
493
  else
data/lib/puma/runner.rb CHANGED
@@ -198,5 +198,12 @@ module Puma
198
198
  }
199
199
  }
200
200
  end
201
+
202
+ # this method call should always be guarded by `@log_writer.debug?`
203
+ def debug_loaded_extensions(str)
204
+ @log_writer.debug "────────────────────────────────── #{str}"
205
+ re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
206
+ $LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
207
+ end
201
208
  end
202
209
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Puma
6
+ # The MIT License
7
+ #
8
+ # Copyright (c) 2017-2022 Agis Anastasopoulos
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of
11
+ # this software and associated documentation files (the "Software"), to deal in
12
+ # the Software without restriction, including without limitation the rights to
13
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
14
+ # the Software, and to permit persons to whom the Software is furnished to do so,
15
+ # subject to the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included in all
18
+ # copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
22
+ # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
23
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24
+ # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #
27
+ # This is a copy of https://github.com/agis/ruby-sdnotify as of commit cca575c
28
+ # The only changes made was "rehoming" it within the Puma module to avoid
29
+ # namespace collisions and applying standard's code formatting style.
30
+ #
31
+ # SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
32
+ # notify systemd about state changes. Methods of this package are no-op on
33
+ # non-systemd systems (eg. Darwin).
34
+ #
35
+ # The API maps closely to the original implementation of sd_notify(3),
36
+ # therefore be sure to check the official man pages prior to using SdNotify.
37
+ #
38
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
39
+ module SdNotify
40
+ # Exception raised when there's an error writing to the notification socket
41
+ class NotifyError < RuntimeError; end
42
+
43
+ READY = "READY=1"
44
+ RELOADING = "RELOADING=1"
45
+ STOPPING = "STOPPING=1"
46
+ STATUS = "STATUS="
47
+ ERRNO = "ERRNO="
48
+ MAINPID = "MAINPID="
49
+ WATCHDOG = "WATCHDOG=1"
50
+ FDSTORE = "FDSTORE=1"
51
+
52
+ def self.ready(unset_env=false)
53
+ notify(READY, unset_env)
54
+ end
55
+
56
+ def self.reloading(unset_env=false)
57
+ notify(RELOADING, unset_env)
58
+ end
59
+
60
+ def self.stopping(unset_env=false)
61
+ notify(STOPPING, unset_env)
62
+ end
63
+
64
+ # @param status [String] a custom status string that describes the current
65
+ # state of the service
66
+ def self.status(status, unset_env=false)
67
+ notify("#{STATUS}#{status}", unset_env)
68
+ end
69
+
70
+ # @param errno [Integer]
71
+ def self.errno(errno, unset_env=false)
72
+ notify("#{ERRNO}#{errno}", unset_env)
73
+ end
74
+
75
+ # @param pid [Integer]
76
+ def self.mainpid(pid, unset_env=false)
77
+ notify("#{MAINPID}#{pid}", unset_env)
78
+ end
79
+
80
+ def self.watchdog(unset_env=false)
81
+ notify(WATCHDOG, unset_env)
82
+ end
83
+
84
+ def self.fdstore(unset_env=false)
85
+ notify(FDSTORE, unset_env)
86
+ end
87
+
88
+ # @param [Boolean] true if the service manager expects watchdog keep-alive
89
+ # notification messages to be sent from this process.
90
+ #
91
+ # If the $WATCHDOG_USEC environment variable is set,
92
+ # and the $WATCHDOG_PID variable is unset or set to the PID of the current
93
+ # process
94
+ #
95
+ # @note Unlike sd_watchdog_enabled(3), this method does not mutate the
96
+ # environment.
97
+ def self.watchdog?
98
+ wd_usec = ENV["WATCHDOG_USEC"]
99
+ wd_pid = ENV["WATCHDOG_PID"]
100
+
101
+ return false if !wd_usec
102
+
103
+ begin
104
+ wd_usec = Integer(wd_usec)
105
+ rescue
106
+ return false
107
+ end
108
+
109
+ return false if wd_usec <= 0
110
+ return true if !wd_pid || wd_pid == $$.to_s
111
+
112
+ false
113
+ end
114
+
115
+ # Notify systemd with the provided state, via the notification socket, if
116
+ # any.
117
+ #
118
+ # Generally this method will be used indirectly through the other methods
119
+ # of the library.
120
+ #
121
+ # @param state [String]
122
+ # @param unset_env [Boolean]
123
+ #
124
+ # @return [Fixnum, nil] the number of bytes written to the notification
125
+ # socket or nil if there was no socket to report to (eg. the program wasn't
126
+ # started by systemd)
127
+ #
128
+ # @raise [NotifyError] if there was an error communicating with the systemd
129
+ # socket
130
+ #
131
+ # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
132
+ def self.notify(state, unset_env=false)
133
+ sock = ENV["NOTIFY_SOCKET"]
134
+
135
+ return nil if !sock
136
+
137
+ ENV.delete("NOTIFY_SOCKET") if unset_env
138
+
139
+ begin
140
+ Addrinfo.unix(sock, :DGRAM).connect do |s|
141
+ s.close_on_exec = true
142
+ s.write(state)
143
+ end
144
+ rescue StandardError => e
145
+ raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
146
+ end
147
+ end
148
+ end
149
+ end
data/lib/puma/server.rb CHANGED
@@ -95,6 +95,7 @@ module Puma
95
95
  @queue_requests = @options[:queue_requests]
96
96
  @max_fast_inline = @options[:max_fast_inline]
97
97
  @io_selector_backend = @options[:io_selector_backend]
98
+ @http_content_length_limit = @options[:http_content_length_limit]
98
99
 
99
100
  temp = !!(@options[:environment] =~ /\A(development|test)\z/)
100
101
  @leak_stack_on_error = @options[:environment] ? temp : true
@@ -334,6 +335,7 @@ module Puma
334
335
  drain += 1 if shutting_down?
335
336
  pool << Client.new(io, @binder.env(sock)).tap { |c|
336
337
  c.listener = sock
338
+ c.http_content_length_limit = @http_content_length_limit
337
339
  c.send(addr_send_name, addr_value) if addr_value
338
340
  }
339
341
  end
data/lib/puma/single.rb CHANGED
@@ -57,6 +57,8 @@ module Puma
57
57
 
58
58
  @events.fire_on_booted!
59
59
 
60
+ debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
61
+
60
62
  begin
61
63
  server_thread.join
62
64
  rescue Interrupt
@@ -1,114 +1,136 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack/handler'
4
-
5
- module Rack
6
- module Handler
7
- module Puma
8
- DEFAULT_OPTIONS = {
9
- :Verbose => false,
10
- :Silent => false
11
- }
12
-
13
- def self.config(app, options = {})
14
- require_relative '../../puma'
15
- require_relative '../../puma/configuration'
16
- require_relative '../../puma/log_writer'
17
- require_relative '../../puma/launcher'
18
-
19
- default_options = DEFAULT_OPTIONS.dup
20
-
21
- # Libraries pass in values such as :Port and there is no way to determine
22
- # if it is a default provided by the library or a special value provided
23
- # by the user. A special key `user_supplied_options` can be passed. This
24
- # contains an array of all explicitly defined user options. We then
25
- # know that all other values are defaults
26
- if user_supplied_options = options.delete(:user_supplied_options)
27
- (options.keys - user_supplied_options).each do |k|
28
- default_options[k] = options.delete(k)
29
- end
3
+ # This module is used as an 'include' file in code at bottom of file
4
+ module Puma
5
+ module RackHandler
6
+ DEFAULT_OPTIONS = {
7
+ :Verbose => false,
8
+ :Silent => false
9
+ }
10
+
11
+ def config(app, options = {})
12
+ require_relative '../../puma'
13
+ require_relative '../../puma/configuration'
14
+ require_relative '../../puma/log_writer'
15
+ require_relative '../../puma/launcher'
16
+
17
+ default_options = DEFAULT_OPTIONS.dup
18
+
19
+ # Libraries pass in values such as :Port and there is no way to determine
20
+ # if it is a default provided by the library or a special value provided
21
+ # by the user. A special key `user_supplied_options` can be passed. This
22
+ # contains an array of all explicitly defined user options. We then
23
+ # know that all other values are defaults
24
+ if user_supplied_options = options.delete(:user_supplied_options)
25
+ (options.keys - user_supplied_options).each do |k|
26
+ default_options[k] = options.delete(k)
30
27
  end
28
+ end
31
29
 
32
- conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
33
- if options.delete(:Verbose)
34
- require 'rack/common_logger'
35
- app = Rack::CommonLogger.new(app, STDOUT)
36
- end
37
-
38
- if options[:environment]
39
- user_config.environment options[:environment]
40
- end
41
-
42
- if options[:Threads]
43
- min, max = options.delete(:Threads).split(':', 2)
44
- user_config.threads min, max
45
- end
46
-
47
- if options[:Host] || options[:Port]
48
- host = options[:Host] || default_options[:Host]
49
- port = options[:Port] || default_options[:Port]
50
- self.set_host_port_to_config(host, port, user_config)
51
- end
52
-
53
- if default_options[:Host]
54
- file_config.set_default_host(default_options[:Host])
55
- end
56
- self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
57
-
58
- user_config.app app
30
+ conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
31
+ if options.delete(:Verbose)
32
+ require 'rack/common_logger'
33
+ app = Rack::CommonLogger.new(app, STDOUT)
59
34
  end
60
- conf
61
- end
62
35
 
63
- def self.run(app, **options)
64
- conf = self.config(app, options)
36
+ if options[:environment]
37
+ user_config.environment options[:environment]
38
+ end
65
39
 
66
- log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
40
+ if options[:Threads]
41
+ min, max = options.delete(:Threads).split(':', 2)
42
+ user_config.threads min, max
43
+ end
67
44
 
68
- launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)
45
+ if options[:Host] || options[:Port]
46
+ host = options[:Host] || default_options[:Host]
47
+ port = options[:Port] || default_options[:Port]
48
+ self.set_host_port_to_config(host, port, user_config)
49
+ end
69
50
 
70
- yield launcher if block_given?
71
- begin
72
- launcher.run
73
- rescue Interrupt
74
- puts "* Gracefully stopping, waiting for requests to finish"
75
- launcher.stop
76
- puts "* Goodbye!"
51
+ if default_options[:Host]
52
+ file_config.set_default_host(default_options[:Host])
77
53
  end
54
+ self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
55
+
56
+ user_config.app app
78
57
  end
58
+ conf
59
+ end
60
+
61
+ def run(app, **options)
62
+ conf = self.config(app, options)
63
+
64
+ log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
65
+
66
+ launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)
79
67
 
80
- def self.valid_options
81
- {
82
- "Host=HOST" => "Hostname to listen on (default: localhost)",
83
- "Port=PORT" => "Port to listen on (default: 8080)",
84
- "Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
85
- "Verbose" => "Don't report each request (default: false)"
86
- }
68
+ yield launcher if block_given?
69
+ begin
70
+ launcher.run
71
+ rescue Interrupt
72
+ puts "* Gracefully stopping, waiting for requests to finish"
73
+ launcher.stop
74
+ puts "* Goodbye!"
87
75
  end
76
+ end
88
77
 
89
- def self.set_host_port_to_config(host, port, config)
90
- config.clear_binds! if host || port
91
-
92
- if host && (host[0,1] == '.' || host[0,1] == '/')
93
- config.bind "unix://#{host}"
94
- elsif host && host =~ /^ssl:\/\//
95
- uri = URI.parse(host)
96
- uri.port ||= port || ::Puma::Configuration::DEFAULTS[:tcp_port]
97
- config.bind uri.to_s
98
- else
99
-
100
- if host
101
- port ||= ::Puma::Configuration::DEFAULTS[:tcp_port]
102
- end
103
-
104
- if port
105
- host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
106
- config.port port, host
107
- end
78
+ def valid_options
79
+ {
80
+ "Host=HOST" => "Hostname to listen on (default: localhost)",
81
+ "Port=PORT" => "Port to listen on (default: 8080)",
82
+ "Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
83
+ "Verbose" => "Don't report each request (default: false)"
84
+ }
85
+ end
86
+
87
+ def set_host_port_to_config(host, port, config)
88
+ config.clear_binds! if host || port
89
+
90
+ if host && (host[0,1] == '.' || host[0,1] == '/')
91
+ config.bind "unix://#{host}"
92
+ elsif host && host =~ /^ssl:\/\//
93
+ uri = URI.parse(host)
94
+ uri.port ||= port || ::Puma::Configuration::DEFAULTS[:tcp_port]
95
+ config.bind uri.to_s
96
+ else
97
+
98
+ if host
99
+ port ||= ::Puma::Configuration::DEFAULTS[:tcp_port]
100
+ end
101
+
102
+ if port
103
+ host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
104
+ config.port port, host
108
105
  end
109
106
  end
110
107
  end
108
+ end
109
+ end
111
110
 
112
- register :puma, Puma
111
+ # rackup was removed in Rack 3, it is now a separate gem
112
+ if Object.const_defined? :Rackup
113
+ module Rackup
114
+ module Handler
115
+ module Puma
116
+ class << self
117
+ include ::Puma::RackHandler
118
+ end
119
+ end
120
+ register :puma, Puma
121
+ end
122
+ end
123
+ elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
124
+ module Rack
125
+ module Handler
126
+ module Puma
127
+ class << self
128
+ include ::Puma::RackHandler
129
+ end
130
+ end
131
+ register :puma, Puma
132
+ end
113
133
  end
134
+ else
135
+ raise "You must install the rackup gem when using Rack 3"
114
136
  end
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.0.2
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Phoenix
@@ -104,6 +104,7 @@ files:
104
104
  - lib/puma/minissl/context_builder.rb
105
105
  - lib/puma/null_io.rb
106
106
  - lib/puma/plugin.rb
107
+ - lib/puma/plugin/systemd.rb
107
108
  - lib/puma/plugin/tmp_restart.rb
108
109
  - lib/puma/rack/builder.rb
109
110
  - lib/puma/rack/urlmap.rb
@@ -111,10 +112,10 @@ files:
111
112
  - lib/puma/reactor.rb
112
113
  - lib/puma/request.rb
113
114
  - lib/puma/runner.rb
115
+ - lib/puma/sd_notify.rb
114
116
  - lib/puma/server.rb
115
117
  - lib/puma/single.rb
116
118
  - lib/puma/state_file.rb
117
- - lib/puma/systemd.rb
118
119
  - lib/puma/thread_pool.rb
119
120
  - lib/puma/util.rb
120
121
  - lib/rack/handler/puma.rb
@@ -144,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
145
  - !ruby/object:Gem::Version
145
146
  version: '0'
146
147
  requirements: []
147
- rubygems_version: 3.2.26
148
+ rubygems_version: 3.3.20
148
149
  signing_key:
149
150
  specification_version: 4
150
151
  summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for
data/lib/puma/systemd.rb DELETED
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sd_notify'
4
-
5
- module Puma
6
- class Systemd
7
- def initialize(log_writer, events)
8
- @log_writer = log_writer
9
- @events = events
10
- end
11
-
12
- def hook_events
13
- @events.on_booted { SdNotify.ready }
14
- @events.on_stopped { SdNotify.stopping }
15
- @events.on_restart { SdNotify.reloading }
16
- end
17
-
18
- def start_watchdog
19
- return unless SdNotify.watchdog?
20
-
21
- ping_f = watchdog_sleep_time
22
-
23
- log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
24
- Thread.new do
25
- loop do
26
- sleep ping_f
27
- SdNotify.watchdog
28
- end
29
- end
30
- end
31
-
32
- private
33
-
34
- def watchdog_sleep_time
35
- usec = Integer(ENV["WATCHDOG_USEC"])
36
-
37
- sec_f = usec / 1_000_000.0
38
- # "It is recommended that a daemon sends a keep-alive notification message
39
- # to the service manager every half of the time returned here."
40
- sec_f / 2
41
- end
42
-
43
- def log(str)
44
- @log_writer.log(str)
45
- end
46
- end
47
- end