puma 4.3.8 → 5.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1543 -521
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/docs/kubernetes.md +66 -0
  14. data/docs/nginx.md +1 -1
  15. data/docs/plugins.md +15 -15
  16. data/docs/rails_dev_mode.md +28 -0
  17. data/docs/restart.md +46 -23
  18. data/docs/signals.md +13 -11
  19. data/docs/stats.md +142 -0
  20. data/docs/systemd.md +85 -128
  21. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  22. data/ext/puma_http11/ext_help.h +1 -1
  23. data/ext/puma_http11/extconf.rb +51 -9
  24. data/ext/puma_http11/http11_parser.c +68 -57
  25. data/ext/puma_http11/http11_parser.h +1 -1
  26. data/ext/puma_http11/http11_parser.java.rl +1 -1
  27. data/ext/puma_http11/http11_parser.rl +1 -1
  28. data/ext/puma_http11/http11_parser_common.rl +1 -1
  29. data/ext/puma_http11/mini_ssl.c +295 -124
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  34. data/ext/puma_http11/puma_http11.c +32 -51
  35. data/lib/puma/app/status.rb +50 -36
  36. data/lib/puma/binder.rb +225 -106
  37. data/lib/puma/cli.rb +24 -18
  38. data/lib/puma/client.rb +197 -92
  39. data/lib/puma/cluster/worker.rb +173 -0
  40. data/lib/puma/cluster/worker_handle.rb +94 -0
  41. data/lib/puma/cluster.rb +212 -220
  42. data/lib/puma/commonlogger.rb +2 -2
  43. data/lib/puma/configuration.rb +58 -49
  44. data/lib/puma/const.rb +26 -9
  45. data/lib/puma/control_cli.rb +99 -76
  46. data/lib/puma/detect.rb +29 -2
  47. data/lib/puma/dsl.rb +368 -96
  48. data/lib/puma/error_logger.rb +104 -0
  49. data/lib/puma/events.rb +55 -34
  50. data/lib/puma/io_buffer.rb +9 -2
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/json_serialization.rb +96 -0
  53. data/lib/puma/launcher.rb +128 -46
  54. data/lib/puma/minissl/context_builder.rb +14 -9
  55. data/lib/puma/minissl.rb +137 -50
  56. data/lib/puma/null_io.rb +18 -1
  57. data/lib/puma/plugin.rb +3 -12
  58. data/lib/puma/queue_close.rb +26 -0
  59. data/lib/puma/rack/builder.rb +1 -5
  60. data/lib/puma/reactor.rb +85 -369
  61. data/lib/puma/request.rb +489 -0
  62. data/lib/puma/runner.rb +46 -61
  63. data/lib/puma/server.rb +292 -751
  64. data/lib/puma/single.rb +9 -65
  65. data/lib/puma/state_file.rb +48 -8
  66. data/lib/puma/systemd.rb +46 -0
  67. data/lib/puma/thread_pool.rb +125 -57
  68. data/lib/puma/util.rb +32 -4
  69. data/lib/puma.rb +48 -0
  70. data/lib/rack/handler/puma.rb +2 -3
  71. data/lib/rack/version_restriction.rb +15 -0
  72. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  73. metadata +29 -24
  74. data/docs/tcp_mode.md +0 -96
  75. data/ext/puma_http11/io_buffer.c +0 -155
  76. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  77. data/lib/puma/accept_nonblock.rb +0 -29
  78. data/lib/puma/tcp_logger.rb +0 -41
  79. data/tools/jungle/README.md +0 -19
  80. data/tools/jungle/init.d/README.md +0 -61
  81. data/tools/jungle/init.d/puma +0 -421
  82. data/tools/jungle/init.d/run-puma +0 -18
  83. data/tools/jungle/upstart/README.md +0 -61
  84. data/tools/jungle/upstart/puma-manager.conf +0 -31
  85. data/tools/jungle/upstart/puma.conf +0 -69
  86. /data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
data/lib/puma/server.rb CHANGED
@@ -9,12 +9,12 @@ require 'puma/null_io'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
11
  require 'puma/binder'
12
- require 'puma/accept_nonblock'
13
12
  require 'puma/util'
14
-
15
- require 'puma/puma_http11'
13
+ require 'puma/io_buffer'
14
+ require 'puma/request'
16
15
 
17
16
  require 'socket'
17
+ require 'io/wait'
18
18
  require 'forwardable'
19
19
 
20
20
  module Puma
@@ -32,18 +32,32 @@ module Puma
32
32
  class Server
33
33
 
34
34
  include Puma::Const
35
+ include Request
35
36
  extend Forwardable
36
37
 
37
38
  attr_reader :thread
38
39
  attr_reader :events
40
+ attr_reader :min_threads, :max_threads # for #stats
41
+ attr_reader :requests_count # @version 5.0.0
42
+ attr_reader :log_writer # to help with backports
43
+
44
+ # @todo the following may be deprecated in the future
45
+ attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
46
+ :leak_stack_on_error,
47
+ :persistent_timeout, :reaping_time
48
+
49
+ # @deprecated v6.0.0
50
+ attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
51
+ :leak_stack_on_error, :min_threads, :max_threads,
52
+ :persistent_timeout, :reaping_time
53
+
39
54
  attr_accessor :app
55
+ attr_accessor :binder
56
+
57
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
58
+ :add_unix_listener, :connected_ports
40
59
 
41
- attr_accessor :min_threads
42
- attr_accessor :max_threads
43
- attr_accessor :persistent_timeout
44
- attr_accessor :auto_trim_time
45
- attr_accessor :reaping_time
46
- attr_accessor :first_data_timeout
60
+ ThreadLocalKey = :puma_server
47
61
 
