puma 5.0.0.beta1-java → 5.0.3-java
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +1188 -559
- data/README.md +15 -8
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +10 -7
- data/docs/jungle/README.md +0 -4
- data/docs/jungle/rc.d/puma +2 -2
- data/docs/nginx.md +1 -1
- data/docs/restart.md +46 -23
- data/docs/signals.md +7 -7
- data/docs/systemd.md +1 -1
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/http11_parser.c +3 -1
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/mini_ssl.c +53 -38
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +22 -11
- data/lib/puma.rb +16 -0
- data/lib/puma/app/status.rb +47 -44
- data/lib/puma/binder.rb +40 -12
- data/lib/puma/client.rb +68 -82
- data/lib/puma/cluster.rb +30 -187
- data/lib/puma/cluster/worker.rb +170 -0
- data/lib/puma/cluster/worker_handle.rb +83 -0
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +9 -7
- data/lib/puma/const.rb +2 -1
- data/lib/puma/control_cli.rb +2 -0
- data/lib/puma/detect.rb +9 -0
- data/lib/puma/dsl.rb +77 -39
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +37 -31
- data/lib/puma/launcher.rb +20 -10
- data/lib/puma/minissl.rb +55 -10
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/reactor.rb +77 -373
- data/lib/puma/request.rb +438 -0
- data/lib/puma/runner.rb +7 -19
- data/lib/puma/server.rb +229 -506
- data/lib/puma/single.rb +3 -2
- data/lib/puma/state_file.rb +1 -1
- data/lib/puma/thread_pool.rb +32 -5
- data/lib/puma/util.rb +12 -0
- metadata +12 -10
- 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
@@ -9,11 +9,9 @@ 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
13
|
require 'puma/io_buffer'
|
15
|
-
|
16
|
-
require 'puma/puma_http11'
|
14
|
+
require 'puma/request'
|
17
15
|
|
18
16
|
require 'socket'
|
19
17
|
require 'forwardable'
|
@@ -33,19 +31,31 @@ module Puma
|
|
33
31
|
class Server
|
34
32
|
|
35
33
|
include Puma::Const
|
34
|
+
include Request
|
36
35
|
extend Forwardable
|
37
36
|
|
38
37
|
attr_reader :thread
|
39
38
|
attr_reader :events
|
40
|
-
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
|
+
|
41
52
|
attr_accessor :app
|
53
|
+
attr_accessor :binder
|
42
54
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
attr_accessor :reaping_time
|
48
|
-
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
|
49
59
|
|
50
60
|
# Create a server for the rack app +app+.
|
51
61
|
#
|
@@ -55,6 +65,10 @@ module Puma
|
|
55
65
|
# Server#run returns a thread that you can join on to wait for the server
|
56
66
|
# to do its work.
|
57
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
|
+
#
|
58
72
|
def initialize(app, events=Events.stdio, options={})
|
59
73
|
@app = app
|
60
74
|
@events = events
|
@@ -62,24 +76,25 @@ module Puma
|
|
62
76
|
@check, @notify = nil
|
63
77
|
@status = :stop
|
64
78
|
|
65
|
-
@min_threads = 0
|
66
|
-
@max_threads = 16
|
67
79
|
@auto_trim_time = 30
|
68
80
|
@reaping_time = 1
|
69
81
|
|
70
82
|
@thread = nil
|
71
83
|
@thread_pool = nil
|
72
|
-
@early_hints = nil
|
73
84
|
|
74
|
-
@
|
75
|
-
@first_data_timeout = options.fetch(:first_data_timeout, FIRST_DATA_TIMEOUT)
|
85
|
+
@options = options
|
76
86
|
|
77
|
-
@
|
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
|
78
93
|
|
79
|
-
|
94
|
+
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
95
|
+
@leak_stack_on_error = @options[:environment] ? temp : true
|
80
96
|
|
81
|
-
@
|
82
|
-
@queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
|
97
|
+
@binder = Binder.new(events)
|
83
98
|
|
84
99
|
ENV['RACK_ENV'] ||= "development"
|
85
100
|
|
@@ -90,18 +105,39 @@ module Puma
|
|
90
105
|
@requests_count = 0
|
91
106
|
end
|
92
107
|
|
93
|
-
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
94
|
-
|
95
|
-
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
|
96
|
-
|
97
108
|
def inherit_binder(bind)
|
98
109
|
@binder = bind
|
99
110
|
end
|
100
111
|
|
112
|
+
class << self
|
113
|
+
# @!attribute [r] current
|
114
|
+
def current
|
115
|
+
Thread.current[ThreadLocalKey]
|
116
|
+
end
|
117
|
+
|
118
|
+
# :nodoc:
|
119
|
+
# @version 5.0.0
|
120
|
+
def tcp_cork_supported?
|
121
|
+
RbConfig::CONFIG['host_os'] =~ /linux/ &&
|
122
|
+
Socket.const_defined?(:IPPROTO_TCP) &&
|
123
|
+
Socket.const_defined?(:TCP_CORK)
|
124
|
+
end
|
125
|
+
|
126
|
+
# :nodoc:
|
127
|
+
# @version 5.0.0
|
128
|
+
def closed_socket_supported?
|
129
|
+
RbConfig::CONFIG['host_os'] =~ /linux/ &&
|
130
|
+
Socket.const_defined?(:IPPROTO_TCP) &&
|
131
|
+
Socket.const_defined?(:TCP_INFO)
|
132
|
+
end
|
133
|
+
private :tcp_cork_supported?
|
134
|
+
private :closed_socket_supported?
|
135
|
+
end
|
136
|
+
|
101
137
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
102
138
|
# packetizes our stream. This improves both latency and throughput.
|
103
139
|
#
|
104
|
-
if
|
140
|
+
if tcp_cork_supported?
|
105
141
|
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
106
142
|
|
107
143
|
# 6 == Socket::IPPROTO_TCP
|
@@ -109,7 +145,7 @@ module Puma
|
|
109
145
|
# 1/0 == turn on/off
|
110
146
|
def cork_socket(socket)
|
111
147
|
begin
|
112
|
-
socket.setsockopt(
|
148
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
|
113
149
|
rescue IOError, SystemCallError
|
114
150
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
115
151
|
end
|
@@ -117,18 +153,26 @@ module Puma
|
|
117
153
|
|
118
154
|
def uncork_socket(socket)
|
119
155
|
begin
|
120
|
-
socket.setsockopt(
|
156
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
|
121
157
|
rescue IOError, SystemCallError
|
122
158
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
123
159
|
end
|
124
160
|
end
|
161
|
+
else
|
162
|
+
def cork_socket(socket)
|
163
|
+
end
|
125
164
|
|
165
|
+
def uncork_socket(socket)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
if closed_socket_supported?
|
126
170
|
def closed_socket?(socket)
|
127
171
|
return false unless socket.kind_of? TCPSocket
|
128
172
|
return false unless @precheck_closing
|
129
173
|
|
130
174
|
begin
|
131
|
-
tcp_info = socket.getsockopt(Socket::
|
175
|
+
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
132
176
|
rescue IOError, SystemCallError
|
133
177
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
134
178
|
@precheck_closing = false
|
@@ -140,21 +184,17 @@ module Puma
|
|
140
184
|
end
|
141
185
|
end
|
142
186
|
else
|
143
|
-
def cork_socket(socket)
|
144
|
-
end
|
145
|
-
|
146
|
-
def uncork_socket(socket)
|
147
|
-
end
|
148
|
-
|
149
187
|
def closed_socket?(socket)
|
150
188
|
false
|
151
189
|
end
|
152
190
|
end
|
153
191
|
|
192
|
+
# @!attribute [r] backlog
|
154
193
|
def backlog
|
155
194
|
@thread_pool and @thread_pool.backlog
|
156
195
|
end
|
157
196
|
|
197
|
+
# @!attribute [r] running
|
158
198
|
def running
|
159
199
|
@thread_pool and @thread_pool.spawned
|
160
200
|
end
|
@@ -167,6 +207,7 @@ module Puma
|
|
167
207
|
# there are 5 threads sitting idle ready to take
|
168
208
|
# a request. If one request comes in, then the
|
169
209
|
# value would be 4 until it finishes processing.
|
210
|
+
# @!attribute [r] pool_capacity
|
170
211
|
def pool_capacity
|
171
212
|
@thread_pool and @thread_pool.pool_capacity
|
172
213
|
end
|
@@ -184,55 +225,19 @@ module Puma
|
|
184
225
|
|
185
226
|
@status = :run
|
186
227
|
|
187
|
-
@thread_pool = ThreadPool.new(
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
process_now = false
|
195
|
-
|
196
|
-
begin
|
197
|
-
if @queue_requests
|
198
|
-
process_now = client.eagerly_finish
|
199
|
-
else
|
200
|
-
client.finish(@first_data_timeout)
|
201
|
-
process_now = true
|
202
|
-
end
|
203
|
-
rescue MiniSSL::SSLError => e
|
204
|
-
ssl_socket = client.io
|
205
|
-
addr = ssl_socket.peeraddr.last
|
206
|
-
cert = ssl_socket.peercert
|
207
|
-
|
208
|
-
client.close
|
209
|
-
|
210
|
-
@events.ssl_error self, addr, cert, e
|
211
|
-
rescue HttpParserError => e
|
212
|
-
client.write_error(400)
|
213
|
-
client.close
|
214
|
-
|
215
|
-
@events.parse_error self, client.env, e
|
216
|
-
rescue ConnectionError, EOFError
|
217
|
-
client.close
|
218
|
-
else
|
219
|
-
if process_now
|
220
|
-
process_client client, buffer
|
221
|
-
else
|
222
|
-
client.set_timeout @first_data_timeout
|
223
|
-
@reactor.add client
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
process_now
|
228
|
-
end
|
228
|
+
@thread_pool = ThreadPool.new(
|
229
|
+
@min_threads,
|
230
|
+
@max_threads,
|
231
|
+
::Puma::IOBuffer,
|
232
|
+
&method(:process_client)
|
233
|
+
)
|
229
234
|
|
230
235
|
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
231
236
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
232
237
|
|
233
238
|
if @queue_requests
|
234
|
-
@reactor = Reactor.new
|
235
|
-
@reactor.
|
239
|
+
@reactor = Reactor.new(&method(:reactor_wakeup))
|
240
|
+
@reactor.run
|
236
241
|
end
|
237
242
|
|
238
243
|
if @reaping_time
|
@@ -243,6 +248,8 @@ module Puma
|
|
243
248
|
@thread_pool.auto_trim!(@auto_trim_time)
|
244
249
|
end
|
245
250
|
|
251
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
252
|
+
|
246
253
|
@events.fire :state, :running
|
247
254
|
|
248
255
|
if background
|
@@ -256,8 +263,45 @@ module Puma
|
|
256
263
|
end
|
257
264
|
end
|
258
265
|
|
266
|
+
# This method is called from the Reactor thread when a queued Client receives data,
|
267
|
+
# times out, or when the Reactor is shutting down.
|
268
|
+
#
|
269
|
+
# It is responsible for ensuring that a request has been completely received
|
270
|
+
# before it starts to be processed by the ThreadPool. This may be known as read buffering.
|
271
|
+
# If read buffering is not done, and no other read buffering is performed (such as by an application server
|
272
|
+
# such as nginx) then the application would be subject to a slow client attack.
|
273
|
+
#
|
274
|
+
# 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).
|
275
|
+
#
|
276
|
+
# The method checks to see if it has the full header and body with
|
277
|
+
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
278
|
+
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
279
|
+
# so that a "worker thread" can pick up the request and begin to execute application logic.
|
280
|
+
# The Client is then removed from the reactor (return `true`).
|
281
|
+
#
|
282
|
+
# If a client object times out, a 408 response is written, its connection is closed,
|
283
|
+
# and the object is removed from the reactor (return `true`).
|
284
|
+
#
|
285
|
+
# If the Reactor is shutting down, all Clients are either timed out or passed to the
|
286
|
+
# ThreadPool, depending on their current state (#can_close?).
|
287
|
+
#
|
288
|
+
# Otherwise, if the full request is not ready then the client will remain in the reactor
|
289
|
+
# (return `false`). When the client sends more data to the socket the `Puma::Client` object
|
290
|
+
# will wake up and again be checked to see if it's ready to be passed to the thread pool.
|
291
|
+
def reactor_wakeup(client)
|
292
|
+
shutdown = !@queue_requests
|
293
|
+
if client.try_to_finish || (shutdown && !client.can_close?)
|
294
|
+
@thread_pool << client
|
295
|
+
elsif shutdown || client.timeout == 0
|
296
|
+
client.timeout!
|
297
|
+
end
|
298
|
+
rescue StandardError => e
|
299
|
+
client_error(e, client)
|
300
|
+
client.close
|
301
|
+
true
|
302
|
+
end
|
303
|
+
|
259
304
|
def handle_servers
|
260
|
-
@check, @notify = Puma::Util.pipe unless @notify
|
261
305
|
begin
|
262
306
|
check = @check
|
263
307
|
sockets = [check] + @binder.ios
|
@@ -281,35 +325,26 @@ module Puma
|
|
281
325
|
if sock == check
|
282
326
|
break if handle_check
|
283
327
|
else
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
end
|
299
|
-
rescue SystemCallError
|
300
|
-
# nothing
|
301
|
-
rescue Errno::ECONNABORTED
|
302
|
-
# client closed the socket even before accept
|
303
|
-
begin
|
304
|
-
io.close
|
305
|
-
rescue
|
306
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
307
|
-
end
|
328
|
+
pool.wait_until_not_full
|
329
|
+
pool.wait_for_less_busy_worker(
|
330
|
+
@options[:wait_for_less_busy_worker].to_f)
|
331
|
+
|
332
|
+
io = begin
|
333
|
+
sock.accept_nonblock
|
334
|
+
rescue IO::WaitReadable
|
335
|
+
next
|
336
|
+
end
|
337
|
+
client = Client.new io, @binder.env(sock)
|
338
|
+
if remote_addr_value
|
339
|
+
client.peerip = remote_addr_value
|
340
|
+
elsif remote_addr_header
|
341
|
+
client.remote_addr_header = remote_addr_header
|
308
342
|
end
|
343
|
+
pool << client
|
309
344
|
end
|
310
345
|
end
|
311
346
|
rescue Object => e
|
312
|
-
@events.unknown_error
|
347
|
+
@events.unknown_error e, nil, "Listen loop"
|
313
348
|
end
|
314
349
|
end
|
315
350
|
|
@@ -317,15 +352,18 @@ module Puma
|
|
317
352
|
|
318
353
|
if queue_requests
|
319
354
|
@queue_requests = false
|
320
|
-
@reactor.clear!
|
321
355
|
@reactor.shutdown
|
322
356
|
end
|
323
357
|
graceful_shutdown if @status == :stop || @status == :restart
|
324
358
|
rescue Exception => e
|
325
|
-
|
326
|
-
STDERR.puts e.backtrace
|
359
|
+
@events.unknown_error e, nil, "Exception handling servers"
|
327
360
|
ensure
|
328
|
-
|
361
|
+
begin
|
362
|
+
@check.close unless @check.closed?
|
363
|
+
rescue Errno::EBADF, RuntimeError
|
364
|
+
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
365
|
+
# Errno::EBADF is infrequently raised
|
366
|
+
end
|
329
367
|
@notify.close
|
330
368
|
@notify = nil
|
331
369
|
@check = nil
|
@@ -353,27 +391,48 @@ module Puma
|
|
353
391
|
return false
|
354
392
|
end
|
355
393
|
|
356
|
-
# Given a connection on +client+, handle the incoming requests
|
394
|
+
# Given a connection on +client+, handle the incoming requests,
|
395
|
+
# or queue the connection in the Reactor if no request is available.
|
396
|
+
#
|
397
|
+
# This method is called from a ThreadPool worker thread.
|
357
398
|
#
|
358
|
-
# This method
|
399
|
+
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
359
400
|
# indicates that it supports keep alive, wait for another request before
|
360
401
|
# returning.
|
361
402
|
#
|
403
|
+
# Return true if one or more requests were processed.
|
362
404
|
def process_client(client, buffer)
|
405
|
+
# Advertise this server into the thread
|
406
|
+
Thread.current[ThreadLocalKey] = self
|
407
|
+
|
408
|
+
clean_thread_locals = @options[:clean_thread_locals]
|
409
|
+
close_socket = true
|
410
|
+
|
411
|
+
requests = 0
|
412
|
+
|
363
413
|
begin
|
414
|
+
if @queue_requests &&
|
415
|
+
!client.eagerly_finish
|
364
416
|
|
365
|
-
|
366
|
-
|
417
|
+
client.set_timeout(@first_data_timeout)
|
418
|
+
if @reactor.add client
|
419
|
+
close_socket = false
|
420
|
+
return false
|
421
|
+
end
|
422
|
+
end
|
367
423
|
|
368
|
-
|
424
|
+
with_force_shutdown(client) do
|
425
|
+
client.finish(@first_data_timeout)
|
426
|
+
end
|
369
427
|
|
370
428
|
while true
|
429
|
+
@requests_count += 1
|
371
430
|
case handle_request(client, buffer)
|
372
431
|
when false
|
373
|
-
|
432
|
+
break
|
374
433
|
when :async
|
375
434
|
close_socket = false
|
376
|
-
|
435
|
+
break
|
377
436
|
when true
|
378
437
|
buffer.reset
|
379
438
|
|
@@ -391,48 +450,25 @@ module Puma
|
|
391
450
|
check_for_more_data = false
|
392
451
|
end
|
393
452
|
|
394
|
-
|
395
|
-
|
396
|
-
|
453
|
+
next_request_ready = with_force_shutdown(client) do
|
454
|
+
client.reset(check_for_more_data)
|
455
|
+
end
|
456
|
+
|
457
|
+
unless next_request_ready
|
458
|
+
break unless @queue_requests
|
397
459
|
client.set_timeout @persistent_timeout
|
398
|
-
@reactor.add client
|
399
|
-
|
460
|
+
if @reactor.add client
|
461
|
+
close_socket = false
|
462
|
+
break
|
463
|
+
end
|
400
464
|
end
|
401
465
|
end
|
402
466
|
end
|
403
|
-
|
404
|
-
# The client disconnected while we were reading data
|
405
|
-
rescue ConnectionError
|
406
|
-
# Swallow them. The ensure tries to close +client+ down
|
407
|
-
|
408
|
-
# SSL handshake error
|
409
|
-
rescue MiniSSL::SSLError => e
|
410
|
-
lowlevel_error(e, client.env)
|
411
|
-
|
412
|
-
ssl_socket = client.io
|
413
|
-
addr = ssl_socket.peeraddr.last
|
414
|
-
cert = ssl_socket.peercert
|
415
|
-
|
416
|
-
close_socket = true
|
417
|
-
|
418
|
-
@events.ssl_error self, addr, cert, e
|
419
|
-
|
420
|
-
# The client doesn't know HTTP well
|
421
|
-
rescue HttpParserError => e
|
422
|
-
lowlevel_error(e, client.env)
|
423
|
-
|
424
|
-
client.write_error(400)
|
425
|
-
|
426
|
-
@events.parse_error self, client.env, e
|
427
|
-
|
428
|
-
# Server error
|
467
|
+
true
|
429
468
|
rescue StandardError => e
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
@events.unknown_error self, e, "Read"
|
435
|
-
|
469
|
+
client_error(e, client)
|
470
|
+
# The ensure tries to close +client+ down
|
471
|
+
requests > 0
|
436
472
|
ensure
|
437
473
|
buffer.reset
|
438
474
|
|
@@ -442,321 +478,20 @@ module Puma
|
|
442
478
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
443
479
|
# Already closed
|
444
480
|
rescue StandardError => e
|
445
|
-
@events.unknown_error
|
481
|
+
@events.unknown_error e, nil, "Client"
|
446
482
|
end
|
447
483
|
end
|
448
484
|
end
|
449
485
|
|
450
|
-
#
|
451
|
-
#
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
env[SERVER_NAME] = host[0, colon]
|
457
|
-
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
458
|
-
else
|
459
|
-
env[SERVER_NAME] = host
|
460
|
-
env[SERVER_PORT] = default_server_port(env)
|
461
|
-
end
|
462
|
-
else
|
463
|
-
env[SERVER_NAME] = LOCALHOST
|
464
|
-
env[SERVER_PORT] = default_server_port(env)
|
465
|
-
end
|
466
|
-
|
467
|
-
unless env[REQUEST_PATH]
|
468
|
-
# it might be a dumbass full host request header
|
469
|
-
uri = URI.parse(env[REQUEST_URI])
|
470
|
-
env[REQUEST_PATH] = uri.path
|
471
|
-
|
472
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
473
|
-
|
474
|
-
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
475
|
-
# so only set the env value if there actually is a value.
|
476
|
-
env[QUERY_STRING] = uri.query if uri.query
|
477
|
-
end
|
478
|
-
|
479
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
480
|
-
|
481
|
-
# From http://www.ietf.org/rfc/rfc3875 :
|
482
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
483
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
484
|
-
# may not identify the ultimate source of the request.
|
485
|
-
# They identify the client for the immediate request to the
|
486
|
-
# server; that client may be a proxy, gateway, or other
|
487
|
-
# intermediary acting on behalf of the actual source client."
|
488
|
-
#
|
489
|
-
|
490
|
-
unless env.key?(REMOTE_ADDR)
|
491
|
-
begin
|
492
|
-
addr = client.peerip
|
493
|
-
rescue Errno::ENOTCONN
|
494
|
-
# Client disconnects can result in an inability to get the
|
495
|
-
# peeraddr from the socket; default to localhost.
|
496
|
-
addr = LOCALHOST_IP
|
497
|
-
end
|
498
|
-
|
499
|
-
# Set unix socket addrs to localhost
|
500
|
-
addr = LOCALHOST_IP if addr.empty?
|
501
|
-
|
502
|
-
env[REMOTE_ADDR] = addr
|
503
|
-
end
|
486
|
+
# Triggers a client timeout if the thread-pool shuts down
|
487
|
+
# during execution of the provided block.
|
488
|
+
def with_force_shutdown(client, &block)
|
489
|
+
@thread_pool.with_force_shutdown(&block)
|
490
|
+
rescue ThreadPool::ForceShutdown
|
491
|
+
client.timeout!
|
504
492
|
end
|
505
493
|
|
506
|
-
|
507
|
-
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"
|
508
|
-
PORT_443
|
509
|
-
else
|
510
|
-
PORT_80
|
511
|
-
end
|
512
|
-
end
|
513
|
-
|
514
|
-
# Takes the request +req+, invokes the Rack application to construct
|
515
|
-
# the response and writes it back to +req.io+.
|
516
|
-
#
|
517
|
-
# The second parameter +lines+ is a IO-like object unique to this thread.
|
518
|
-
# This is normally an instance of Puma::IOBuffer.
|
519
|
-
#
|
520
|
-
# It'll return +false+ when the connection is closed, this doesn't mean
|
521
|
-
# that the response wasn't successful.
|
522
|
-
#
|
523
|
-
# It'll return +:async+ if the connection remains open but will be handled
|
524
|
-
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
525
|
-
#
|
526
|
-
# Finally, it'll return +true+ on keep-alive connections.
|
527
|
-
def handle_request(req, lines)
|
528
|
-
@requests_count +=1
|
529
|
-
|
530
|
-
env = req.env
|
531
|
-
client = req.io
|
532
|
-
|
533
|
-
return false if closed_socket?(client)
|
534
|
-
|
535
|
-
normalize_env env, req
|
536
|
-
|
537
|
-
env[PUMA_SOCKET] = client
|
538
|
-
|
539
|
-
if env[HTTPS_KEY] && client.peercert
|
540
|
-
env[PUMA_PEERCERT] = client.peercert
|
541
|
-
end
|
542
|
-
|
543
|
-
env[HIJACK_P] = true
|
544
|
-
env[HIJACK] = req
|
545
|
-
|
546
|
-
body = req.body
|
547
|
-
|
548
|
-
head = env[REQUEST_METHOD] == HEAD
|
549
|
-
|
550
|
-
env[RACK_INPUT] = body
|
551
|
-
env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
552
|
-
|
553
|
-
if @early_hints
|
554
|
-
env[EARLY_HINTS] = lambda { |headers|
|
555
|
-
begin
|
556
|
-
fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
|
557
|
-
|
558
|
-
headers.each_pair do |k, vs|
|
559
|
-
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
560
|
-
vs.to_s.split(NEWLINE).each do |v|
|
561
|
-
next if possible_header_injection?(v)
|
562
|
-
fast_write client, "#{k}: #{v}\r\n"
|
563
|
-
end
|
564
|
-
else
|
565
|
-
fast_write client, "#{k}: #{vs}\r\n"
|
566
|
-
end
|
567
|
-
end
|
568
|
-
|
569
|
-
fast_write client, "\r\n".freeze
|
570
|
-
rescue ConnectionError
|
571
|
-
# noop, if we lost the socket we just won't send the early hints
|
572
|
-
end
|
573
|
-
}
|
574
|
-
end
|
575
|
-
|
576
|
-
# A rack extension. If the app writes #call'ables to this
|
577
|
-
# array, we will invoke them when the request is done.
|
578
|
-
#
|
579
|
-
after_reply = env[RACK_AFTER_REPLY] = []
|
580
|
-
|
581
|
-
begin
|
582
|
-
begin
|
583
|
-
status, headers, res_body = @app.call(env)
|
584
|
-
|
585
|
-
return :async if req.hijacked
|
586
|
-
|
587
|
-
status = status.to_i
|
588
|
-
|
589
|
-
if status == -1
|
590
|
-
unless headers.empty? and res_body == []
|
591
|
-
raise "async response must have empty headers and body"
|
592
|
-
end
|
593
|
-
|
594
|
-
return :async
|
595
|
-
end
|
596
|
-
rescue ThreadPool::ForceShutdown => e
|
597
|
-
@events.unknown_error self, e, "Rack app", env
|
598
|
-
@events.log "Detected force shutdown of a thread"
|
599
|
-
|
600
|
-
status, headers, res_body = lowlevel_error(e, env, 503)
|
601
|
-
rescue Exception => e
|
602
|
-
@events.unknown_error self, e, "Rack app", env
|
603
|
-
|
604
|
-
status, headers, res_body = lowlevel_error(e, env, 500)
|
605
|
-
end
|
606
|
-
|
607
|
-
content_length = nil
|
608
|
-
no_body = head
|
609
|
-
|
610
|
-
if res_body.kind_of? Array and res_body.size == 1
|
611
|
-
content_length = res_body[0].bytesize
|
612
|
-
end
|
613
|
-
|
614
|
-
cork_socket client
|
615
|
-
|
616
|
-
line_ending = LINE_END
|
617
|
-
colon = COLON
|
618
|
-
|
619
|
-
http_11 = env[HTTP_VERSION] == HTTP_11
|
620
|
-
if http_11
|
621
|
-
allow_chunked = true
|
622
|
-
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
623
|
-
|
624
|
-
# An optimization. The most common response is 200, so we can
|
625
|
-
# reply with the proper 200 status without having to compute
|
626
|
-
# the response header.
|
627
|
-
#
|
628
|
-
if status == 200
|
629
|
-
lines << HTTP_11_200
|
630
|
-
else
|
631
|
-
lines.append "HTTP/1.1 ", status.to_s, " ",
|
632
|
-
fetch_status_code(status), line_ending
|
633
|
-
|
634
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
635
|
-
end
|
636
|
-
else
|
637
|
-
allow_chunked = false
|
638
|
-
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
639
|
-
|
640
|
-
# Same optimization as above for HTTP/1.1
|
641
|
-
#
|
642
|
-
if status == 200
|
643
|
-
lines << HTTP_10_200
|
644
|
-
else
|
645
|
-
lines.append "HTTP/1.0 ", status.to_s, " ",
|
646
|
-
fetch_status_code(status), line_ending
|
647
|
-
|
648
|
-
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
649
|
-
end
|
650
|
-
end
|
651
|
-
|
652
|
-
# regardless of what the client wants, we always close the connection
|
653
|
-
# if running without request queueing
|
654
|
-
keep_alive &&= @queue_requests
|
655
|
-
|
656
|
-
response_hijack = nil
|
657
|
-
|
658
|
-
headers.each do |k, vs|
|
659
|
-
case k.downcase
|
660
|
-
when CONTENT_LENGTH2
|
661
|
-
next if possible_header_injection?(vs)
|
662
|
-
content_length = vs
|
663
|
-
next
|
664
|
-
when TRANSFER_ENCODING
|
665
|
-
allow_chunked = false
|
666
|
-
content_length = nil
|
667
|
-
when HIJACK
|
668
|
-
response_hijack = vs
|
669
|
-
next
|
670
|
-
end
|
671
|
-
|
672
|
-
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
673
|
-
vs.to_s.split(NEWLINE).each do |v|
|
674
|
-
next if possible_header_injection?(v)
|
675
|
-
lines.append k, colon, v, line_ending
|
676
|
-
end
|
677
|
-
else
|
678
|
-
lines.append k, colon, line_ending
|
679
|
-
end
|
680
|
-
end
|
681
|
-
|
682
|
-
# HTTP/1.1 & 1.0 assume different defaults:
|
683
|
-
# - HTTP 1.0 assumes the connection will be closed if not specified
|
684
|
-
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
685
|
-
# Only set the header if we're doing something which is not the default
|
686
|
-
# for this protocol version
|
687
|
-
if http_11
|
688
|
-
lines << CONNECTION_CLOSE if !keep_alive
|
689
|
-
else
|
690
|
-
lines << CONNECTION_KEEP_ALIVE if keep_alive
|
691
|
-
end
|
692
|
-
|
693
|
-
if no_body
|
694
|
-
if content_length and status != 204
|
695
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
696
|
-
end
|
697
|
-
|
698
|
-
lines << line_ending
|
699
|
-
fast_write client, lines.to_s
|
700
|
-
return keep_alive
|
701
|
-
end
|
702
|
-
|
703
|
-
if content_length
|
704
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
705
|
-
chunked = false
|
706
|
-
elsif !response_hijack and allow_chunked
|
707
|
-
lines << TRANSFER_ENCODING_CHUNKED
|
708
|
-
chunked = true
|
709
|
-
end
|
710
|
-
|
711
|
-
lines << line_ending
|
712
|
-
|
713
|
-
fast_write client, lines.to_s
|
714
|
-
|
715
|
-
if response_hijack
|
716
|
-
response_hijack.call client
|
717
|
-
return :async
|
718
|
-
end
|
719
|
-
|
720
|
-
begin
|
721
|
-
res_body.each do |part|
|
722
|
-
next if part.bytesize.zero?
|
723
|
-
if chunked
|
724
|
-
fast_write client, part.bytesize.to_s(16)
|
725
|
-
fast_write client, line_ending
|
726
|
-
fast_write client, part
|
727
|
-
fast_write client, line_ending
|
728
|
-
else
|
729
|
-
fast_write client, part
|
730
|
-
end
|
731
|
-
|
732
|
-
client.flush
|
733
|
-
end
|
734
|
-
|
735
|
-
if chunked
|
736
|
-
fast_write client, CLOSE_CHUNKED
|
737
|
-
client.flush
|
738
|
-
end
|
739
|
-
rescue SystemCallError, IOError
|
740
|
-
raise ConnectionError, "Connection error detected during write"
|
741
|
-
end
|
742
|
-
|
743
|
-
ensure
|
744
|
-
uncork_socket client
|
745
|
-
|
746
|
-
body.close
|
747
|
-
req.tempfile.unlink if req.tempfile
|
748
|
-
res_body.close if res_body.respond_to? :close
|
749
|
-
|
750
|
-
after_reply.each { |o| o.call }
|
751
|
-
end
|
752
|
-
|
753
|
-
return keep_alive
|
754
|
-
end
|
755
|
-
|
756
|
-
def fetch_status_code(status)
|
757
|
-
HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
|
758
|
-
end
|
759
|
-
private :fetch_status_code
|
494
|
+
# :nocov:
|
760
495
|
|
761
496
|
# Given the request +env+ from +client+ and the partial body +body+
|
762
497
|
# plus a potential Content-Length value +cl+, finish reading
|
@@ -764,6 +499,7 @@ module Puma
|
|
764
499
|
#
|
765
500
|
# If the body is larger than MAX_BODY, a Tempfile object is used
|
766
501
|
# for the body, otherwise a StringIO is used.
|
502
|
+
# @deprecated 6.0.0
|
767
503
|
#
|
768
504
|
def read_body(env, client, body, cl)
|
769
505
|
content_length = cl.to_i
|
@@ -796,7 +532,7 @@ module Puma
|
|
796
532
|
|
797
533
|
remain -= stream.write(chunk)
|
798
534
|
|
799
|
-
#
|
535
|
+
# Read the rest of the chunks
|
800
536
|
while remain > 0
|
801
537
|
chunk = client.readpartial(CHUNK_SIZE)
|
802
538
|
unless chunk
|
@@ -811,6 +547,25 @@ module Puma
|
|
811
547
|
|
812
548
|
return stream
|
813
549
|
end
|
550
|
+
# :nocov:
|
551
|
+
|
552
|
+
# Handle various error types thrown by Client I/O operations.
|
553
|
+
def client_error(e, client)
|
554
|
+
# Swallow, do not log
|
555
|
+
return if [ConnectionError, EOFError].include?(e.class)
|
556
|
+
|
557
|
+
lowlevel_error(e, client.env)
|
558
|
+
case e
|
559
|
+
when MiniSSL::SSLError
|
560
|
+
@events.ssl_error e, client.io
|
561
|
+
when HttpParserError
|
562
|
+
client.write_error(400)
|
563
|
+
@events.parse_error e, client
|
564
|
+
else
|
565
|
+
client.write_error(500)
|
566
|
+
@events.unknown_error e, nil, "Read"
|
567
|
+
end
|
568
|
+
end
|
814
569
|
|
815
570
|
# A fallback rack response if +@app+ raises as exception.
|
816
571
|
#
|
@@ -878,7 +633,7 @@ module Puma
|
|
878
633
|
|
879
634
|
if @thread_pool
|
880
635
|
if timeout = @options[:force_shutdown_after]
|
881
|
-
@thread_pool.shutdown timeout.
|
636
|
+
@thread_pool.shutdown timeout.to_f
|
882
637
|
else
|
883
638
|
@thread_pool.shutdown
|
884
639
|
end
|
@@ -886,19 +641,16 @@ module Puma
|
|
886
641
|
end
|
887
642
|
|
888
643
|
def notify_safely(message)
|
889
|
-
@
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
644
|
+
@notify << message
|
645
|
+
rescue IOError, NoMethodError, Errno::EPIPE
|
646
|
+
# The server, in another thread, is shutting down
|
647
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
648
|
+
rescue RuntimeError => e
|
649
|
+
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
650
|
+
if e.message.include?('IOError')
|
894
651
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
895
|
-
|
896
|
-
|
897
|
-
if e.message.include?('IOError')
|
898
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
899
|
-
else
|
900
|
-
raise e
|
901
|
-
end
|
652
|
+
else
|
653
|
+
raise e
|
902
654
|
end
|
903
655
|
end
|
904
656
|
private :notify_safely
|
@@ -921,46 +673,17 @@ module Puma
|
|
921
673
|
@thread.join if @thread && sync
|
922
674
|
end
|
923
675
|
|
924
|
-
def fast_write(io, str)
|
925
|
-
n = 0
|
926
|
-
while true
|
927
|
-
begin
|
928
|
-
n = io.syswrite str
|
929
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
930
|
-
if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
|
931
|
-
raise ConnectionError, "Socket timeout writing data"
|
932
|
-
end
|
933
|
-
|
934
|
-
retry
|
935
|
-
rescue Errno::EPIPE, SystemCallError, IOError
|
936
|
-
raise ConnectionError, "Socket timeout writing data"
|
937
|
-
end
|
938
|
-
|
939
|
-
return if n == str.bytesize
|
940
|
-
str = str.byteslice(n..-1)
|
941
|
-
end
|
942
|
-
end
|
943
|
-
private :fast_write
|
944
|
-
|
945
|
-
ThreadLocalKey = :puma_server
|
946
|
-
|
947
|
-
def self.current
|
948
|
-
Thread.current[ThreadLocalKey]
|
949
|
-
end
|
950
|
-
|
951
676
|
def shutting_down?
|
952
677
|
@status == :stop || @status == :restart
|
953
678
|
end
|
954
679
|
|
955
|
-
def possible_header_injection?(header_value)
|
956
|
-
HTTP_INJECTION_REGEX =~ header_value.to_s
|
957
|
-
end
|
958
|
-
private :possible_header_injection?
|
959
|
-
|
960
680
|
# List of methods invoked by #stats.
|
681
|
+
# @version 5.0.0
|
961
682
|
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
962
683
|
|
963
684
|
# Returns a hash of stats about the running server for reporting purposes.
|
685
|
+
# @version 5.0.0
|
686
|
+
# @!attribute [r] stats
|
964
687
|
def stats
|
965
688
|
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
966
689
|
end
|