puma 4.3.12 → 5.6.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +1526 -524
- data/LICENSE +23 -20
- data/README.md +120 -36
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +33 -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/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +85 -128
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +44 -10
- data/ext/puma_http11/http11_parser.c +45 -47
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +1 -1
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/http11_parser_common.rl +0 -0
- data/ext/puma_http11/mini_ssl.c +225 -89
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
- data/ext/puma_http11/puma_http11.c +32 -51
- data/lib/puma/app/status.rb +50 -36
- data/lib/puma/binder.rb +225 -106
- data/lib/puma/cli.rb +24 -18
- data/lib/puma/client.rb +146 -84
- data/lib/puma/cluster/worker.rb +173 -0
- data/lib/puma/cluster/worker_handle.rb +94 -0
- data/lib/puma/cluster.rb +212 -220
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +58 -49
- data/lib/puma/const.rb +22 -7
- data/lib/puma/control_cli.rb +99 -76
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +368 -96
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -34
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +128 -46
- data/lib/puma/minissl/context_builder.rb +14 -9
- data/lib/puma/minissl.rb +137 -50
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +1 -5
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +0 -0
- data/lib/puma/reactor.rb +85 -369
- data/lib/puma/request.rb +489 -0
- data/lib/puma/runner.rb +46 -61
- data/lib/puma/server.rb +292 -763
- data/lib/puma/single.rb +9 -65
- data/lib/puma/state_file.rb +48 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +125 -57
- data/lib/puma/util.rb +32 -4
- data/lib/puma.rb +48 -0
- data/lib/rack/handler/puma.rb +2 -3
- data/lib/rack/version_restriction.rb +15 -0
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +29 -24
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/accept_nonblock.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- 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/request.rb
ADDED
@@ -0,0 +1,489 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
|
5
|
+
# The methods here are included in Server, but are separated into this file.
|
6
|
+
# All the methods here pertain to passing the request to the app, then
|
7
|
+
# writing the response back to the client.
|
8
|
+
#
|
9
|
+
# None of the methods here are called externally, with the exception of
|
10
|
+
# #handle_request, which is called in Server#process_client.
|
11
|
+
# @version 5.0.3
|
12
|
+
#
|
13
|
+
module Request
|
14
|
+
|
15
|
+
include Puma::Const
|
16
|
+
|
17
|
+
# Takes the request contained in +client+, invokes the Rack application to construct
|
18
|
+
# the response and writes it back to +client.io+.
|
19
|
+
#
|
20
|
+
# It'll return +false+ when the connection is closed, this doesn't mean
|
21
|
+
# that the response wasn't successful.
|
22
|
+
#
|
23
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
24
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
25
|
+
#
|
26
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
27
|
+
# @param client [Puma::Client]
|
28
|
+
# @param lines [Puma::IOBuffer]
|
29
|
+
# @param requests [Integer]
|
30
|
+
# @return [Boolean,:async]
|
31
|
+
#
|
32
|
+
def handle_request(client, lines, requests)
|
33
|
+
env = client.env
|
34
|
+
io = client.io # io may be a MiniSSL::Socket
|
35
|
+
|
36
|
+
return false if closed_socket?(io)
|
37
|
+
|
38
|
+
normalize_env env, client
|
39
|
+
|
40
|
+
env[PUMA_SOCKET] = io
|
41
|
+
|
42
|
+
if env[HTTPS_KEY] && io.peercert
|
43
|
+
env[PUMA_PEERCERT] = io.peercert
|
44
|
+
end
|
45
|
+
|
46
|
+
env[HIJACK_P] = true
|
47
|
+
env[HIJACK] = client
|
48
|
+
|
49
|
+
body = client.body
|
50
|
+
|
51
|
+
head = env[REQUEST_METHOD] == HEAD
|
52
|
+
|
53
|
+
env[RACK_INPUT] = body
|
54
|
+
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
55
|
+
|
56
|
+
if @early_hints
|
57
|
+
env[EARLY_HINTS] = lambda { |headers|
|
58
|
+
begin
|
59
|
+
fast_write io, str_early_hints(headers)
|
60
|
+
rescue ConnectionError => e
|
61
|
+
@events.debug_error e
|
62
|
+
# noop, if we lost the socket we just won't send the early hints
|
63
|
+
end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
req_env_post_parse env
|
68
|
+
|
69
|
+
# A rack extension. If the app writes #call'ables to this
|
70
|
+
# array, we will invoke them when the request is done.
|
71
|
+
#
|
72
|
+
after_reply = env[RACK_AFTER_REPLY] = []
|
73
|
+
|
74
|
+
begin
|
75
|
+
begin
|
76
|
+
status, headers, res_body = @thread_pool.with_force_shutdown do
|
77
|
+
@app.call(env)
|
78
|
+
end
|
79
|
+
|
80
|
+
return :async if client.hijacked
|
81
|
+
|
82
|
+
status = status.to_i
|
83
|
+
|
84
|
+
if status == -1
|
85
|
+
unless headers.empty? and res_body == []
|
86
|
+
raise "async response must have empty headers and body"
|
87
|
+
end
|
88
|
+
|
89
|
+
return :async
|
90
|
+
end
|
91
|
+
rescue ThreadPool::ForceShutdown => e
|
92
|
+
@events.unknown_error e, client, "Rack app"
|
93
|
+
@events.log "Detected force shutdown of a thread"
|
94
|
+
|
95
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
96
|
+
rescue Exception => e
|
97
|
+
@events.unknown_error e, client, "Rack app"
|
98
|
+
|
99
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
100
|
+
end
|
101
|
+
|
102
|
+
res_info = {}
|
103
|
+
res_info[:content_length] = nil
|
104
|
+
res_info[:no_body] = head
|
105
|
+
|
106
|
+
res_info[:content_length] = if res_body.kind_of? Array and res_body.size == 1
|
107
|
+
res_body[0].bytesize
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
|
112
|
+
cork_socket io
|
113
|
+
|
114
|
+
str_headers(env, status, headers, res_info, lines, requests, client)
|
115
|
+
|
116
|
+
line_ending = LINE_END
|
117
|
+
|
118
|
+
content_length = res_info[:content_length]
|
119
|
+
response_hijack = res_info[:response_hijack]
|
120
|
+
|
121
|
+
if res_info[:no_body]
|
122
|
+
if content_length and status != 204
|
123
|
+
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
124
|
+
end
|
125
|
+
|
126
|
+
lines << LINE_END
|
127
|
+
fast_write io, lines.to_s
|
128
|
+
return res_info[:keep_alive]
|
129
|
+
end
|
130
|
+
|
131
|
+
if content_length
|
132
|
+
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
133
|
+
chunked = false
|
134
|
+
elsif !response_hijack and res_info[:allow_chunked]
|
135
|
+
lines << TRANSFER_ENCODING_CHUNKED
|
136
|
+
chunked = true
|
137
|
+
end
|
138
|
+
|
139
|
+
lines << line_ending
|
140
|
+
|
141
|
+
fast_write io, lines.to_s
|
142
|
+
|
143
|
+
if response_hijack
|
144
|
+
response_hijack.call io
|
145
|
+
return :async
|
146
|
+
end
|
147
|
+
|
148
|
+
begin
|
149
|
+
res_body.each do |part|
|
150
|
+
next if part.bytesize.zero?
|
151
|
+
if chunked
|
152
|
+
fast_write io, (part.bytesize.to_s(16) << line_ending)
|
153
|
+
fast_write io, part # part may have different encoding
|
154
|
+
fast_write io, line_ending
|
155
|
+
else
|
156
|
+
fast_write io, part
|
157
|
+
end
|
158
|
+
io.flush
|
159
|
+
end
|
160
|
+
|
161
|
+
if chunked
|
162
|
+
fast_write io, CLOSE_CHUNKED
|
163
|
+
io.flush
|
164
|
+
end
|
165
|
+
rescue SystemCallError, IOError
|
166
|
+
raise ConnectionError, "Connection error detected during write"
|
167
|
+
end
|
168
|
+
|
169
|
+
ensure
|
170
|
+
begin
|
171
|
+
uncork_socket io
|
172
|
+
|
173
|
+
body.close
|
174
|
+
client.tempfile.unlink if client.tempfile
|
175
|
+
ensure
|
176
|
+
# Whatever happens, we MUST call `close` on the response body.
|
177
|
+
# Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
|
178
|
+
res_body.close if res_body.respond_to? :close
|
179
|
+
end
|
180
|
+
|
181
|
+
begin
|
182
|
+
after_reply.each { |o| o.call }
|
183
|
+
rescue StandardError => e
|
184
|
+
@log_writer.debug_error e
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
res_info[:keep_alive]
|
189
|
+
end
|
190
|
+
|
191
|
+
# @param env [Hash] see Puma::Client#env, from request
|
192
|
+
# @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
|
193
|
+
#
|
194
|
+
def default_server_port(env)
|
195
|
+
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"
|
196
|
+
PORT_443
|
197
|
+
else
|
198
|
+
PORT_80
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Writes to an io (normally Client#io) using #syswrite
|
203
|
+
# @param io [#syswrite] the io to write to
|
204
|
+
# @param str [String] the string written to the io
|
205
|
+
# @raise [ConnectionError]
|
206
|
+
#
|
207
|
+
def fast_write(io, str)
|
208
|
+
n = 0
|
209
|
+
while true
|
210
|
+
begin
|
211
|
+
n = io.syswrite str
|
212
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
213
|
+
unless io.wait_writable WRITE_TIMEOUT
|
214
|
+
raise ConnectionError, "Socket timeout writing data"
|
215
|
+
end
|
216
|
+
|
217
|
+
retry
|
218
|
+
rescue Errno::EPIPE, SystemCallError, IOError
|
219
|
+
raise ConnectionError, "Socket timeout writing data"
|
220
|
+
end
|
221
|
+
|
222
|
+
return if n == str.bytesize
|
223
|
+
str = str.byteslice(n..-1)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
private :fast_write
|
227
|
+
|
228
|
+
# @param status [Integer] status from the app
|
229
|
+
# @return [String] the text description from Puma::HTTP_STATUS_CODES
|
230
|
+
#
|
231
|
+
def fetch_status_code(status)
|
232
|
+
HTTP_STATUS_CODES.fetch(status) { 'CUSTOM' }
|
233
|
+
end
|
234
|
+
private :fetch_status_code
|
235
|
+
|
236
|
+
# Given a Hash +env+ for the request read from +client+, add
|
237
|
+
# and fixup keys to comply with Rack's env guidelines.
|
238
|
+
# @param env [Hash] see Puma::Client#env, from request
|
239
|
+
# @param client [Puma::Client] only needed for Client#peerip
|
240
|
+
# @todo make private in 6.0.0
|
241
|
+
#
|
242
|
+
def normalize_env(env, client)
|
243
|
+
if host = env[HTTP_HOST]
|
244
|
+
# host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
|
245
|
+
if colon = host.rindex("]:") # IPV6 with port
|
246
|
+
env[SERVER_NAME] = host[0, colon+1]
|
247
|
+
env[SERVER_PORT] = host[colon+2, host.bytesize]
|
248
|
+
elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
|
249
|
+
env[SERVER_NAME] = host[0, colon]
|
250
|
+
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
251
|
+
else
|
252
|
+
env[SERVER_NAME] = host
|
253
|
+
env[SERVER_PORT] = default_server_port(env)
|
254
|
+
end
|
255
|
+
else
|
256
|
+
env[SERVER_NAME] = LOCALHOST
|
257
|
+
env[SERVER_PORT] = default_server_port(env)
|
258
|
+
end
|
259
|
+
|
260
|
+
unless env[REQUEST_PATH]
|
261
|
+
# it might be a dumbass full host request header
|
262
|
+
uri = URI.parse(env[REQUEST_URI])
|
263
|
+
env[REQUEST_PATH] = uri.path
|
264
|
+
|
265
|
+
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
266
|
+
|
267
|
+
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
268
|
+
# so only set the env value if there actually is a value.
|
269
|
+
env[QUERY_STRING] = uri.query if uri.query
|
270
|
+
end
|
271
|
+
|
272
|
+
env[PATH_INFO] = env[REQUEST_PATH]
|
273
|
+
|
274
|
+
# From https://www.ietf.org/rfc/rfc3875 :
|
275
|
+
# "Script authors should be aware that the REMOTE_ADDR and
|
276
|
+
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
277
|
+
# may not identify the ultimate source of the request.
|
278
|
+
# They identify the client for the immediate request to the
|
279
|
+
# server; that client may be a proxy, gateway, or other
|
280
|
+
# intermediary acting on behalf of the actual source client."
|
281
|
+
#
|
282
|
+
|
283
|
+
unless env.key?(REMOTE_ADDR)
|
284
|
+
begin
|
285
|
+
addr = client.peerip
|
286
|
+
rescue Errno::ENOTCONN
|
287
|
+
# Client disconnects can result in an inability to get the
|
288
|
+
# peeraddr from the socket; default to localhost.
|
289
|
+
addr = LOCALHOST_IP
|
290
|
+
end
|
291
|
+
|
292
|
+
# Set unix socket addrs to localhost
|
293
|
+
addr = LOCALHOST_IP if addr.empty?
|
294
|
+
|
295
|
+
env[REMOTE_ADDR] = addr
|
296
|
+
end
|
297
|
+
end
|
298
|
+
# private :normalize_env
|
299
|
+
|
300
|
+
# @param header_key [#to_s]
|
301
|
+
# @return [Boolean]
|
302
|
+
#
|
303
|
+
def illegal_header_key?(header_key)
|
304
|
+
!!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
|
305
|
+
end
|
306
|
+
|
307
|
+
# @param header_value [#to_s]
|
308
|
+
# @return [Boolean]
|
309
|
+
#
|
310
|
+
def illegal_header_value?(header_value)
|
311
|
+
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
312
|
+
end
|
313
|
+
private :illegal_header_key?, :illegal_header_value?
|
314
|
+
|
315
|
+
# Fixup any headers with `,` in the name to have `_` now. We emit
|
316
|
+
# headers with `,` in them during the parse phase to avoid ambiguity
|
317
|
+
# with the `-` to `_` conversion for critical headers. But here for
|
318
|
+
# compatibility, we'll convert them back. This code is written to
|
319
|
+
# avoid allocation in the common case (ie there are no headers
|
320
|
+
# with `,` in their names), that's why it has the extra conditionals.
|
321
|
+
#
|
322
|
+
# @note If a normalized version of a `,` header already exists, we ignore
|
323
|
+
# the `,` version. This prevents clobbering headers managed by proxies
|
324
|
+
# but not by clients (Like X-Forwarded-For).
|
325
|
+
#
|
326
|
+
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
327
|
+
# @version 5.0.3
|
328
|
+
#
|
329
|
+
def req_env_post_parse(env)
|
330
|
+
to_delete = nil
|
331
|
+
to_add = nil
|
332
|
+
|
333
|
+
env.each do |k,v|
|
334
|
+
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
335
|
+
if to_delete
|
336
|
+
to_delete << k
|
337
|
+
else
|
338
|
+
to_delete = [k]
|
339
|
+
end
|
340
|
+
|
341
|
+
new_k = k.tr(",", "_")
|
342
|
+
if env.key?(new_k)
|
343
|
+
next
|
344
|
+
end
|
345
|
+
|
346
|
+
unless to_add
|
347
|
+
to_add = {}
|
348
|
+
end
|
349
|
+
|
350
|
+
to_add[new_k] = v
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
if to_delete # rubocop:disable Style/SafeNavigation
|
355
|
+
to_delete.each { |k| env.delete(k) }
|
356
|
+
end
|
357
|
+
|
358
|
+
if to_add
|
359
|
+
env.merge! to_add
|
360
|
+
end
|
361
|
+
end
|
362
|
+
private :req_env_post_parse
|
363
|
+
|
364
|
+
# Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
|
365
|
+
# @param headers [Hash] the headers returned by the Rack application
|
366
|
+
# @return [String]
|
367
|
+
# @version 5.0.3
|
368
|
+
#
|
369
|
+
def str_early_hints(headers)
|
370
|
+
eh_str = "HTTP/1.1 103 Early Hints\r\n".dup
|
371
|
+
headers.each_pair do |k, vs|
|
372
|
+
next if illegal_header_key?(k)
|
373
|
+
|
374
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
375
|
+
vs.to_s.split(NEWLINE).each do |v|
|
376
|
+
next if illegal_header_value?(v)
|
377
|
+
eh_str << "#{k}: #{v}\r\n"
|
378
|
+
end
|
379
|
+
else
|
380
|
+
eh_str << "#{k}: #{vs}\r\n"
|
381
|
+
end
|
382
|
+
end
|
383
|
+
"#{eh_str}\r\n".freeze
|
384
|
+
end
|
385
|
+
private :str_early_hints
|
386
|
+
|
387
|
+
# Processes and write headers to the IOBuffer.
|
388
|
+
# @param env [Hash] see Puma::Client#env, from request
|
389
|
+
# @param status [Integer] the status returned by the Rack application
|
390
|
+
# @param headers [Hash] the headers returned by the Rack application
|
391
|
+
# @param res_info [Hash] used to pass info between this method and #handle_request
|
392
|
+
# @param lines [Puma::IOBuffer] modified inn place
|
393
|
+
# @param requests [Integer] number of inline requests handled
|
394
|
+
# @param client [Puma::Client]
|
395
|
+
# @version 5.0.3
|
396
|
+
#
|
397
|
+
def str_headers(env, status, headers, res_info, lines, requests, client)
|
398
|
+
line_ending = LINE_END
|
399
|
+
colon = COLON
|
400
|
+
|
401
|
+
http_11 = env[HTTP_VERSION] == HTTP_11
|
402
|
+
if http_11
|
403
|
+
res_info[:allow_chunked] = true
|
404
|
+
res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
405
|
+
|
406
|
+
# An optimization. The most common response is 200, so we can
|
407
|
+
# reply with the proper 200 status without having to compute
|
408
|
+
# the response header.
|
409
|
+
#
|
410
|
+
if status == 200
|
411
|
+
lines << HTTP_11_200
|
412
|
+
else
|
413
|
+
lines.append "HTTP/1.1 ", status.to_s, " ",
|
414
|
+
fetch_status_code(status), line_ending
|
415
|
+
|
416
|
+
res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
417
|
+
end
|
418
|
+
else
|
419
|
+
res_info[:allow_chunked] = false
|
420
|
+
res_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
421
|
+
|
422
|
+
# Same optimization as above for HTTP/1.1
|
423
|
+
#
|
424
|
+
if status == 200
|
425
|
+
lines << HTTP_10_200
|
426
|
+
else
|
427
|
+
lines.append "HTTP/1.0 ", status.to_s, " ",
|
428
|
+
fetch_status_code(status), line_ending
|
429
|
+
|
430
|
+
res_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
# regardless of what the client wants, we always close the connection
|
435
|
+
# if running without request queueing
|
436
|
+
res_info[:keep_alive] &&= @queue_requests
|
437
|
+
|
438
|
+
# Close the connection after a reasonable number of inline requests
|
439
|
+
# if the server is at capacity and the listener has a new connection ready.
|
440
|
+
# This allows Puma to service connections fairly when the number
|
441
|
+
# of concurrent connections exceeds the size of the threadpool.
|
442
|
+
res_info[:keep_alive] &&= requests < @max_fast_inline ||
|
443
|
+
@thread_pool.busy_threads < @max_threads ||
|
444
|
+
!client.listener.to_io.wait_readable(0)
|
445
|
+
|
446
|
+
res_info[:response_hijack] = nil
|
447
|
+
|
448
|
+
headers.each do |k, vs|
|
449
|
+
next if illegal_header_key?(k)
|
450
|
+
|
451
|
+
case k.downcase
|
452
|
+
when CONTENT_LENGTH2
|
453
|
+
next if illegal_header_value?(vs)
|
454
|
+
res_info[:content_length] = vs
|
455
|
+
next
|
456
|
+
when TRANSFER_ENCODING
|
457
|
+
res_info[:allow_chunked] = false
|
458
|
+
res_info[:content_length] = nil
|
459
|
+
when HIJACK
|
460
|
+
res_info[:response_hijack] = vs
|
461
|
+
next
|
462
|
+
when BANNED_HEADER_KEY
|
463
|
+
next
|
464
|
+
end
|
465
|
+
|
466
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
467
|
+
vs.to_s.split(NEWLINE).each do |v|
|
468
|
+
next if illegal_header_value?(v)
|
469
|
+
lines.append k, colon, v, line_ending
|
470
|
+
end
|
471
|
+
else
|
472
|
+
lines.append k, colon, line_ending
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# HTTP/1.1 & 1.0 assume different defaults:
|
477
|
+
# - HTTP 1.0 assumes the connection will be closed if not specified
|
478
|
+
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
479
|
+
# Only set the header if we're doing something which is not the default
|
480
|
+
# for this protocol version
|
481
|
+
if http_11
|
482
|
+
lines << CONNECTION_CLOSE if !res_info[:keep_alive]
|
483
|
+
else
|
484
|
+
lines << CONNECTION_KEEP_ALIVE if res_info[:keep_alive]
|
485
|
+
end
|
486
|
+
end
|
487
|
+
private :str_headers
|
488
|
+
end
|
489
|
+
end
|
data/lib/puma/runner.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'puma/server'
|
4
4
|
require 'puma/const'
|
5
|
-
require 'puma/minissl/context_builder'
|
6
5
|
|
7
6
|
module Puma
|
8
7
|
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
@@ -16,10 +15,16 @@ module Puma
|
|
16
15
|
@app = nil
|
17
16
|
@control = nil
|
18
17
|
@started_at = Time.now
|
18
|
+
@wakeup = nil
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
@
|
21
|
+
def wakeup!
|
22
|
+
return unless @wakeup
|
23
|
+
|
24
|
+
@wakeup.write "!" unless @wakeup.closed?
|
25
|
+
|
26
|
+
rescue SystemCallError, IOError
|
27
|
+
Puma::Util.purge_interrupt_queue
|
23
28
|
end
|
24
29
|
|
25
30
|
def development?
|
@@ -34,7 +39,8 @@ module Puma
|
|
34
39
|
@events.log str
|
35
40
|
end
|
36
41
|
|
37
|
-
|
42
|
+
# @version 5.0.0
|
43
|
+
def stop_control
|
38
44
|
@control.stop(true) if @control
|
39
45
|
end
|
40
46
|
|
@@ -52,42 +58,27 @@ module Puma
|
|
52
58
|
|
53
59
|
require 'puma/app/status'
|
54
60
|
|
55
|
-
uri = URI.parse str
|
56
|
-
|
57
61
|
if token = @options[:control_auth_token]
|
58
62
|
token = nil if token.empty? || token == 'none'
|
59
63
|
end
|
60
64
|
|
61
65
|
app = Puma::App::Status.new @launcher, token
|
62
66
|
|
63
|
-
control = Puma::Server.new app, @launcher.events
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
case uri.scheme
|
68
|
-
when "ssl"
|
69
|
-
log "* Starting control server on #{str}"
|
70
|
-
params = Util.parse_query uri.query
|
71
|
-
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
72
|
-
|
73
|
-
control.add_ssl_listener uri.host, uri.port, ctx
|
74
|
-
when "tcp"
|
75
|
-
log "* Starting control server on #{str}"
|
76
|
-
control.add_tcp_listener uri.host, uri.port
|
77
|
-
when "unix"
|
78
|
-
log "* Starting control server on #{str}"
|
79
|
-
path = "#{uri.host}#{uri.path}"
|
80
|
-
mask = @options[:control_url_umask]
|
81
|
-
|
82
|
-
control.add_unix_listener path, mask
|
83
|
-
else
|
84
|
-
error "Invalid control URI: #{str}"
|
85
|
-
end
|
67
|
+
control = Puma::Server.new app, @launcher.events,
|
68
|
+
{ min_threads: 0, max_threads: 1, queue_requests: false }
|
69
|
+
|
70
|
+
control.binder.parse [str], self, 'Starting control server'
|
86
71
|
|
87
|
-
control.run
|
72
|
+
control.run thread_name: 'ctl'
|
88
73
|
@control = control
|
89
74
|
end
|
90
75
|
|
76
|
+
# @version 5.0.0
|
77
|
+
def close_control_listeners
|
78
|
+
@control.binder.close_listeners if @control
|
79
|
+
end
|
80
|
+
|
81
|
+
# @!attribute [r] ruby_engine
|
91
82
|
def ruby_engine
|
92
83
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
93
84
|
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
@@ -103,14 +94,18 @@ module Puma
|
|
103
94
|
def output_header(mode)
|
104
95
|
min_t = @options[:min_threads]
|
105
96
|
max_t = @options[:max_threads]
|
97
|
+
environment = @options[:environment]
|
106
98
|
|
107
99
|
log "Puma starting in #{mode} mode..."
|
108
|
-
log "*
|
109
|
-
log "*
|
110
|
-
log "*
|
100
|
+
log "* Puma version: #{Puma::Const::PUMA_VERSION} (#{ruby_engine}) (\"#{Puma::Const::CODE_NAME}\")"
|
101
|
+
log "* Min threads: #{min_t}"
|
102
|
+
log "* Max threads: #{max_t}"
|
103
|
+
log "* Environment: #{environment}"
|
111
104
|
|
112
|
-
if
|
113
|
-
log "*
|
105
|
+
if mode == "cluster"
|
106
|
+
log "* Master PID: #{Process.pid}"
|
107
|
+
else
|
108
|
+
log "* PID: #{Process.pid}"
|
114
109
|
end
|
115
110
|
end
|
116
111
|
|
@@ -124,23 +119,24 @@ module Puma
|
|
124
119
|
append = @options[:redirect_append]
|
125
120
|
|
126
121
|
if stdout
|
127
|
-
|
128
|
-
raise "Cannot redirect STDOUT to #{stdout}"
|
129
|
-
end
|
122
|
+
ensure_output_directory_exists(stdout, 'STDOUT')
|
130
123
|
|
131
124
|
STDOUT.reopen stdout, (append ? "a" : "w")
|
132
|
-
STDOUT.sync = true
|
133
125
|
STDOUT.puts "=== puma startup: #{Time.now} ==="
|
126
|
+
STDOUT.flush unless STDOUT.sync
|
134
127
|
end
|
135
128
|
|
136
129
|
if stderr
|
137
|
-
|
138
|
-
raise "Cannot redirect STDERR to #{stderr}"
|
139
|
-
end
|
130
|
+
ensure_output_directory_exists(stderr, 'STDERR')
|
140
131
|
|
141
132
|
STDERR.reopen stderr, (append ? "a" : "w")
|
142
|
-
STDERR.sync = true
|
143
133
|
STDERR.puts "=== puma startup: #{Time.now} ==="
|
134
|
+
STDERR.flush unless STDERR.sync
|
135
|
+
end
|
136
|
+
|
137
|
+
if @options[:mutate_stdout_and_stderr_to_sync_on_write]
|
138
|
+
STDOUT.sync = true
|
139
|
+
STDERR.sync = true
|
144
140
|
end
|
145
141
|
end
|
146
142
|
|
@@ -150,7 +146,6 @@ module Puma
|
|
150
146
|
exit 1
|
151
147
|
end
|
152
148
|
|
153
|
-
# Load the app before we daemonize.
|
154
149
|
begin
|
155
150
|
@app = @launcher.config.app
|
156
151
|
rescue Exception => e
|
@@ -161,32 +156,22 @@ module Puma
|
|
161
156
|
@launcher.binder.parse @options[:binds], self
|
162
157
|
end
|
163
158
|
|
159
|
+
# @!attribute [r] app
|
164
160
|
def app
|
165
161
|
@app ||= @launcher.config.app
|
166
162
|
end
|
167
163
|
|
168
164
|
def start_server
|
169
|
-
min_t = @options[:min_threads]
|
170
|
-
max_t = @options[:max_threads]
|
171
|
-
|
172
165
|
server = Puma::Server.new app, @launcher.events, @options
|
173
|
-
server.min_threads = min_t
|
174
|
-
server.max_threads = max_t
|
175
166
|
server.inherit_binder @launcher.binder
|
167
|
+
server
|
168
|
+
end
|
176
169
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
if @options[:early_hints]
|
182
|
-
server.early_hints = true
|
183
|
-
end
|
184
|
-
|
185
|
-
unless development? || test?
|
186
|
-
server.leak_stack_on_error = false
|
170
|
+
private
|
171
|
+
def ensure_output_directory_exists(path, io_name)
|
172
|
+
unless Dir.exist?(File.dirname(path))
|
173
|
+
raise "Cannot redirect #{io_name} to #{path}"
|
187
174
|
end
|
188
|
-
|
189
|
-
server
|
190
175
|
end
|
191
176
|
end
|
192
177
|
end
|