48
62
  # Create a server for the rack app +app+.
49
63
  #
@@ -53,85 +67,118 @@ module Puma
53
67
  # Server#run returns a thread that you can join on to wait for the server
54
68
  # to do its work.
55
69
  #
70
+ # @note Several instance variables exist so they are available for testing,
71
+ # and have default values set via +fetch+. Normally the values are set via
72
+ # `::Puma::Configuration.puma_default_options`.
73
+ #
56
74
  def initialize(app, events=Events.stdio, options={})
57
75
  @app = app
58
76
  @events = events
77
+ @log_writer = events
59
78
 
60
- @check, @notify = Puma::Util.pipe
61
-
79
+ @check, @notify = nil
62
80
  @status = :stop
63
81
 
64
- @min_threads = 0
65
- @max_threads = 16
66
82
  @auto_trim_time = 30
67
83
  @reaping_time = 1
68
84
 
69
85
  @thread = nil
70
86
  @thread_pool = nil
71
- @early_hints = nil
72
87
 
73
- @persistent_timeout = options.fetch(:persistent_timeout, PERSISTENT_TIMEOUT)
74
- @first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
88
+ @options = options
75
89
 
76
- @binder = Binder.new(events)
90
+ @early_hints = options.fetch :early_hints, nil
91
+ @first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
92
+ @min_threads = options.fetch :min_threads, 0
93
+ @max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
94
+ @persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
95
+ @queue_requests = options.fetch :queue_requests, true
96
+ @max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
97
+ @io_selector_backend = options.fetch :io_selector_backend, :auto
77
98
 
78
- @leak_stack_on_error = true
99
+ temp = !!(@options[:environment] =~ /\A(development|test)\z/)
100
+ @leak_stack_on_error = @options[:environment] ? temp : true
79
101
 
80
- @options = options
81
- @queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
102
+ @binder = Binder.new(events)
82
103
 
83
104
  ENV['RACK_ENV'] ||= "development"
84
105
 
85
106
  @mode = :http
86
107
 
87
108
  @precheck_closing = true
88
- end
89
109
 
90
- attr_accessor :binder, :leak_stack_on_error, :early_hints
91
-
92
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
110
+ @requests_count = 0
111
+ end
93
112
 
94
113
  def inherit_binder(bind)
95
114
  @binder = bind
96
115
  end
97
116
 
98
- def tcp_mode!
99
- @mode = :tcp
117
+ class << self
118
+ # @!attribute [r] current
119
+ def current
120
+ Thread.current[ThreadLocalKey]
121
+ end
122
+
123
+ # :nodoc:
124
+ # @version 5.0.0
125
+ def tcp_cork_supported?
126
+ Socket.const_defined?(:TCP_CORK) && Socket.const_defined?(:IPPROTO_TCP)
127
+ end
128
+
129
+ # :nodoc:
130
+ # @version 5.0.0
131
+ def closed_socket_supported?
132
+ Socket.const_defined?(:TCP_INFO) && Socket.const_defined?(:IPPROTO_TCP)
133
+ end
134
+ private :tcp_cork_supported?
135
+ private :closed_socket_supported?
100
136
  end
101
137
 
102
138
  # On Linux, use TCP_CORK to better control how the TCP stack
103
139
  # packetizes our stream. This improves both latency and throughput.
140
+ # socket parameter may be an MiniSSL::Socket, so use to_io
104
141
  #
105
- if RUBY_PLATFORM =~ /linux/
106
- UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
107
-
142
+ if tcp_cork_supported?
108
143
  # 6 == Socket::IPPROTO_TCP
109
144
  # 3 == TCP_CORK
110
145
  # 1/0 == turn on/off
111
146
  def cork_socket(socket)
147
+ skt = socket.to_io
112
148
  begin
113
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
149
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
114
150
  rescue IOError, SystemCallError
115
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
151
+ Puma::Util.purge_interrupt_queue
116
152
  end
117
153
  end
118
154
 
119
155
  def uncork_socket(socket)
156
+ skt = socket.to_io
120
157
  begin
121
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
158
+ skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
122
159
  rescue IOError, SystemCallError
123
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
160
+ Puma::Util.purge_interrupt_queue
124
161
  end
125
162
  end
163
+ else
164
+ def cork_socket(socket)
165
+ end
166
+
167
+ def uncork_socket(socket)
168
+ end
169
+ end
170
+
171
+ if closed_socket_supported?
172
+ UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
126
173
 
127
174
  def closed_socket?(socket)
128
- return false unless socket.kind_of? TCPSocket
129
- return false unless @precheck_closing
175
+ skt = socket.to_io
176
+ return false unless skt.kind_of?(TCPSocket) && @precheck_closing
130
177
 
131
178
  begin
132
- tcp_info = socket.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO)
179
+ tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
133
180
  rescue IOError, SystemCallError
134
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
181
+ Puma::Util.purge_interrupt_queue
135
182
  @precheck_closing = false
136
183
  false
137
184
  else
@@ -141,21 +188,17 @@ module Puma
141
188
  end
142
189
  end
143
190
  else
144
- def cork_socket(socket)
145
- end
146
-
147
- def uncork_socket(socket)
148
- end
149
-
150
191
  def closed_socket?(socket)
151
192
  false
152
193
  end
153
194
  end
154
195
 
196
+ # @!attribute [r] backlog
155
197
  def backlog
156
198
  @thread_pool and @thread_pool.backlog
157
199
  end
158
200
 
201
+ # @!attribute [r] running
159
202
  def running
