puma 5.0.2-java → 5.2.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +667 -567
- data/README.md +51 -21
- data/bin/puma-wild +3 -9
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +6 -7
- data/docs/fork_worker.md +2 -0
- data/docs/jungle/README.md +0 -4
- data/docs/jungle/rc.d/puma +2 -2
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +1 -1
- data/docs/restart.md +46 -23
- data/docs/stats.md +142 -0
- data/docs/systemd.md +25 -3
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +18 -5
- data/ext/puma_http11/http11_parser.c +45 -47
- data/ext/puma_http11/http11_parser.java.rl +1 -1
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +199 -119
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
- data/ext/puma_http11/puma_http11.c +25 -12
- data/lib/puma.rb +2 -2
- data/lib/puma/app/status.rb +44 -46
- data/lib/puma/binder.rb +69 -25
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +26 -79
- data/lib/puma/cluster.rb +37 -202
- data/lib/puma/cluster/worker.rb +176 -0
- data/lib/puma/cluster/worker_handle.rb +86 -0
- data/lib/puma/configuration.rb +21 -8
- data/lib/puma/const.rb +11 -3
- data/lib/puma/control_cli.rb +73 -70
- data/lib/puma/dsl.rb +100 -22
- data/lib/puma/error_logger.rb +10 -3
- data/lib/puma/events.rb +18 -3
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +57 -15
- data/lib/puma/minissl.rb +47 -16
- data/lib/puma/minissl/context_builder.rb +6 -0
- data/lib/puma/null_io.rb +4 -0
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/reactor.rb +85 -363
- data/lib/puma/request.rb +451 -0
- data/lib/puma/runner.rb +17 -23
- data/lib/puma/server.rb +164 -553
- data/lib/puma/single.rb +2 -2
- data/lib/puma/state_file.rb +5 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/util.rb +11 -0
- metadata +11 -6
- data/docs/jungle/upstart/README.md +0 -61
- data/docs/jungle/upstart/puma-manager.conf +0 -31
- data/docs/jungle/upstart/puma.conf +0 -69
- data/lib/puma/accept_nonblock.rb +0 -29
data/lib/puma/server.rb
CHANGED
|
@@ -11,6 +11,7 @@ require 'puma/client'
|
|
|
11
11
|
require 'puma/binder'
|
|
12
12
|
require 'puma/util'
|
|
13
13
|
require 'puma/io_buffer'
|
|
14
|
+
require 'puma/request'
|
|
14
15
|
|
|
15
16
|
require 'socket'
|
|
16
17
|
require 'forwardable'
|
|
@@ -30,19 +31,31 @@ module Puma
|
|
|
30
31
|
class Server
|
|
31
32
|
|
|
32
33
|
include Puma::Const
|
|
34
|
+
include Request
|
|
33
35
|
extend Forwardable
|
|
34
36
|
|
|
35
37
|
attr_reader :thread
|
|
36
38
|
attr_reader :events
|
|
37
|
-
attr_reader :
|
|
39
|
+
attr_reader :min_threads, :max_threads # for #stats
|
|
40
|
+
attr_reader :requests_count # @version 5.0.0
|
|
41
|
+
|
|
42
|
+
# @todo the following may be deprecated in the future
|
|
43
|
+
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
|
|
44
|
+
:leak_stack_on_error,
|
|
45
|
+
:persistent_timeout, :reaping_time
|
|
46
|
+
|
|
47
|
+
# @deprecated v6.0.0
|
|
48
|
+
attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
|
|
49
|
+
:leak_stack_on_error, :min_threads, :max_threads,
|
|
50
|
+
:persistent_timeout, :reaping_time
|
|
51
|
+
|
|
38
52
|
attr_accessor :app
|
|
53
|
+
attr_accessor :binder
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
attr_accessor :reaping_time
|
|
45
|
-
attr_accessor :first_data_timeout
|
|
55
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
|
|
56
|
+
:add_unix_listener, :connected_ports
|
|
57
|
+
|
|
58
|
+
ThreadLocalKey = :puma_server
|
|
46
59
|
|
|
47
60
|
# Create a server for the rack app +app+.
|
|
48
61
|
#
|
|
@@ -52,6 +65,10 @@ module Puma
|
|
|
52
65
|
# Server#run returns a thread that you can join on to wait for the server
|
|
53
66
|
# to do its work.
|
|
54
67
|
#
|
|
68
|
+
# @note Several instance variables exist so they are available for testing,
|
|
69
|
+
# and have default values set via +fetch+. Normally the values are set via
|
|
70
|
+
# `::Puma::Configuration.puma_default_options`.
|
|
71
|
+
#
|
|
55
72
|
def initialize(app, events=Events.stdio, options={})
|
|
56
73
|
@app = app
|
|
57
74
|
@events = events
|
|
@@ -59,24 +76,27 @@ module Puma
|
|
|
59
76
|
@check, @notify = nil
|
|
60
77
|
@status = :stop
|
|
61
78
|
|
|
62
|
-
@min_threads = 0
|
|
63
|
-
@max_threads = 16
|
|
64
79
|
@auto_trim_time = 30
|
|
65
80
|
@reaping_time = 1
|
|
66
81
|
|
|
67
82
|
@thread = nil
|
|
68
83
|
@thread_pool = nil
|
|
69
|
-
@early_hints = nil
|
|
70
84
|
|
|
71
|
-
@
|
|
72
|
-
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
|
85
|
+
@options = options
|
|
73
86
|
|
|
74
|
-
@
|
|
87
|
+
@early_hints = options.fetch :early_hints, nil
|
|
88
|
+
@first_data_timeout = options.fetch :first_data_timeout, FIRST_DATA_TIMEOUT
|
|
89
|
+
@min_threads = options.fetch :min_threads, 0
|
|
90
|
+
@max_threads = options.fetch :max_threads , (Puma.mri? ? 5 : 16)
|
|
91
|
+
@persistent_timeout = options.fetch :persistent_timeout, PERSISTENT_TIMEOUT
|
|
92
|
+
@queue_requests = options.fetch :queue_requests, true
|
|
93
|
+
@max_fast_inline = options.fetch :max_fast_inline, MAX_FAST_INLINE
|
|
94
|
+
@io_selector_backend = options.fetch :io_selector_backend, :auto
|
|
75
95
|
|
|
76
|
-
|
|
96
|
+
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
|
97
|
+
@leak_stack_on_error = @options[:environment] ? temp : true
|
|
77
98
|
|
|
78
|
-
@
|
|
79
|
-
@queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
|
|
99
|
+
@binder = Binder.new(events)
|
|
80
100
|
|
|
81
101
|
ENV['RACK_ENV'] ||= "development"
|
|
82
102
|
|
|
@@ -85,19 +105,18 @@ module Puma
|
|
|
85
105
|
@precheck_closing = true
|
|
86
106
|
|
|
87
107
|
@requests_count = 0
|
|
88
|
-
|
|
89
|
-
@shutdown_mutex = Mutex.new
|
|
90
108
|
end
|
|
91
109
|
|
|
92
|
-
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
|
93
|
-
|
|
94
|
-
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
|
|
95
|
-
|
|
96
110
|
def inherit_binder(bind)
|
|
97
111
|
@binder = bind
|
|
98
112
|
end
|
|
99
113
|
|
|
100
114
|
class << self
|
|
115
|
+
# @!attribute [r] current
|
|
116
|
+
def current
|
|
117
|
+
Thread.current[ThreadLocalKey]
|
|
118
|
+
end
|
|
119
|
+
|
|
101
120
|
# :nodoc:
|
|
102
121
|
# @version 5.0.0
|
|
103
122
|
def tcp_cork_supported?
|
|
@@ -172,10 +191,12 @@ module Puma
|
|
|
172
191
|
end
|
|
173
192
|
end
|
|
174
193
|
|
|
194
|
+
# @!attribute [r] backlog
|
|
175
195
|
def backlog
|
|
176
196
|
@thread_pool and @thread_pool.backlog
|
|
177
197
|
end
|
|
178
198
|
|
|
199
|
+
# @!attribute [r] running
|
|
179
200
|
def running
|
|
180
201
|
@thread_pool and @thread_pool.spawned
|
|
181
202
|
end
|
|
@@ -188,6 +209,7 @@ module Puma
|
|
|
188
209
|
# there are 5 threads sitting idle ready to take
|
|
189
210
|
# a request. If one request comes in, then the
|
|
190
211
|
# value would be 4 until it finishes processing.
|
|
212
|
+
# @!attribute [r] pool_capacity
|
|
191
213
|
def pool_capacity
|
|
192
214
|
@thread_pool and @thread_pool.pool_capacity
|
|
193
215
|
end
|
|
@@ -198,67 +220,26 @@ module Puma
|
|
|
198
220
|
# up in the background to handle requests. Otherwise requests
|
|
199
221
|
# are handled synchronously.
|
|
200
222
|
#
|
|
201
|
-
def run(background=true)
|
|
223
|
+
def run(background=true, thread_name: 'server')
|
|
202
224
|
BasicSocket.do_not_reverse_lookup = true
|
|
203
225
|
|
|
204
226
|
@events.fire :state, :booting
|
|
205
227
|
|
|
206
228
|
@status = :run
|
|
207
229
|
|
|
208
|
-
@thread_pool = ThreadPool.new(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
process_now = false
|
|
216
|
-
|
|
217
|
-
begin
|
|
218
|
-
if @queue_requests
|
|
219
|
-
process_now = client.eagerly_finish
|
|
220
|
-
else
|
|
221
|
-
@thread_pool.with_force_shutdown do
|
|
222
|
-
client.finish(@first_data_timeout)
|
|
223
|
-
end
|
|
224
|
-
process_now = true
|
|
225
|
-
end
|
|
226
|
-
rescue MiniSSL::SSLError => e
|
|
227
|
-
@events.ssl_error e, client.io
|
|
228
|
-
client.close
|
|
229
|
-
|
|
230
|
-
rescue HttpParserError => e
|
|
231
|
-
client.write_error(400)
|
|
232
|
-
client.close
|
|
233
|
-
|
|
234
|
-
@events.parse_error e, client
|
|
235
|
-
rescue EOFError => e
|
|
236
|
-
client.close
|
|
237
|
-
|
|
238
|
-
# Swallow, do not log
|
|
239
|
-
rescue ConnectionError, ThreadPool::ForceShutdown => e
|
|
240
|
-
client.close
|
|
241
|
-
|
|
242
|
-
@events.connection_error e, client
|
|
243
|
-
else
|
|
244
|
-
process_now ||= @shutdown_mutex.synchronize do
|
|
245
|
-
next true unless @queue_requests
|
|
246
|
-
client.set_timeout @first_data_timeout
|
|
247
|
-
@reactor.add client
|
|
248
|
-
false
|
|
249
|
-
end
|
|
250
|
-
process_client client, buffer if process_now
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
process_now
|
|
254
|
-
end
|
|
230
|
+
@thread_pool = ThreadPool.new(
|
|
231
|
+
@min_threads,
|
|
232
|
+
@max_threads,
|
|
233
|
+
::Puma::IOBuffer,
|
|
234
|
+
&method(:process_client)
|
|
235
|
+
)
|
|
255
236
|
|
|
256
237
|
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
|
257
238
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
|
258
239
|
|
|
259
240
|
if @queue_requests
|
|
260
|
-
@reactor = Reactor.new
|
|
261
|
-
@reactor.
|
|
241
|
+
@reactor = Reactor.new(@io_selector_backend, &method(:reactor_wakeup))
|
|
242
|
+
@reactor.run
|
|
262
243
|
end
|
|
263
244
|
|
|
264
245
|
if @reaping_time
|
|
@@ -269,11 +250,13 @@ module Puma
|
|
|
269
250
|
@thread_pool.auto_trim!(@auto_trim_time)
|
|
270
251
|
end
|
|
271
252
|
|
|
253
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
|
254
|
+
|
|
272
255
|
@events.fire :state, :running
|
|
273
256
|
|
|
274
257
|
if background
|
|
275
258
|
@thread = Thread.new do
|
|
276
|
-
Puma.set_thread_name
|
|
259
|
+
Puma.set_thread_name thread_name
|
|
277
260
|
handle_servers
|
|
278
261
|
end
|
|
279
262
|
return @thread
|
|
@@ -282,8 +265,45 @@ module Puma
|
|
|
282
265
|
end
|
|
283
266
|
end
|
|
284
267
|
|
|
268
|
+
# This method is called from the Reactor thread when a queued Client receives data,
|
|
269
|
+
# times out, or when the Reactor is shutting down.
|
|
270
|
+
#
|
|
271
|
+
# It is responsible for ensuring that a request has been completely received
|
|
272
|
+
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
|
273
|
+
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
|
274
|
+
# such as nginx) then the application would be subject to a slow client attack.
|
|
275
|
+
#
|
|
276
|
+
# 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).
|
|
277
|
+
#
|
|
278
|
+
# The method checks to see if it has the full header and body with
|
|
279
|
+
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
|
280
|
+
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
|
281
|
+
# so that a "worker thread" can pick up the request and begin to execute application logic.
|
|
282
|
+
# The Client is then removed from the reactor (return `true`).
|
|
283
|
+
#
|
|
284
|
+
# If a client object times out, a 408 response is written, its connection is closed,
|
|
285
|
+
# and the object is removed from the reactor (return `true`).
|
|
286
|
+
#
|
|
287
|
+
# If the Reactor is shutting down, all Clients are either timed out or passed to the
|
|
288
|
+
# ThreadPool, depending on their current state (#can_close?).
|
|
289
|
+
#
|
|
290
|
+
# Otherwise, if the full request is not ready then the client will remain in the reactor
|
|
291
|
+
# (return `false`). When the client sends more data to the socket the `Puma::Client` object
|
|
292
|
+
# will wake up and again be checked to see if it's ready to be passed to the thread pool.
|
|
293
|
+
def reactor_wakeup(client)
|
|
294
|
+
shutdown = !@queue_requests
|
|
295
|
+
if client.try_to_finish || (shutdown && !client.can_close?)
|
|
296
|
+
@thread_pool << client
|
|
297
|
+
elsif shutdown || client.timeout == 0
|
|
298
|
+
client.timeout!
|
|
299
|
+
end
|
|
300
|
+
rescue StandardError => e
|
|
301
|
+
client_error(e, client)
|
|
302
|
+
client.close
|
|
303
|
+
true
|
|
304
|
+
end
|
|
305
|
+
|
|
285
306
|
def handle_servers
|
|
286
|
-
@check, @notify = Puma::Util.pipe unless @notify
|
|
287
307
|
begin
|
|
288
308
|
check = @check
|
|
289
309
|
sockets = [check] + @binder.ios
|
|
@@ -333,10 +353,7 @@ module Puma
|
|
|
333
353
|
@events.fire :state, @status
|
|
334
354
|
|
|
335
355
|
if queue_requests
|
|
336
|
-
@
|
|
337
|
-
@queue_requests = false
|
|
338
|
-
end
|
|
339
|
-
@reactor.clear!
|
|
356
|
+
@queue_requests = false
|
|
340
357
|
@reactor.shutdown
|
|
341
358
|
end
|
|
342
359
|
graceful_shutdown if @status == :stop || @status == :restart
|
|
@@ -376,27 +393,48 @@ module Puma
|
|
|
376
393
|
return false
|
|
377
394
|
end
|
|
378
395
|
|
|
379
|
-
# Given a connection on +client+, handle the incoming requests
|
|
396
|
+
# Given a connection on +client+, handle the incoming requests,
|
|
397
|
+
# or queue the connection in the Reactor if no request is available.
|
|
398
|
+
#
|
|
399
|
+
# This method is called from a ThreadPool worker thread.
|
|
380
400
|
#
|
|
381
|
-
# This method
|
|
401
|
+
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
|
382
402
|
# indicates that it supports keep alive, wait for another request before
|
|
383
403
|
# returning.
|
|
384
404
|
#
|
|
405
|
+
# Return true if one or more requests were processed.
|
|
385
406
|
def process_client(client, buffer)
|
|
407
|
+
# Advertise this server into the thread
|
|
408
|
+
Thread.current[ThreadLocalKey] = self
|
|
409
|
+
|
|
410
|
+
clean_thread_locals = @options[:clean_thread_locals]
|
|
411
|
+
close_socket = true
|
|
412
|
+
|
|
413
|
+
requests = 0
|
|
414
|
+
|
|
386
415
|
begin
|
|
416
|
+
if @queue_requests &&
|
|
417
|
+
!client.eagerly_finish
|
|
387
418
|
|
|
388
|
-
|
|
389
|
-
|
|
419
|
+
client.set_timeout(@first_data_timeout)
|
|
420
|
+
if @reactor.add client
|
|
421
|
+
close_socket = false
|
|
422
|
+
return false
|
|
423
|
+
end
|
|
424
|
+
end
|
|
390
425
|
|
|
391
|
-
|
|
426
|
+
with_force_shutdown(client) do
|
|
427
|
+
client.finish(@first_data_timeout)
|
|
428
|
+
end
|
|
392
429
|
|
|
393
430
|
while true
|
|
431
|
+
@requests_count += 1
|
|
394
432
|
case handle_request(client, buffer)
|
|
395
433
|
when false
|
|
396
|
-
|
|
434
|
+
break
|
|
397
435
|
when :async
|
|
398
436
|
close_socket = false
|
|
399
|
-
|
|
437
|
+
break
|
|
400
438
|
when true
|
|
401
439
|
buffer.reset
|
|
402
440
|
|
|
@@ -406,55 +444,33 @@ module Puma
|
|
|
406
444
|
|
|
407
445
|
check_for_more_data = @status == :run
|
|
408
446
|
|
|
409
|
-
if requests >=
|
|
447
|
+
if requests >= @max_fast_inline
|
|
410
448
|
# This will mean that reset will only try to use the data it already
|
|
411
449
|
# has buffered and won't try to read more data. What this means is that
|
|
412
450
|
# every client, independent of their request speed, gets treated like a slow
|
|
413
|
-
# one once every
|
|
451
|
+
# one once every max_fast_inline requests.
|
|
414
452
|
check_for_more_data = false
|
|
415
453
|
end
|
|
416
454
|
|
|
417
|
-
next_request_ready =
|
|
455
|
+
next_request_ready = with_force_shutdown(client) do
|
|
418
456
|
client.reset(check_for_more_data)
|
|
419
457
|
end
|
|
420
458
|
|
|
421
459
|
unless next_request_ready
|
|
422
|
-
@
|
|
423
|
-
|
|
460
|
+
break unless @queue_requests
|
|
461
|
+
client.set_timeout @persistent_timeout
|
|
462
|
+
if @reactor.add client
|
|
424
463
|
close_socket = false
|
|
425
|
-
|
|
426
|
-
@reactor.add client
|
|
427
|
-
return
|
|
464
|
+
break
|
|
428
465
|
end
|
|
429
466
|
end
|
|
430
467
|
end
|
|
431
468
|
end
|
|
432
|
-
|
|
433
|
-
# The client disconnected while we were reading data
|
|
434
|
-
rescue ConnectionError, ThreadPool::ForceShutdown
|
|
435
|
-
# Swallow them. The ensure tries to close +client+ down
|
|
436
|
-
|
|
437
|
-
# SSL handshake error
|
|
438
|
-
rescue MiniSSL::SSLError => e
|
|
439
|
-
lowlevel_error e, client.env
|
|
440
|
-
@events.ssl_error e, client.io
|
|
441
|
-
close_socket = true
|
|
442
|
-
|
|
443
|
-
# The client doesn't know HTTP well
|
|
444
|
-
rescue HttpParserError => e
|
|
445
|
-
lowlevel_error(e, client.env)
|
|
446
|
-
|
|
447
|
-
client.write_error(400)
|
|
448
|
-
|
|
449
|
-
@events.parse_error e, client
|
|
450
|
-
|
|
451
|
-
# Server error
|
|
469
|
+
true
|
|
452
470
|
rescue StandardError => e
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
@events.unknown_error e, nil, "Read"
|
|
471
|
+
client_error(e, client)
|
|
472
|
+
# The ensure tries to close +client+ down
|
|
473
|
+
requests > 0
|
|
458
474
|
ensure
|
|
459
475
|
buffer.reset
|
|
460
476
|
|
|
@@ -469,403 +485,32 @@ module Puma
|
|
|
469
485
|
end
|
|
470
486
|
end
|
|
471
487
|
|
|
472
|
-
#
|
|
473
|
-
#
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
env[SERVER_NAME] = host[0, colon]
|
|
479
|
-
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
|
480
|
-
else
|
|
481
|
-
env[SERVER_NAME] = host
|
|
482
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
483
|
-
end
|
|
484
|
-
else
|
|
485
|
-
env[SERVER_NAME] = LOCALHOST
|
|
486
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
unless env[REQUEST_PATH]
|
|
490
|
-
# it might be a dumbass full host request header
|
|
491
|
-
uri = URI.parse(env[REQUEST_URI])
|
|
492
|
-
env[REQUEST_PATH] = uri.path
|
|
493
|
-
|
|
494
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
|
495
|
-
|
|
496
|
-
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
|
497
|
-
# so only set the env value if there actually is a value.
|
|
498
|
-
env[QUERY_STRING] = uri.query if uri.query
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
|
502
|
-
|
|
503
|
-
# From https://www.ietf.org/rfc/rfc3875 :
|
|
504
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
|
505
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
506
|
-
# may not identify the ultimate source of the request.
|
|
507
|
-
# They identify the client for the immediate request to the
|
|
508
|
-
# server; that client may be a proxy, gateway, or other
|
|
509
|
-
# intermediary acting on behalf of the actual source client."
|
|
510
|
-
#
|
|
511
|
-
|
|
512
|
-
unless env.key?(REMOTE_ADDR)
|
|
513
|
-
begin
|
|
514
|
-
addr = client.peerip
|
|
515
|
-
rescue Errno::ENOTCONN
|
|
516
|
-
# Client disconnects can result in an inability to get the
|
|
517
|
-
# peeraddr from the socket; default to localhost.
|
|
518
|
-
addr = LOCALHOST_IP
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
# Set unix socket addrs to localhost
|
|
522
|
-
addr = LOCALHOST_IP if addr.empty?
|
|
523
|
-
|
|
524
|
-
env[REMOTE_ADDR] = addr
|
|
525
|
-
end
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
def default_server_port(env)
|
|
529
|
-
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"
|
|
530
|
-
PORT_443
|
|
531
|
-
else
|
|
532
|
-
PORT_80
|
|
533
|
-
end
|
|
534
|
-
end
|
|
535
|
-
|
|
536
|
-
# Takes the request +req+, invokes the Rack application to construct
|
|
537
|
-
# the response and writes it back to +req.io+.
|
|
538
|
-
#
|
|
539
|
-
# The second parameter +lines+ is a IO-like object unique to this thread.
|
|
540
|
-
# This is normally an instance of Puma::IOBuffer.
|
|
541
|
-
#
|
|
542
|
-
# It'll return +false+ when the connection is closed, this doesn't mean
|
|
543
|
-
# that the response wasn't successful.
|
|
544
|
-
#
|
|
545
|
-
# It'll return +:async+ if the connection remains open but will be handled
|
|
546
|
-
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
|
547
|
-
#
|
|
548
|
-
# Finally, it'll return +true+ on keep-alive connections.
|
|
549
|
-
def handle_request(req, lines)
|
|
550
|
-
@requests_count +=1
|
|
551
|
-
|
|
552
|
-
env = req.env
|
|
553
|
-
client = req.io
|
|
554
|
-
|
|
555
|
-
return false if closed_socket?(client)
|
|
556
|
-
|
|
557
|
-
normalize_env env, req
|
|
558
|
-
|
|
559
|
-
env[PUMA_SOCKET] = client
|
|
560
|
-
|
|
561
|
-
if env[HTTPS_KEY] && client.peercert
|
|
562
|
-
env[PUMA_PEERCERT] = client.peercert
|
|
563
|
-
end
|
|
564
|
-
|
|
565
|
-
env[HIJACK_P] = true
|
|
566
|
-
env[HIJACK] = req
|
|
567
|
-
|
|
568
|
-
body = req.body
|
|
569
|
-
|
|
570
|
-
head = env[REQUEST_METHOD] == HEAD
|
|
571
|
-
|
|
572
|
-
env[RACK_INPUT] = body
|
|
573
|
-
env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
|
574
|
-
|
|
575
|
-
if @early_hints
|
|
576
|
-
env[EARLY_HINTS] = lambda { |headers|
|
|
577
|
-
begin
|
|
578
|
-
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
|
579
|
-
|
|
580
|
-
headers.each_pair do |k, vs|
|
|
581
|
-
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
|
582
|
-
vs.to_s.split(NEWLINE).each do |v|
|
|
583
|
-
next if possible_header_injection?(v)
|
|
584
|
-
fast_write client, "#{k}: #{v}\r\n"
|
|
585
|
-
end
|
|
586
|
-
else
|
|
587
|
-
fast_write client, "#{k}: #{vs}\r\n"
|
|
588
|
-
end
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
fast_write client, "\r\n".freeze
|
|
592
|
-
rescue ConnectionError => e
|
|
593
|
-
@events.debug_error e
|
|
594
|
-
# noop, if we lost the socket we just won't send the early hints
|
|
595
|
-
end
|
|
596
|
-
}
|
|
597
|
-
end
|
|
598
|
-
|
|
599
|
-
# Fixup any headers with , in the name to have _ now. We emit
|
|
600
|
-
# headers with , in them during the parse phase to avoid ambiguity
|
|
601
|
-
# with the - to _ conversion for critical headers. But here for
|
|
602
|
-
# compatibility, we'll convert them back. This code is written to
|
|
603
|
-
# avoid allocation in the common case (ie there are no headers
|
|
604
|
-
# with , in their names), that's why it has the extra conditionals.
|
|
605
|
-
|
|
606
|
-
to_delete = nil
|
|
607
|
-
to_add = nil
|
|
608
|
-
|
|
609
|
-
env.each do |k,v|
|
|
610
|
-
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
|
611
|
-
if to_delete
|
|
612
|
-
to_delete << k
|
|
613
|
-
else
|
|
614
|
-
to_delete = [k]
|
|
615
|
-
end
|
|
616
|
-
|
|
617
|
-
unless to_add
|
|
618
|
-
to_add = {}
|
|
619
|
-
end
|
|
620
|
-
|
|
621
|
-
to_add[k.tr(",", "_")] = v
|
|
622
|
-
end
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
if to_delete
|
|
626
|
-
to_delete.each { |k| env.delete(k) }
|
|
627
|
-
env.merge! to_add
|
|
628
|
-
end
|
|
629
|
-
|
|
630
|
-
# A rack extension. If the app writes #call'ables to this
|
|
631
|
-
# array, we will invoke them when the request is done.
|
|
632
|
-
#
|
|
633
|
-
after_reply = env[RACK_AFTER_REPLY] = []
|
|
634
|
-
|
|
635
|
-
begin
|
|
636
|
-
begin
|
|
637
|
-
status, headers, res_body = @thread_pool.with_force_shutdown do
|
|
638
|
-
@app.call(env)
|
|
639
|
-
end
|
|
640
|
-
|
|
641
|
-
return :async if req.hijacked
|
|
642
|
-
|
|
643
|
-
status = status.to_i
|
|
644
|
-
|
|
645
|
-
if status == -1
|
|
646
|
-
unless headers.empty? and res_body == []
|
|
647
|
-
raise "async response must have empty headers and body"
|
|
648
|
-
end
|
|
649
|
-
|
|
650
|
-
return :async
|
|
651
|
-
end
|
|
652
|
-
rescue ThreadPool::ForceShutdown => e
|
|
653
|
-
@events.unknown_error e, req, "Rack app"
|
|
654
|
-
@events.log "Detected force shutdown of a thread"
|
|
655
|
-
|
|
656
|
-
status, headers, res_body = lowlevel_error(e, env, 503)
|
|
657
|
-
rescue Exception => e
|
|
658
|
-
@events.unknown_error e, req, "Rack app"
|
|
659
|
-
|
|
660
|
-
status, headers, res_body = lowlevel_error(e, env, 500)
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
content_length = nil
|
|
664
|
-
no_body = head
|
|
665
|
-
|
|
666
|
-
if res_body.kind_of? Array and res_body.size == 1
|
|
667
|
-
content_length = res_body[0].bytesize
|
|
668
|
-
end
|
|
669
|
-
|
|
670
|
-
cork_socket client
|
|
671
|
-
|
|
672
|
-
line_ending = LINE_END
|
|
673
|
-
colon = COLON
|
|
674
|
-
|
|
675
|
-
http_11 = env[HTTP_VERSION] == HTTP_11
|
|
676
|
-
if http_11
|
|
677
|
-
allow_chunked = true
|
|
678
|
-
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
|
679
|
-
|
|
680
|
-
# An optimization. The most common response is 200, so we can
|
|
681
|
-
# reply with the proper 200 status without having to compute
|
|
682
|
-
# the response header.
|
|
683
|
-
#
|
|
684
|
-
if status == 200
|
|
685
|
-
lines << HTTP_11_200
|
|
686
|
-
else
|
|
687
|
-
lines.append "HTTP/1.1 ", status.to_s, " ",
|
|
688
|
-
fetch_status_code(status), line_ending
|
|
689
|
-
|
|
690
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
691
|
-
end
|
|
692
|
-
else
|
|
693
|
-
allow_chunked = false
|
|
694
|
-
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
|
695
|
-
|
|
696
|
-
# Same optimization as above for HTTP/1.1
|
|
697
|
-
#
|
|
698
|
-
if status == 200
|
|
699
|
-
lines << HTTP_10_200
|
|
700
|
-
else
|
|
701
|
-
lines.append "HTTP/1.0 ", status.to_s, " ",
|
|
702
|
-
fetch_status_code(status), line_ending
|
|
703
|
-
|
|
704
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
|
705
|
-
end
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
# regardless of what the client wants, we always close the connection
|
|
709
|
-
# if running without request queueing
|
|
710
|
-
keep_alive &&= @queue_requests
|
|
711
|
-
|
|
712
|
-
response_hijack = nil
|
|
713
|
-
|
|
714
|
-
headers.each do |k, vs|
|
|
715
|
-
case k.downcase
|
|
716
|
-
when CONTENT_LENGTH2
|
|
717
|
-
next if possible_header_injection?(vs)
|
|
718
|
-
content_length = vs
|
|
719
|
-
next
|
|
720
|
-
when TRANSFER_ENCODING
|
|
721
|
-
allow_chunked = false
|
|
722
|
-
content_length = nil
|
|
723
|
-
when HIJACK
|
|
724
|
-
response_hijack = vs
|
|
725
|
-
next
|
|
726
|
-
end
|
|
727
|
-
|
|
728
|
-
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
|
729
|
-
vs.to_s.split(NEWLINE).each do |v|
|
|
730
|
-
next if possible_header_injection?(v)
|
|
731
|
-
lines.append k, colon, v, line_ending
|
|
732
|
-
end
|
|
733
|
-
else
|
|
734
|
-
lines.append k, colon, line_ending
|
|
735
|
-
end
|
|
736
|
-
end
|
|
737
|
-
|
|
738
|
-
# HTTP/1.1 & 1.0 assume different defaults:
|
|
739
|
-
# - HTTP 1.0 assumes the connection will be closed if not specified
|
|
740
|
-
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
|
741
|
-
# Only set the header if we're doing something which is not the default
|
|
742
|
-
# for this protocol version
|
|
743
|
-
if http_11
|
|
744
|
-
lines << CONNECTION_CLOSE if !keep_alive
|
|
745
|
-
else
|
|
746
|
-
lines << CONNECTION_KEEP_ALIVE if keep_alive
|
|
747
|
-
end
|
|
748
|
-
|
|
749
|
-
if no_body
|
|
750
|
-
if content_length and status != 204
|
|
751
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
|
752
|
-
end
|
|
753
|
-
|
|
754
|
-
lines << line_ending
|
|
755
|
-
fast_write client, lines.to_s
|
|
756
|
-
return keep_alive
|
|
757
|
-
end
|
|
758
|
-
|
|
759
|
-
if content_length
|
|
760
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
|
761
|
-
chunked = false
|
|
762
|
-
elsif !response_hijack and allow_chunked
|
|
763
|
-
lines << TRANSFER_ENCODING_CHUNKED
|
|
764
|
-
chunked = true
|
|
765
|
-
end
|
|
766
|
-
|
|
767
|
-
lines << line_ending
|
|
768
|
-
|
|
769
|
-
fast_write client, lines.to_s
|
|
770
|
-
|
|
771
|
-
if response_hijack
|
|
772
|
-
response_hijack.call client
|
|
773
|
-
return :async
|
|
774
|
-
end
|
|
775
|
-
|
|
776
|
-
begin
|
|
777
|
-
res_body.each do |part|
|
|
778
|
-
next if part.bytesize.zero?
|
|
779
|
-
if chunked
|
|
780
|
-
fast_write client, part.bytesize.to_s(16)
|
|
781
|
-
fast_write client, line_ending
|
|
782
|
-
fast_write client, part
|
|
783
|
-
fast_write client, line_ending
|
|
784
|
-
else
|
|
785
|
-
fast_write client, part
|
|
786
|
-
end
|
|
787
|
-
|
|
788
|
-
client.flush
|
|
789
|
-
end
|
|
790
|
-
|
|
791
|
-
if chunked
|
|
792
|
-
fast_write client, CLOSE_CHUNKED
|
|
793
|
-
client.flush
|
|
794
|
-
end
|
|
795
|
-
rescue SystemCallError, IOError
|
|
796
|
-
raise ConnectionError, "Connection error detected during write"
|
|
797
|
-
end
|
|
798
|
-
|
|
799
|
-
ensure
|
|
800
|
-
uncork_socket client
|
|
801
|
-
|
|
802
|
-
body.close
|
|
803
|
-
req.tempfile.unlink if req.tempfile
|
|
804
|
-
res_body.close if res_body.respond_to? :close
|
|
805
|
-
|
|
806
|
-
after_reply.each { |o| o.call }
|
|
807
|
-
end
|
|
808
|
-
|
|
809
|
-
return keep_alive
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
def fetch_status_code(status)
|
|
813
|
-
HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
|
|
488
|
+
# Triggers a client timeout if the thread-pool shuts down
|
|
489
|
+
# during execution of the provided block.
|
|
490
|
+
def with_force_shutdown(client, &block)
|
|
491
|
+
@thread_pool.with_force_shutdown(&block)
|
|
492
|
+
rescue ThreadPool::ForceShutdown
|
|
493
|
+
client.timeout!
|
|
814
494
|
end
|
|
815
|
-
private :fetch_status_code
|
|
816
|
-
|
|
817
|
-
# Given the request +env+ from +client+ and the partial body +body+
|
|
818
|
-
# plus a potential Content-Length value +cl+, finish reading
|
|
819
|
-
# the body and return it.
|
|
820
|
-
#
|
|
821
|
-
# If the body is larger than MAX_BODY, a Tempfile object is used
|
|
822
|
-
# for the body, otherwise a StringIO is used.
|
|
823
|
-
#
|
|
824
|
-
def read_body(env, client, body, cl)
|
|
825
|
-
content_length = cl.to_i
|
|
826
495
|
|
|
827
|
-
|
|
496
|
+
# :nocov:
|
|
828
497
|
|
|
829
|
-
|
|
498
|
+
# Handle various error types thrown by Client I/O operations.
|
|
499
|
+
def client_error(e, client)
|
|
500
|
+
# Swallow, do not log
|
|
501
|
+
return if [ConnectionError, EOFError].include?(e.class)
|
|
830
502
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
503
|
+
lowlevel_error(e, client.env)
|
|
504
|
+
case e
|
|
505
|
+
when MiniSSL::SSLError
|
|
506
|
+
@events.ssl_error e, client.io
|
|
507
|
+
when HttpParserError
|
|
508
|
+
client.write_error(400)
|
|
509
|
+
@events.parse_error e, client
|
|
835
510
|
else
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
stream = StringIO.new body[0,0]
|
|
839
|
-
end
|
|
840
|
-
|
|
841
|
-
stream.write body
|
|
842
|
-
|
|
843
|
-
# Read an odd sized chunk so we can read even sized ones
|
|
844
|
-
# after this
|
|
845
|
-
chunk = client.readpartial(remain % CHUNK_SIZE)
|
|
846
|
-
|
|
847
|
-
# No chunk means a closed socket
|
|
848
|
-
unless chunk
|
|
849
|
-
stream.close
|
|
850
|
-
return nil
|
|
851
|
-
end
|
|
852
|
-
|
|
853
|
-
remain -= stream.write(chunk)
|
|
854
|
-
|
|
855
|
-
# Raed the rest of the chunks
|
|
856
|
-
while remain > 0
|
|
857
|
-
chunk = client.readpartial(CHUNK_SIZE)
|
|
858
|
-
unless chunk
|
|
859
|
-
stream.close
|
|
860
|
-
return nil
|
|
861
|
-
end
|
|
862
|
-
|
|
863
|
-
remain -= stream.write(chunk)
|
|
511
|
+
client.write_error(500)
|
|
512
|
+
@events.unknown_error e, nil, "Read"
|
|
864
513
|
end
|
|
865
|
-
|
|
866
|
-
stream.rewind
|
|
867
|
-
|
|
868
|
-
return stream
|
|
869
514
|
end
|
|
870
515
|
|
|
871
516
|
# A fallback rack response if +@app+ raises as exception.
|
|
@@ -942,19 +587,16 @@ module Puma
|
|
|
942
587
|
end
|
|
943
588
|
|
|
944
589
|
def notify_safely(message)
|
|
945
|
-
@
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
590
|
+
@notify << message
|
|
591
|
+
rescue IOError, NoMethodError, Errno::EPIPE
|
|
592
|
+
# The server, in another thread, is shutting down
|
|
593
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
594
|
+
rescue RuntimeError => e
|
|
595
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
|
596
|
+
if e.message.include?('IOError')
|
|
950
597
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
if e.message.include?('IOError')
|
|
954
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
955
|
-
else
|
|
956
|
-
raise e
|
|
957
|
-
end
|
|
598
|
+
else
|
|
599
|
+
raise e
|
|
958
600
|
end
|
|
959
601
|
end
|
|
960
602
|
private :notify_safely
|
|
@@ -977,48 +619,17 @@ module Puma
|
|
|
977
619
|
@thread.join if @thread && sync
|
|
978
620
|
end
|
|
979
621
|
|
|
980
|
-
def fast_write(io, str)
|
|
981
|
-
n = 0
|
|
982
|
-
while true
|
|
983
|
-
begin
|
|
984
|
-
n = io.syswrite str
|
|
985
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
986
|
-
if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
|
|
987
|
-
raise ConnectionError, "Socket timeout writing data"
|
|
988
|
-
end
|
|
989
|
-
|
|
990
|
-
retry
|
|
991
|
-
rescue Errno::EPIPE, SystemCallError, IOError
|
|
992
|
-
raise ConnectionError, "Socket timeout writing data"
|
|
993
|
-
end
|
|
994
|
-
|
|
995
|
-
return if n == str.bytesize
|
|
996
|
-
str = str.byteslice(n..-1)
|
|
997
|
-
end
|
|
998
|
-
end
|
|
999
|
-
private :fast_write
|
|
1000
|
-
|
|
1001
|
-
ThreadLocalKey = :puma_server
|
|
1002
|
-
|
|
1003
|
-
def self.current
|
|
1004
|
-
Thread.current[ThreadLocalKey]
|
|
1005
|
-
end
|
|
1006
|
-
|
|
1007
622
|
def shutting_down?
|
|
1008
623
|
@status == :stop || @status == :restart
|
|
1009
624
|
end
|
|
1010
625
|
|
|
1011
|
-
def possible_header_injection?(header_value)
|
|
1012
|
-
HTTP_INJECTION_REGEX =~ header_value.to_s
|
|
1013
|
-
end
|
|
1014
|
-
private :possible_header_injection?
|
|
1015
|
-
|
|
1016
626
|
# List of methods invoked by #stats.
|
|
1017
627
|
# @version 5.0.0
|
|
1018
628
|
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
|
1019
629
|
|
|
1020
630
|
# Returns a hash of stats about the running server for reporting purposes.
|
|
1021
631
|
# @version 5.0.0
|
|
632
|
+
# @!attribute [r] stats
|
|
1022
633
|
def stats
|
|
1023
634
|
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
|
1024
635
|
end
|