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