160
203
  @thread_pool and @thread_pool.spawned
161
204
  end
@@ -168,176 +211,38 @@ module Puma
168
211
  # there are 5 threads sitting idle ready to take
169
212
  # a request. If one request comes in, then the
170
213
  # value would be 4 until it finishes processing.
214
+ # @!attribute [r] pool_capacity
171
215
  def pool_capacity
172
216
  @thread_pool and @thread_pool.pool_capacity
173
217
  end
174
218
 
175
- # Lopez Mode == raw tcp apps
176
-
177
- def run_lopez_mode(background=true)
178
- @thread_pool = ThreadPool.new(@min_threads,
179
- @max_threads,
180
- Hash) do |client, tl|
181
-
182
- io = client.to_io
183
- addr = io.peeraddr.last
184
-
185
- if addr.empty?
186
- # Set unix socket addrs to localhost
187
- addr = "127.0.0.1:0"
188
- else
189
- addr = "#{addr}:#{io.peeraddr[1]}"
190
- end
191
-
192
- env = { 'thread' => tl, REMOTE_ADDR => addr }
193
-
194
- begin
195
- @app.call env, client.to_io
196
- rescue Object => e
197
- STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
198
- STDERR.puts e.backtrace
199
- end
200
-
201
- client.close unless env['detach']
202
- end
203
-
204
- @events.fire :state, :running
205
-
206
- if background
207
- @thread = Thread.new do
208
- Puma.set_thread_name "server"
209
- handle_servers_lopez_mode
210
- end
211
- return @thread
212
- else
213
- handle_servers_lopez_mode
214
- end
215
- end
216
-
217
- def handle_servers_lopez_mode
218
- begin
219
- check = @check
220
- sockets = [check] + @binder.ios
221
- pool = @thread_pool
222
-
223
- while @status == :run
224
- begin
225
- ios = IO.select sockets
226
- ios.first.each do |sock|
227
- if sock == check
228
- break if handle_check
229
- else
230
- begin
231
- if io = sock.accept_nonblock
232
- client = Client.new io, nil
233
- pool << client
234
- end
235
- rescue SystemCallError
236
- # nothing
237
- rescue Errno::ECONNABORTED
238
- # client closed the socket even before accept
239
- begin
240
- io.close
241
- rescue
242
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
243
- end
244
- end
245
- end
246
- end
247
- rescue Object => e
248
- @events.unknown_error self, e, "Listen loop"
249
- end
250
- end
251
-
252
- @events.fire :state, @status
253
-
254
- graceful_shutdown if @status == :stop || @status == :restart
255
-
256
- rescue Exception => e
257
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
258
- STDERR.puts e.backtrace
259
- ensure
260
- begin
261
- @check.close
262
- rescue
263
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
264
- end
265
-
266
- # Prevent can't modify frozen IOError (RuntimeError)
267
- begin
268
- @notify.close
269
- rescue IOError
270
- # no biggy
271
- end
272
- end
273
-
274
- @events.fire :state, :done
275
- end
276
219
  # Runs the server.
277
220
  #
278
221
  # If +background+ is true (the default) then a thread is spun
279
222
  # up in the background to handle requests. Otherwise requests
280
223
  # are handled synchronously.
281
224
  #
282
- def run(background=true)
225
+ def run(background=true, thread_name: 'srv')
283
226
  BasicSocket.do_not_reverse_lookup = true
284
227
 
285
228
  @events.fire :state, :booting
286
229
 
287
230
  @status = :run
288
231
 
289
- if @mode == :tcp
290
- return run_lopez_mode(background)
291
- end
292
-
293
- queue_requests = @queue_requests
294
-
295
- @thread_pool = ThreadPool.new(@min_threads,
296
- @max_threads,
297
- IOBuffer) do |client, buffer|
298
-
299
- # Advertise this server into the thread
300
- Thread.current[ThreadLocalKey] = self
301
-
302
- process_now = false
303
-
304
- begin
305
- if queue_requests
306
- process_now = client.eagerly_finish
307
- else
308
- client.finish
309
- process_now = true
310
- end
311
- rescue MiniSSL::SSLError => e
312
- ssl_socket = client.io
313
- addr = ssl_socket.peeraddr.last
314
- cert = ssl_socket.peercert
315
-
316
- client.close
317
-
318
- @events.ssl_error self, addr, cert, e
319
- rescue HttpParserError => e
320
- client.write_error(400)
321
- client.close
322
-
323
- @events.parse_error self, client.env, e
324
- rescue ConnectionError, EOFError
325
- client.close
326
- else
327
- if process_now
328
- process_client client, buffer
329
- else
330
- client.set_timeout @first_data_timeout
331
- @reactor.add client
332
- end
333
- end
334
- end
232
+ @thread_pool = ThreadPool.new(
233
+ thread_name,
234
+ @min_threads,
235
+ @max_threads,
236
+ ::Puma::IOBuffer,
237
+ &method(:process_client)
238
+ )
335
239
 
240
+ @thread_pool.out_of_band_hook = @options[:out_of_band]
336
241
  @thread_pool.clean_thread_locals = @options[:clean_thread_locals]
337
242
 
338
- if queue_requests
339
- @reactor = Reactor.new self, @thread_pool
340
- @reactor.run_in_thread
243
+ if @queue_requests
244
+ @reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
245
+ @reactor.run
341
246
  end
342
247
 
343
248
  if @reaping_time
@@ -348,11 +253,13 @@ module Puma
348
253
  @thread_pool.auto_trim!(@auto_trim_time)
349
254
  end
350
255
 
256
+ @check, @notify = Puma::Util.pipe unless @notify
257
+
351
258
  @events.fire :state, :running
352
259
 
353
260
  if background
354
261
  @thread = Thread.new do
355
- Puma.set_thread_name "server"
262
+ Puma.set_thread_name thread_name
356
263
  handle_servers
357
264
  end
358
265
  return @thread
@@ -361,75 +268,118 @@ module Puma
361
268
  end
362
269
  end
363
270
 
271
+ # This method is called from the Reactor thread when a queued Client receives data,
272
+ # times out, or when the Reactor is shutting down.
273
+ #
274
+ # It is responsible for ensuring that a request has been completely received
275
+ # before it starts to be processed by the ThreadPool. This may be known as read buffering.
276
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
277
+ # such as nginx) then the application would be subject to a slow client attack.
278
+ #
279
+ # For a graphical representation of how the request buffer works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
280
+ #
281
+ # The method checks to see if it has the full header and body with
282
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
283
+ # then the request is passed to the ThreadPool (`@thread_pool << client`)
284
+ # so that a "worker thread" can pick up the request and begin to execute application logic.
285
+ # The Client is then removed from the reactor (return `true`).
286
+ #
287
+ # If a client object times out, a 408 response is written, its connection is closed,
288
+ # and the object is removed from the reactor (return `true`).
289
+ #
290
+ # If the Reactor is shutting down, all Clients are either timed out or passed to the
291
+ # ThreadPool, depending on their current state (#can_close?).
292
+ #
293
+ # Otherwise, if the full request is not ready then the client will remain in the reactor
294
+ # (return `false`). When the client sends more data to the socket the `Puma::Client` object
295
+ # will wake up and again be checked to see if it's ready to be passed to the thread pool.
296
+ def reactor_wakeup(client)
297
+ shutdown = !@queue_requests
298
+ if client.try_to_finish || (shutdown && !client.can_close?)
299
+ @thread_pool << client
300
+ elsif shutdown || client.timeout == 0
301
+ client.timeout!
302
+ else
303
+ client.set_timeout(@first_data_timeout)
304
+ false
305
+ end
306
+ rescue StandardError => e
307
+ client_error(e, client)
308
+ client.close
309
+ true
310
+ end
311
+
364
312
  def handle_servers
365
313
  begin
366
314
  check = @check
367
315
  sockets = [check] + @binder.ios
368
316
  pool = @thread_pool
369
317
  queue_requests = @queue_requests
318
+ drain = @options[:drain_on_shutdown] ? 0 : nil
370
319
 
371
- remote_addr_value = nil
372
- remote_addr_header = nil
373
-
374
- case @options[:remote_address]
320
+ addr_send_name, addr_value = case @options[:remote_address]
375
321
  when :value
376
- remote_addr_value = @options[:remote_address_value]
322
+ [:peerip=, @options[:remote_address_value]]
377
323
  when :header
378
- remote_addr_header = @options[:remote_address_header]
324
+ [:remote_addr_header=, @options[:remote_address_header]]
325
+ when :proxy_protocol
326
+ [:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
327
+ else
328
+ [nil, nil]
379
329
  end
380
330
 
381
- while @status == :run
331
+ while @status == :run || (drain && shutting_down?)
382
332
  begin
383
- ios = IO.select sockets
333
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
334
+ break unless ios
384
335
  ios.first.each do |sock|
385
336
  if sock == check
386
337
  break if handle_check
387
338
  else
388
- begin
389
- if io = sock.accept_nonblock
390
- client = Client.new io, @binder.env(sock)
391
- if remote_addr_value
392
- client.peerip = remote_addr_value
393
- elsif remote_addr_header
394
- client.remote_addr_header = remote_addr_header
395
- end
396
-
397
- pool << client
398
- busy_threads = pool.wait_until_not_full
399
- if busy_threads == 0
400
- @options[:out_of_band].each(&:call) if @options[:out_of_band]
401
- end
402
- end
403
- rescue SystemCallError
404
- # nothing
405
- rescue Errno::ECONNABORTED
406
- # client closed the socket even before accept
407
- begin
408
- io.close
409
- rescue
410
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
411
- end
339
+ pool.wait_until_not_full
340
+ pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
341
+
342
+ io = begin
343
+ sock.accept_nonblock
344
+ rescue IO::WaitReadable
345
+ next
412
346
  end
347
+ drain += 1 if shutting_down?
348
+ pool << Client.new(io, @binder.env(sock)).tap { |c|
349
+ c.listener = sock
350
+ c.send(addr_send_name, addr_value) if addr_value
351
+ }
413
352
  end
414
353
  end
415
- rescue Object => e
416
- @events.unknown_error self, e, "Listen loop"
354
+ rescue IOError, Errno::EBADF
355
+ # In the case that any of the sockets are unexpectedly close.
356
+ raise
357
+ rescue StandardError => e
358
+ @events.unknown_error e, nil, "Listen loop"
417
359
  end
418
360
  end
419
361
 
362
+ @events.debug "Drained #{drain} additional connections." if drain
420
363
  @events.fire :state, @status
421
364
 
422
- graceful_shutdown if @status == :stop || @status == :restart
423
365
  if queue_requests
424
- @reactor.clear!
366
+ @queue_requests = false
425
367
  @reactor.shutdown
426
368
  end
369
+ graceful_shutdown if @status == :stop || @status == :restart
427
370
  rescue Exception => e
428
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
429
- STDERR.puts e.backtrace
371
+ @events.unknown_error e, nil, "Exception handling servers"
430
372
  ensure
431
- @check.close
432
- @notify.close
373
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
374
+ # Errno::EBADF is infrequently raised
375
+ [@check, @notify].each do |io|
376
+ begin
377
+ io.close unless io.closed?
378
+ rescue Errno::EBADF, RuntimeError
379
+ end
380
+ end
381
+ @notify = nil
382
+ @check = nil
433
383
  end
434
384
 
435
385
  @events.fire :state, :done
@@ -451,514 +401,149 @@ module Puma
451
401
  return true
452
402
  end
453
403
 
454
- return false
404
+ false
455
405
  end
456
406
 
457
- # Given a connection on +client+, handle the incoming requests.
407
+ # Given a connection on +client+, handle the incoming requests,
408
+ # or queue the connection in the Reactor if no request is available.
409
+ #
410
+ # This method is called from a ThreadPool worker thread.
458
411
  #
459
- # This method support HTTP Keep-Alive so it may, depending on if the client
412
+ # This method supports HTTP Keep-Alive so it may, depending on if the client
460
413
  # indicates that it supports keep alive, wait for another request before
461
414
  # returning.
462
415
  #
416
+ # Return true if one or more requests were processed.
463
417
  def process_client(client, buffer)
418
+ # Advertise this server into the thread
419
+ Thread.current[ThreadLocalKey] = self
420
+
421
+ clean_thread_locals = @options[:clean_thread_locals]
422
+ close_socket = true
423
+
424
+ requests = 0
425
+
464
426
  begin
427
+ if @queue_requests &&
428
+ !client.eagerly_finish
465
429
 
466
- clean_thread_locals = @options[:clean_thread_locals]
467
- close_socket = true
430
+ client.set_timeout(@first_data_timeout)
431
+ if @reactor.add client
432
+ close_socket = false
433
+ return false
434
+ end
435
+ end
468
436
 
469
- requests = 0
437
+ with_force_shutdown(client) do
438
+ client.finish(@first_data_timeout)
439
+ end
470
440
 
471
441
  while true
472
- case handle_request(client, buffer)
442
+ @requests_count += 1
443
+ case handle_request(client, buffer, requests + 1)
473
444
  when false
474
- return
445
+ break
475
446
  when :async
476
447
  close_socket = false
477
- return
448
+ break
478
449
  when true
479
- return unless @queue_requests
480
450
  buffer.reset
481
451
 
482
452
  ThreadPool.clean_thread_locals if clean_thread_locals
483
453
 
484
454
  requests += 1
485
455
 
486
- # Closing keepalive sockets after they've made a reasonable
487
- # number of requests allows Puma to service many connections
488
- # fairly, even when the number of concurrent connections exceeds
489
- # the size of the threadpool. It also allows cluster mode Pumas
490
- # to keep load evenly distributed across workers, because clients
491
- # are randomly assigned a new worker when opening a new connection.
492
- #
493
- # Previously, Puma would kick connections in this conditional back
494
- # to the reactor. However, because this causes the todo set to increase
495
- # in size, the wait_until_full mutex would never unlock, leaving
496
- # any additional connections unserviced.
497
- break if requests >= MAX_FAST_INLINE
498
-
499
- check_for_more_data = @status == :run
500
-
501
- unless client.reset(check_for_more_data)
502
- close_socket = false
456
+ # As an optimization, try to read the next request from the
457
+ # socket for a short time before returning to the reactor.
458
+ fast_check = @status == :run
459
+
460
+ # Always pass the client back to the reactor after a reasonable
461
+ # number of inline requests if there are other requests pending.
462
+ fast_check = false if requests >= @max_fast_inline &&
463
+ @thread_pool.backlog > 0
464
+
465
+ next_request_ready = with_force_shutdown(client) do
466
+ client.reset(fast_check)
467
+ end
468
+
469
+ unless next_request_ready
470
+ break unless @queue_requests
503
471
  client.set_timeout @persistent_timeout
504
- @reactor.add client
505
- return
472
+ if @reactor.add client
473
+ close_socket = false
474
+ break
475
+ end
506
476
  end
507
477
  end
508
478
  end
509
-
510
- # The client disconnected while we were reading data
511
- rescue ConnectionError
512
- # Swallow them. The ensure tries to close +client+ down
513
-
514
- # SSL handshake error
515
- rescue MiniSSL::SSLError => e
516
- lowlevel_error(e, client.env)
517
-
518
- ssl_socket = client.io
519
- addr = ssl_socket.peeraddr.last
520
- cert = ssl_socket.peercert
521
-
522
- close_socket = true
523
-
524
- @events.ssl_error self, addr, cert, e
525
-
526
- # The client doesn't know HTTP well
527
- rescue HttpParserError => e
528
- lowlevel_error(e, client.env)
529
-
530
- client.write_error(400)
531
-
532
- @events.parse_error self, client.env, e
533
-
534
- # Server error
479
+ true
535
480
  rescue StandardError => e
536
- lowlevel_error(e, client.env)
537
-
538
- client.write_error(500)
539
-
540
- @events.unknown_error self, e, "Read"
541
-
481
+ client_error(e, client)
482
+ # The ensure tries to close +client+ down
483
+ requests > 0
542
484
  ensure
543
485
  buffer.reset
544
486
 
545
487
  begin
546
488
  client.close if close_socket
547
489
  rescue IOError, SystemCallError
548
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
490
+ Puma::Util.purge_interrupt_queue
549
491
  # Already closed
550
492
  rescue StandardError => e
551
- @events.unknown_error self, e, "Client"
493
+ @events.unknown_error e, nil, "Client"
552
494
  end
553
495
  end
554
496
  end
555
497
 
556
- # Given a Hash +env+ for the request read from +client+, add
557
- # and fixup keys to comply with Rack's env guidelines.
558
- #
559
- def normalize_env(env, client)
560
- if host = env[HTTP_HOST]
561
- if colon = host.index(":")
562
- env[SERVER_NAME] = host[0, colon]
563
- env[SERVER_PORT] = host[colon+1, host.bytesize]
564
- else
565
- env[SERVER_NAME] = host
566
- env[SERVER_PORT] = default_server_port(env)
567
- end
568
- else
569
- env[SERVER_NAME] = LOCALHOST
570
- env[SERVER_PORT] = default_server_port(env)
571
- end
572
-
573
- unless env[REQUEST_PATH]
574
- # it might be a dumbass full host request header
575
- uri = URI.parse(env[REQUEST_URI])
576
- env[REQUEST_PATH] = uri.path
577
-
578
- raise "No REQUEST PATH" unless env[REQUEST_PATH]
579
-
580
- # A nil env value will cause a LintError (and fatal errors elsewhere),
581
- # so only set the env value if there actually is a value.
582
- env[QUERY_STRING] = uri.query if uri.query
583
- end
584
-
585
- env[PATH_INFO] = env[REQUEST_PATH]
586
-
587
- # From http://www.ietf.org/rfc/rfc3875 :
588
- # "Script authors should be aware that the REMOTE_ADDR and
589
- # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
590
- # may not identify the ultimate source of the request.
591
- # They identify the client for the immediate request to the
592
- # server; that client may be a proxy, gateway, or other
593
- # intermediary acting on behalf of the actual source client."
594
- #
595
-
596
- unless env.key?(REMOTE_ADDR)
597
- begin
598
- addr = client.peerip
599
- rescue Errno::ENOTCONN
600
- # Client disconnects can result in an inability to get the
601
- # peeraddr from the socket; default to localhost.
602
- addr = LOCALHOST_IP
603
- end
604
-
605
- # Set unix socket addrs to localhost
606
- addr = LOCALHOST_IP if addr.empty?
607
-
608
- env[REMOTE_ADDR] = addr
609
- end
610
- end
611
-
612
- def default_server_port(env)
613
- if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
614
- PORT_443
615
- else
616
- PORT_80
617
- end
618
- end
619
-
620
- # Takes the request +req+, invokes the Rack application to construct
621
- # the response and writes it back to +req.io+.
622
- #
623
- # The second parameter +lines+ is a IO-like object unique to this thread.
624
- # This is normally an instance of Puma::IOBuffer.
625
- #
626
- # It'll return +false+ when the connection is closed, this doesn't mean
627
- # that the response wasn't successful.
628
- #
629
- # It'll return +:async+ if the connection remains open but will be handled
630
- # elsewhere, i.e. the connection has been hijacked by the Rack application.
631
- #
632
- # Finally, it'll return +true+ on keep-alive connections.
633
- def handle_request(req, lines)
634
- env = req.env
635
- client = req.io
636
-
637
- return false if closed_socket?(client)
638
-
639
- normalize_env env, req
640
-
641
- env[PUMA_SOCKET] = client
642
-
643
- if env[HTTPS_KEY] && client.peercert
644
- env[PUMA_PEERCERT] = client.peercert
645
- end
646
-
647
- env[HIJACK_P] = true
648
- env[HIJACK] = req
649
-
650
- body = req.body
651
-
652
- head = env[REQUEST_METHOD] == HEAD
653
-
654
- env[RACK_INPUT] = body
655
- env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
656
-
657
- if @early_hints
658
- env[EARLY_HINTS] = lambda { |headers|
659
- begin
660
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
661
-
662
- headers.each_pair do |k, vs|
663
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
664
- vs.to_s.split(NEWLINE).each do |v|
665
- next if possible_header_injection?(v)
666
- fast_write client, "#{k}: #{v}\r\n"
667
- end
668
- else
669
- fast_write client, "#{k}: #{vs}\r\n"
670
- end
671
- end
672
-
673
- fast_write client, "\r\n".freeze
674
- rescue ConnectionError
675
- # noop, if we lost the socket we just won't send the early hints
676
- end
677
- }
678
- end
679
-
680
- # Fixup any headers with , in the name to have _ now. We emit
681
- # headers with , in them during the parse phase to avoid ambiguity
682
- # with the - to _ conversion for critical headers. But here for
683
- # compatibility, we'll convert them back. This code is written to
684
- # avoid allocation in the common case (ie there are no headers
685
- # with , in their names), that's why it has the extra conditionals.
686
-
687
- to_delete = nil
688
- to_add = nil
689
-
690
- env.each do |k,v|
691
- if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
692
- if to_delete
693
- to_delete << k
694
- else
695
- to_delete = [k]
696
- end
697
-
698
- unless to_add
699
- to_add = {}
700
- end
701
-
702
- to_add[k.tr(",", "_")] = v
703
- end
704
- end
705
-
706
- if to_delete
707
- to_delete.each { |k| env.delete(k) }
708
- env.merge! to_add
709
- end
710
-
711
- # A rack extension. If the app writes #call'ables to this
712
- # array, we will invoke them when the request is done.
713
- #
714
- after_reply = env[RACK_AFTER_REPLY] = []
715
-
716
- begin
717
- begin
718
- status, headers, res_body = @app.call(env)
719
-
720
- return :async if req.hijacked
721
-
722
- status = status.to_i
723
-
724
- if status == -1
725
- unless headers.empty? and res_body == []
726
- raise "async response must have empty headers and body"
727
- end
728
-
729
- return :async
730
- end
731
- rescue ThreadPool::ForceShutdown => e
732
- @events.log "Detected force shutdown of a thread, returning 503"
733
- @events.unknown_error self, e, "Rack app"
734
-
735
- status = 503
736
- headers = {}
737
- res_body = ["Request was internally terminated early\n"]
738
-
739
- rescue Exception => e
740
- @events.unknown_error self, e, "Rack app", env
741
-
742
- status, headers, res_body = lowlevel_error(e, env)
743
- end
744
-
745
- content_length = nil
746
- no_body = head
747
-
748
- if res_body.kind_of? Array and res_body.size == 1
749
- content_length = res_body[0].bytesize
750
- end
751
-
752
- cork_socket client
753
-
754
- line_ending = LINE_END
755
- colon = COLON
756
-
757
- http_11 = if env[HTTP_VERSION] == HTTP_11
758
- allow_chunked = true
759
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
760
- include_keepalive_header = false
761
-
762
- # An optimization. The most common response is 200, so we can
763
- # reply with the proper 200 status without having to compute
764
- # the response header.
765
- #
766
- if status == 200
767
- lines << HTTP_11_200
768
- else
769
- lines.append "HTTP/1.1 ", status.to_s, " ",
770
- fetch_status_code(status), line_ending
771
-
772
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
773
- end
774
- true
775
- else
776
- allow_chunked = false
777
- keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
778
- include_keepalive_header = keep_alive
779
-
780
- # Same optimization as above for HTTP/1.1
781
- #
782
- if status == 200
783
- lines << HTTP_10_200
784
- else
785
- lines.append "HTTP/1.0 ", status.to_s, " ",
786
- fetch_status_code(status), line_ending
787
-
788
- no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
789
- end
790
- false
791
- end
792
-
793
- response_hijack = nil
794
-
795
- headers.each do |k, vs|
796
- case k.downcase
797
- when CONTENT_LENGTH2
798
- next if possible_header_injection?(vs)
799
- content_length = vs
800
- next
801
- when TRANSFER_ENCODING
802
- allow_chunked = false
803
- content_length = nil
804
- when HIJACK
805
- response_hijack = vs
806
- next
807
- end
808
-
809
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
810
- vs.to_s.split(NEWLINE).each do |v|
811
- next if possible_header_injection?(v)
812
- lines.append k, colon, v, line_ending
813
- end
814
- else
815
- lines.append k, colon, line_ending
816
- end
817
- end
818
-
819
- if include_keepalive_header
820
- lines << CONNECTION_KEEP_ALIVE
821
- elsif http_11 && !keep_alive
822
- lines << CONNECTION_CLOSE
823
- end
824
-
825
- if no_body
826
- if content_length and status != 204
827
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
828
- end
829
-
830
- lines << line_ending
831
- fast_write client, lines.to_s
832
- return keep_alive
833
- end
834
-
835
- if content_length
836
- lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
837
- chunked = false
838
- elsif !response_hijack and allow_chunked
839
- lines << TRANSFER_ENCODING_CHUNKED
840
- chunked = true
841
- end
842
-
843
- lines << line_ending
844
-
845
- fast_write client, lines.to_s
846
-
847
- if response_hijack
848
- response_hijack.call client
849
- return :async
850
- end
851
-
852
- begin
853
- res_body.each do |part|
854
- next if part.bytesize.zero?
855
- if chunked
856
- fast_write client, part.bytesize.to_s(16)
857
- fast_write client, line_ending
858
- fast_write client, part
859
- fast_write client, line_ending
860
- else
861
- fast_write client, part
862
- end
863
-
864
- client.flush
865
- end
866
-
867
- if chunked
868
- fast_write client, CLOSE_CHUNKED
869
- client.flush
870
- end
871
- rescue SystemCallError, IOError
872
- raise ConnectionError, "Connection error detected during write"
873
- end
874
-
875
- ensure
876
- uncork_socket client
877
-
878
- body.close
879
- req.tempfile.unlink if req.tempfile
880
- res_body.close if res_body.respond_to? :close
881
-
882
- after_reply.each { |o| o.call }
883
- end
884
-
885
- return keep_alive
886
- end
887
-
888
- def fetch_status_code(status)
889
- HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
498
+ # Triggers a client timeout if the thread-pool shuts down
499
+ # during execution of the provided block.
500
+ def with_force_shutdown(client, &block)
501
+ @thread_pool.with_force_shutdown(&block)
502
+ rescue ThreadPool::ForceShutdown
503
+ client.timeout!
890
504
  end
891
- private :fetch_status_code
892
505
 
893
- # Given the request +env+ from +client+ and the partial body +body+
894
- # plus a potential Content-Length value +cl+, finish reading
895
- # the body and return it.
896
- #
897
- # If the body is larger than MAX_BODY, a Tempfile object is used
898
- # for the body, otherwise a StringIO is used.
899
- #
900
- def read_body(env, client, body, cl)
901
- content_length = cl.to_i
506
+ # :nocov:
902
507
 
903
- remain = content_length - body.bytesize
508
+ # Handle various error types thrown by Client I/O operations.
509
+ def client_error(e, client)
510
+ # Swallow, do not log
511
+ return if [ConnectionError, EOFError].include?(e.class)
904
512
 
905
- return StringIO.new(body) if remain <= 0
906
-
907
- # Use a Tempfile if there is a lot of data left
908
- if remain > MAX_BODY
909
- stream = Tempfile.new(Const::PUMA_TMP_BASE)
910
- stream.binmode
513
+ lowlevel_error(e, client.env)
514
+ case e
515
+ when MiniSSL::SSLError
516
+ @events.ssl_error e, client.io
517
+ when HttpParserError
518
+ client.write_error(400)
519
+ @events.parse_error e, client
520
+ when HttpParserError501
521
+ client.write_error(501)
522
+ @events.parse_error e, client
911
523
  else
912
- # The body[0,0] trick is to get an empty string in the same
913
- # encoding as body.
914
- stream = StringIO.new body[0,0]
915
- end
916
-
917
- stream.write body
918
-
919
- # Read an odd sized chunk so we can read even sized ones
920
- # after this
921
- chunk = client.readpartial(remain % CHUNK_SIZE)
922
-
923
- # No chunk means a closed socket
924
- unless chunk
925
- stream.close
926
- return nil
927
- end
928
-
929
- remain -= stream.write(chunk)
930
-
931
- # Raed the rest of the chunks
932
- while remain > 0
933
- chunk = client.readpartial(CHUNK_SIZE)
934
- unless chunk
935
- stream.close
936
- return nil
937
- end
938
-
939
- remain -= stream.write(chunk)
524
+ client.write_error(500)
525
+ @events.unknown_error e, nil, "Read"
940
526
  end
941
-
942
- stream.rewind
943
-
944
- return stream
945
527
  end
946
528
 
947
529
  # A fallback rack response if +@app+ raises as exception.
948
530
  #
949
- def lowlevel_error(e, env)
531
+ def lowlevel_error(e, env, status=500)
950
532
  if handler = @options[:lowlevel_error_handler]
951
533
  if handler.arity == 1
952
534
  return handler.call(e)
953
- else
535
+ elsif handler.arity == 2
954
536
  return handler.call(e, env)
537
+ else
538
+ return handler.call(e, env, status)
955
539
  end
956
540
  end
957
541
 
958
542
  if @leak_stack_on_error
959
- [500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
543
+ backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
544
+ [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
960
545
  else
961
- [500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
546
+ [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
962
547
  end
963
548
  end
964
549
 
@@ -980,35 +565,13 @@ module Puma
980
565
  $stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
981
566
  end
982
567
 
983
- if @options[:drain_on_shutdown]
984
- count = 0
985
-
986
- while true
987
- ios = IO.select @binder.ios, nil, nil, 0
988
- break unless ios
989
-
990
- ios.first.each do |sock|
991
- begin
992
- if io = sock.accept_nonblock
993
- count += 1
994
- client = Client.new io, @binder.env(sock)
995
- @thread_pool << client
996
- end
997
- rescue SystemCallError
998
- end
999
- end
1000
- end
1001
-
1002
- @events.debug "Drained #{count} additional connections."
1003
- end
1004
-
1005
568
  if @status != :restart
1006
569
  @binder.close
1007
570
  end
1008
571
 
1009
572
  if @thread_pool
1010
573
  if timeout = @options[:force_shutdown_after]
1011
- @thread_pool.shutdown timeout.to_i
574
+ @thread_pool.shutdown timeout.to_f
1012
575
  else
1013
576
  @thread_pool.shutdown
1014
577
  end
@@ -1016,18 +579,16 @@ module Puma
1016
579
  end
1017
580
 
1018
581
  def notify_safely(message)
1019
- begin
1020
- @notify << message
1021
- rescue IOError
1022
- # The server, in another thread, is shutting down
1023
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1024
- rescue RuntimeError => e
1025
- # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
1026
- if e.message.include?('IOError')
1027
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
1028
- else
1029
- raise e
1030
- end
582
+ @notify << message
583
+ rescue IOError, NoMethodError, Errno::EPIPE
584
+ # The server, in another thread, is shutting down
585
+ Puma::Util.purge_interrupt_queue
586
+ rescue RuntimeError => e
587
+ # Temporary workaround for https://bugs.ruby-lang.org/issues/13239
588
+ if e.message.include?('IOError')
589
+ Puma::Util.purge_interrupt_queue
590
+ else
591
+ raise e
1031
592
  end
1032
593
  end
1033
594
  private :notify_safely
@@ -1045,44 +606,24 @@ module Puma
1045
606
  @thread.join if @thread && sync
1046
607
  end
1047
608
 
1048
- def begin_restart
609
+ def begin_restart(sync=false)
1049
610
  notify_safely(RESTART_COMMAND)
1050
- end
1051
-
1052
- def fast_write(io, str)
1053
- n = 0
1054
- while true
1055
- begin
1056
- n = io.syswrite str
1057
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK
1058
- if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
1059
- raise ConnectionError, "Socket timeout writing data"
1060
- end
1061
-
1062
- retry
1063
- rescue Errno::EPIPE, SystemCallError, IOError
1064
- raise ConnectionError, "Socket timeout writing data"
1065
- end
1066
-
1067
- return if n == str.bytesize
1068
- str = str.byteslice(n..-1)
1069
- end
1070
- end
1071
- private :fast_write
1072
-
1073
- ThreadLocalKey = :puma_server
1074
-
1075
- def self.current
1076
- Thread.current[ThreadLocalKey]
611
+ @thread.join if @thread && sync
1077
612
  end
1078
613
 
1079
614
  def shutting_down?
1080
615
  @status == :stop || @status == :restart
1081
616
  end
1082
617
 
1083
- def possible_header_injection?(header_value)
1084
- HTTP_INJECTION_REGEX =~ header_value.to_s
618
+ # List of methods invoked by #stats.
619
+ # @version 5.0.0
620
+ STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
621
+
622
+ # Returns a hash of stats about the running server for reporting purposes.
623
+ # @version 5.0.0
624
+ # @!attribute [r] stats
625
+ def stats
626
+ STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
1085
627
  end
1086
- private :possible_header_injection?
1087
628
  end
1088
629
  end