puma 7.2.0-java → 8.0.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +45 -0
- data/README.md +1 -2
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/signals.md +1 -1
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +90 -66
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster.rb +1 -1
- data/lib/puma/configuration.rb +69 -7
- data/lib/puma/const.rb +2 -2
- data/lib/puma/control_cli.rb +1 -1
- data/lib/puma/detect.rb +11 -0
- data/lib/puma/dsl.rb +74 -8
- data/lib/puma/launcher.rb +3 -4
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/{request.rb → response.rb} +15 -186
- data/lib/puma/server.rb +69 -34
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/thread_pool.rb +129 -23
- data/lib/rack/handler/puma.rb +1 -1
- metadata +14 -3
data/lib/puma/cli.rb
CHANGED
|
@@ -148,7 +148,7 @@ module Puma
|
|
|
148
148
|
|
|
149
149
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
|
150
150
|
"Use -b for more advanced options" do |arg|
|
|
151
|
-
user_config.
|
|
151
|
+
user_config.port arg, Configuration.default_tcp_host
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
data/lib/puma/client.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'detect'
|
|
4
4
|
require_relative 'io_buffer'
|
|
5
|
+
require_relative 'client_env'
|
|
5
6
|
require 'tempfile'
|
|
6
7
|
|
|
7
8
|
if Puma::IS_JRUBY
|
|
@@ -20,8 +21,8 @@ module Puma
|
|
|
20
21
|
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
# An instance of this class
|
|
24
|
-
# For example, this could be
|
|
24
|
+
# An instance of this class wraps a connection/socket.
|
|
25
|
+
# For example, this could be an http request from a browser or from CURL.
|
|
25
26
|
#
|
|
26
27
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
|
27
28
|
# by the reactor. The reactor is expected to call `#to_io`
|
|
@@ -29,12 +30,18 @@ module Puma
|
|
|
29
30
|
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
|
30
31
|
# registered.
|
|
31
32
|
#
|
|
32
|
-
# Instances of this class are responsible for knowing if
|
|
33
|
-
#
|
|
33
|
+
# Instances of this class are responsible for knowing if the request line,
|
|
34
|
+
# headers and body are fully buffered and verified via the `try_to_finish` method.
|
|
35
|
+
# All verification of each request is done in the `Client` object.
|
|
34
36
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
|
35
37
|
#
|
|
38
|
+
# Most of the code for env processing and verification is contained
|
|
39
|
+
# in `Puma::ClientEnv`, which is included.
|
|
40
|
+
#
|
|
36
41
|
class Client # :nodoc:
|
|
37
42
|
|
|
43
|
+
include ClientEnv
|
|
44
|
+
|
|
38
45
|
# this tests all values but the last, which must be chunked
|
|
39
46
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
|
40
47
|
|
|
@@ -65,7 +72,13 @@ module Puma
|
|
|
65
72
|
# no body share this one object since it has no state.
|
|
66
73
|
EmptyBody = NullIO.new
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
|
76
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
|
77
|
+
:requests_served, :error_status_code
|
|
78
|
+
|
|
79
|
+
attr_writer :peerip, :http_content_length_limit, :supported_http_methods
|
|
80
|
+
|
|
81
|
+
attr_accessor :remote_addr_header, :listener, :env_set_http_version
|
|
69
82
|
|
|
70
83
|
def initialize(io, env=nil)
|
|
71
84
|
@io = io
|
|
@@ -91,7 +104,8 @@ module Puma
|
|
|
91
104
|
@hijacked = false
|
|
92
105
|
|
|
93
106
|
@http_content_length_limit = nil
|
|
94
|
-
@http_content_length_limit_exceeded =
|
|
107
|
+
@http_content_length_limit_exceeded = nil
|
|
108
|
+
@error_status_code = nil
|
|
95
109
|
|
|
96
110
|
@peerip = nil
|
|
97
111
|
@peer_family = nil
|
|
@@ -107,14 +121,6 @@ module Puma
|
|
|
107
121
|
@read_buffer = String.new # rubocop: disable Performance/UnfreezeString
|
|
108
122
|
end
|
|
109
123
|
|
|
110
|
-
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
|
111
|
-
:tempfile, :io_buffer, :http_content_length_limit_exceeded,
|
|
112
|
-
:requests_served
|
|
113
|
-
|
|
114
|
-
attr_writer :peerip, :http_content_length_limit
|
|
115
|
-
|
|
116
|
-
attr_accessor :remote_addr_header, :listener
|
|
117
|
-
|
|
118
124
|
# Remove in Puma 7?
|
|
119
125
|
def closed?
|
|
120
126
|
@to_io.closed?
|
|
@@ -164,7 +170,8 @@ module Puma
|
|
|
164
170
|
@body_remain = 0
|
|
165
171
|
@peerip = nil if @remote_addr_header
|
|
166
172
|
@in_last_chunk = false
|
|
167
|
-
@http_content_length_limit_exceeded =
|
|
173
|
+
@http_content_length_limit_exceeded = nil
|
|
174
|
+
@error_status_code = nil
|
|
168
175
|
end
|
|
169
176
|
|
|
170
177
|
# only used with back-to-back requests contained in the buffer
|
|
@@ -174,12 +181,7 @@ module Puma
|
|
|
174
181
|
|
|
175
182
|
@parsed_bytes = parser_execute
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
return setup_body
|
|
179
|
-
elsif @parsed_bytes >= MAX_HEADER
|
|
180
|
-
raise HttpParserError,
|
|
181
|
-
"HEADER is longer than allowed, aborting client early."
|
|
182
|
-
end
|
|
184
|
+
@parser.finished? ? process_env_body : false
|
|
183
185
|
end
|
|
184
186
|
end
|
|
185
187
|
|
|
@@ -231,17 +233,6 @@ module Puma
|
|
|
231
233
|
end
|
|
232
234
|
|
|
233
235
|
def try_to_finish
|
|
234
|
-
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
|
235
|
-
@http_content_length_limit_exceeded = true
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
if @http_content_length_limit_exceeded
|
|
239
|
-
@buffer = nil
|
|
240
|
-
@body = EmptyBody
|
|
241
|
-
set_ready
|
|
242
|
-
return true
|
|
243
|
-
end
|
|
244
|
-
|
|
245
236
|
return read_body if in_data_phase
|
|
246
237
|
|
|
247
238
|
data = nil
|
|
@@ -272,18 +263,7 @@ module Puma
|
|
|
272
263
|
|
|
273
264
|
@parsed_bytes = parser_execute
|
|
274
265
|
|
|
275
|
-
|
|
276
|
-
@http_content_length_limit_exceeded = true
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
if @parser.finished?
|
|
280
|
-
setup_body
|
|
281
|
-
elsif @parsed_bytes >= MAX_HEADER
|
|
282
|
-
raise HttpParserError,
|
|
283
|
-
"HEADER is longer than allowed, aborting client early."
|
|
284
|
-
else
|
|
285
|
-
false
|
|
286
|
-
end
|
|
266
|
+
@parser.finished? ? process_env_body : false
|
|
287
267
|
end
|
|
288
268
|
|
|
289
269
|
def eagerly_finish
|
|
@@ -303,7 +283,12 @@ module Puma
|
|
|
303
283
|
# @return [Integer] bytes of buffer read by parser
|
|
304
284
|
#
|
|
305
285
|
def parser_execute
|
|
306
|
-
@parser.execute(@env, @buffer, @parsed_bytes)
|
|
286
|
+
ret = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
287
|
+
|
|
288
|
+
if @env[REQUEST_METHOD] && @supported_http_methods != :any && !@supported_http_methods.key?(@env[REQUEST_METHOD])
|
|
289
|
+
raise HttpParserError501, "#{@env[REQUEST_METHOD]} method is not supported"
|
|
290
|
+
end
|
|
291
|
+
ret
|
|
307
292
|
rescue => e
|
|
308
293
|
@env[HTTP_CONNECTION] = 'close'
|
|
309
294
|
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
|
@@ -337,6 +322,15 @@ module Puma
|
|
|
337
322
|
end
|
|
338
323
|
end
|
|
339
324
|
|
|
325
|
+
# processes the `env` and the request body
|
|
326
|
+
def process_env_body
|
|
327
|
+
temp = setup_body
|
|
328
|
+
normalize_env
|
|
329
|
+
req_env_post_parse
|
|
330
|
+
raise HttpParserError if @error_status_code
|
|
331
|
+
temp
|
|
332
|
+
end
|
|
333
|
+
|
|
340
334
|
def timeout!
|
|
341
335
|
write_error(408) if in_data_phase
|
|
342
336
|
raise ConnectionError
|
|
@@ -353,12 +347,12 @@ module Puma
|
|
|
353
347
|
return @peerip if @peerip
|
|
354
348
|
|
|
355
349
|
if @remote_addr_header
|
|
356
|
-
hdr = (@env[@remote_addr_header] ||
|
|
350
|
+
hdr = (@env[@remote_addr_header] || socket_peerip).split(/[\s,]/).first
|
|
357
351
|
@peerip = hdr
|
|
358
352
|
return hdr
|
|
359
353
|
end
|
|
360
354
|
|
|
361
|
-
@peerip ||=
|
|
355
|
+
@peerip ||= socket_peerip
|
|
362
356
|
end
|
|
363
357
|
|
|
364
358
|
def peer_family
|
|
@@ -393,19 +387,36 @@ module Puma
|
|
|
393
387
|
|
|
394
388
|
private
|
|
395
389
|
|
|
390
|
+
IPV4_MAPPED_IPV6_PREFIX = "::ffff:"
|
|
391
|
+
private_constant :IPV4_MAPPED_IPV6_PREFIX
|
|
392
|
+
|
|
393
|
+
def socket_peerip
|
|
394
|
+
unmap_ipv6(@io.peeraddr.last)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Converts IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1) back to
|
|
398
|
+
# their IPv4 form. These addresses appear when IPv4 clients connect to
|
|
399
|
+
# a dual-stack IPv6 socket.
|
|
400
|
+
def unmap_ipv6(addr)
|
|
401
|
+
addr.delete_prefix(IPV4_MAPPED_IPV6_PREFIX)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Checks the request `Transfer-Encoding` and/or `Content-Length` to see if
|
|
405
|
+
# they are valid. Raises errors if not, otherwise reads the body.
|
|
406
|
+
# @return [Boolean] true if the body can be completely read, false otherwise
|
|
407
|
+
#
|
|
396
408
|
def setup_body
|
|
397
409
|
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
398
410
|
|
|
399
411
|
if @env[HTTP_EXPECT] == CONTINUE
|
|
400
|
-
# TODO allow a hook here to check the headers before
|
|
401
|
-
# going forward
|
|
412
|
+
# TODO allow a hook here to check the headers before going forward
|
|
402
413
|
@io << HTTP_11_100
|
|
403
414
|
@io.flush
|
|
404
415
|
end
|
|
405
416
|
|
|
406
417
|
@read_header = false
|
|
407
418
|
|
|
408
|
-
|
|
419
|
+
parser_body = @parser.body
|
|
409
420
|
|
|
410
421
|
te = @env[TRANSFER_ENCODING2]
|
|
411
422
|
if te
|
|
@@ -422,10 +433,10 @@ module Puma
|
|
|
422
433
|
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
|
423
434
|
end
|
|
424
435
|
@env.delete TRANSFER_ENCODING2
|
|
425
|
-
return setup_chunked_body
|
|
436
|
+
return setup_chunked_body parser_body
|
|
426
437
|
elsif te_lwr == CHUNKED
|
|
427
438
|
@env.delete TRANSFER_ENCODING2
|
|
428
|
-
return setup_chunked_body
|
|
439
|
+
return setup_chunked_body parser_body
|
|
429
440
|
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
|
430
441
|
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
|
431
442
|
else
|
|
@@ -440,10 +451,12 @@ module Puma
|
|
|
440
451
|
if cl
|
|
441
452
|
# cannot contain characters that are not \d, or be empty
|
|
442
453
|
if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
|
|
454
|
+
@error_status_code = 400
|
|
455
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
443
456
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
|
444
457
|
end
|
|
445
458
|
else
|
|
446
|
-
@buffer =
|
|
459
|
+
@buffer = parser_body.empty? ? nil : parser_body
|
|
447
460
|
@body = EmptyBody
|
|
448
461
|
set_ready
|
|
449
462
|
return true
|
|
@@ -451,23 +464,25 @@ module Puma
|
|
|
451
464
|
|
|
452
465
|
content_length = cl.to_i
|
|
453
466
|
|
|
454
|
-
|
|
467
|
+
raise_above_http_content_limit if @http_content_length_limit&.< content_length
|
|
468
|
+
|
|
469
|
+
remain = content_length - parser_body.bytesize
|
|
455
470
|
|
|
456
471
|
if remain <= 0
|
|
457
|
-
# Part of the
|
|
472
|
+
# Part of the parser_body is a pipelined request OR garbage. We'll deal with that later.
|
|
458
473
|
if content_length == 0
|
|
459
474
|
@body = EmptyBody
|
|
460
|
-
if
|
|
475
|
+
if parser_body.empty?
|
|
461
476
|
@buffer = nil
|
|
462
477
|
else
|
|
463
|
-
@buffer =
|
|
478
|
+
@buffer = parser_body
|
|
464
479
|
end
|
|
465
480
|
elsif remain == 0
|
|
466
|
-
@body = StringIO.new
|
|
481
|
+
@body = StringIO.new parser_body
|
|
467
482
|
@buffer = nil
|
|
468
483
|
else
|
|
469
|
-
@body = StringIO.new(
|
|
470
|
-
@buffer =
|
|
484
|
+
@body = StringIO.new(parser_body[0,content_length])
|
|
485
|
+
@buffer = parser_body[content_length..-1]
|
|
471
486
|
end
|
|
472
487
|
set_ready
|
|
473
488
|
return true
|
|
@@ -479,12 +494,12 @@ module Puma
|
|
|
479
494
|
@body.binmode
|
|
480
495
|
@tempfile = @body
|
|
481
496
|
else
|
|
482
|
-
# The
|
|
483
|
-
# encoding as
|
|
484
|
-
@body = StringIO.new
|
|
497
|
+
# The parser_body[0,0] trick is to get an empty string in the same
|
|
498
|
+
# encoding as parser_body.
|
|
499
|
+
@body = StringIO.new parser_body[0,0]
|
|
485
500
|
end
|
|
486
501
|
|
|
487
|
-
@body.write
|
|
502
|
+
@body.write parser_body
|
|
488
503
|
|
|
489
504
|
@body_remain = remain
|
|
490
505
|
|
|
@@ -577,6 +592,10 @@ module Puma
|
|
|
577
592
|
|
|
578
593
|
# @version 5.0.0
|
|
579
594
|
def write_chunk(str)
|
|
595
|
+
if @http_content_length_limit&.< @chunked_content_length + str.bytesize
|
|
596
|
+
raise_above_http_content_limit
|
|
597
|
+
end
|
|
598
|
+
|
|
580
599
|
@chunked_content_length += @body.write(str)
|
|
581
600
|
end
|
|
582
601
|
|
|
@@ -709,8 +728,13 @@ module Puma
|
|
|
709
728
|
@ready = true
|
|
710
729
|
end
|
|
711
730
|
|
|
712
|
-
def
|
|
713
|
-
@
|
|
731
|
+
def raise_above_http_content_limit
|
|
732
|
+
@http_content_length_limit_exceeded = true
|
|
733
|
+
@buffer = nil
|
|
734
|
+
@body = EmptyBody
|
|
735
|
+
@error_status_code = 413
|
|
736
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
737
|
+
raise HttpParserError, "Payload Too Large"
|
|
714
738
|
end
|
|
715
739
|
end
|
|
716
740
|
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Puma
|
|
4
|
+
|
|
5
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# This module is included in `Client`. It contains code to process the `env`
|
|
9
|
+
# before it is passed to the app.
|
|
10
|
+
#
|
|
11
|
+
module ClientEnv # :nodoc:
|
|
12
|
+
|
|
13
|
+
include Puma::Const
|
|
14
|
+
|
|
15
|
+
# Given a Hash +env+ for the request read from +client+, add
|
|
16
|
+
# and fixup keys to comply with Rack's env guidelines.
|
|
17
|
+
# @param env [Hash] see Puma::Client#env, from request
|
|
18
|
+
# @param client [Puma::Client] only needed for Client#peerip
|
|
19
|
+
#
|
|
20
|
+
def normalize_env
|
|
21
|
+
if host = @env[HTTP_HOST]
|
|
22
|
+
# host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
|
|
23
|
+
if colon = host.rindex("]:") # IPV6 with port
|
|
24
|
+
@env[SERVER_NAME] = host[0, colon+1]
|
|
25
|
+
@env[SERVER_PORT] = host[colon+2, host.bytesize]
|
|
26
|
+
elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
|
|
27
|
+
@env[SERVER_NAME] = host[0, colon]
|
|
28
|
+
@env[SERVER_PORT] = host[colon+1, host.bytesize]
|
|
29
|
+
else
|
|
30
|
+
@env[SERVER_NAME] = host
|
|
31
|
+
@env[SERVER_PORT] = default_server_port
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
@env[SERVER_NAME] = LOCALHOST
|
|
35
|
+
@env[SERVER_PORT] = default_server_port
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
unless @env[REQUEST_PATH]
|
|
39
|
+
# it might be a dumbass full host request header
|
|
40
|
+
uri = begin
|
|
41
|
+
URI.parse(@env[REQUEST_URI])
|
|
42
|
+
rescue URI::InvalidURIError
|
|
43
|
+
raise Puma::HttpParserError
|
|
44
|
+
end
|
|
45
|
+
@env[REQUEST_PATH] = uri.path
|
|
46
|
+
|
|
47
|
+
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
|
48
|
+
# so only set the env value if there actually is a value.
|
|
49
|
+
@env[QUERY_STRING] = uri.query if uri.query
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@env[PATH_INFO] = @env[REQUEST_PATH].to_s # #to_s in case it's nil
|
|
53
|
+
|
|
54
|
+
# From https://www.ietf.org/rfc/rfc3875 :
|
|
55
|
+
# "Script authors should be aware that the REMOTE_ADDR and
|
|
56
|
+
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
57
|
+
# may not identify the ultimate source of the request.
|
|
58
|
+
# They identify the client for the immediate request to the
|
|
59
|
+
# server; that client may be a proxy, gateway, or other
|
|
60
|
+
# intermediary acting on behalf of the actual source client."
|
|
61
|
+
#
|
|
62
|
+
|
|
63
|
+
unless @env.key?(REMOTE_ADDR)
|
|
64
|
+
begin
|
|
65
|
+
addr = peerip
|
|
66
|
+
rescue Errno::ENOTCONN
|
|
67
|
+
# Client disconnects can result in an inability to get the
|
|
68
|
+
# peeraddr from the socket; default to unspec.
|
|
69
|
+
if peer_family == Socket::AF_INET6
|
|
70
|
+
addr = UNSPECIFIED_IPV6
|
|
71
|
+
else
|
|
72
|
+
addr = UNSPECIFIED_IPV4
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Set unix socket addrs to localhost
|
|
77
|
+
if addr.empty?
|
|
78
|
+
addr = peer_family == Socket::AF_INET6 ? LOCALHOST_IPV6 : LOCALHOST_IPV4
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@env[REMOTE_ADDR] = addr
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The legacy HTTP_VERSION header can be sent as a client header.
|
|
85
|
+
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
|
86
|
+
@env[HTTP_VERSION] = @env[SERVER_PROTOCOL] if @env_set_http_version
|
|
87
|
+
|
|
88
|
+
@env[PUMA_SOCKET] = @io
|
|
89
|
+
|
|
90
|
+
if @env[HTTPS_KEY] && @io.peercert
|
|
91
|
+
@env[PUMA_PEERCERT] = @io.peercert
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
@env[HIJACK_P] = true
|
|
95
|
+
@env[HIJACK] = method(:full_hijack).to_proc
|
|
96
|
+
|
|
97
|
+
@env[RACK_INPUT] = @body || EmptyBody
|
|
98
|
+
@env[RACK_URL_SCHEME] ||= default_server_port == PORT_443 ? HTTPS : HTTP
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Fixup any headers with `,` in the name to have `_` now. We emit
|
|
102
|
+
# headers with `,` in them during the parse phase to avoid ambiguity
|
|
103
|
+
# with the `-` to `_` conversion for critical headers. But here for
|
|
104
|
+
# compatibility, we'll convert them back. This code is written to
|
|
105
|
+
# avoid allocation in the common case (ie there are no headers
|
|
106
|
+
# with `,` in their names), that's why it has the extra conditionals.
|
|
107
|
+
#
|
|
108
|
+
# @note If a normalized version of a `,` header already exists, we ignore
|
|
109
|
+
# the `,` version. This prevents clobbering headers managed by proxies
|
|
110
|
+
# but not by clients (Like X-Forwarded-For).
|
|
111
|
+
#
|
|
112
|
+
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
|
113
|
+
# @version 5.0.3
|
|
114
|
+
#
|
|
115
|
+
def req_env_post_parse
|
|
116
|
+
to_delete = nil
|
|
117
|
+
to_add = nil
|
|
118
|
+
|
|
119
|
+
@env.each do |k,v|
|
|
120
|
+
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
|
121
|
+
if to_delete
|
|
122
|
+
to_delete << k
|
|
123
|
+
else
|
|
124
|
+
to_delete = [k]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
new_k = k.tr(",", "_")
|
|
128
|
+
if @env.key?(new_k)
|
|
129
|
+
next
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
unless to_add
|
|
133
|
+
to_add = {}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
to_add[new_k] = v
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if to_delete # rubocop:disable Style/SafeNavigation
|
|
141
|
+
to_delete.each { |k| env.delete(k) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if to_add
|
|
145
|
+
@env.merge! to_add
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# A rack extension. If the app writes #call'ables to this
|
|
149
|
+
# array, we will invoke them when the request is done.
|
|
150
|
+
#
|
|
151
|
+
env[RACK_AFTER_REPLY] ||= []
|
|
152
|
+
env[RACK_RESPONSE_FINISHED] ||= []
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
HTTP_ON_VALUES = { "on" => true, HTTPS => true }
|
|
156
|
+
private_constant :HTTP_ON_VALUES
|
|
157
|
+
|
|
158
|
+
# @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
|
|
159
|
+
#
|
|
160
|
+
def default_server_port
|
|
161
|
+
if HTTP_ON_VALUES[@env[HTTPS_KEY]] ||
|
|
162
|
+
@env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
|
|
163
|
+
@env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
|
|
164
|
+
@env[HTTP_X_FORWARDED_SSL] == "on"
|
|
165
|
+
PORT_443
|
|
166
|
+
else
|
|
167
|
+
PORT_80
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
data/lib/puma/cluster.rb
CHANGED
data/lib/puma/configuration.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
3
6
|
require_relative 'plugin'
|
|
4
7
|
require_relative 'const'
|
|
5
8
|
require_relative 'dsl'
|
|
@@ -131,14 +134,16 @@ module Puma
|
|
|
131
134
|
|
|
132
135
|
DEFAULTS = {
|
|
133
136
|
auto_trim_time: 30,
|
|
134
|
-
binds: ['tcp://
|
|
135
|
-
fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
|
|
137
|
+
binds: ['tcp://[::]:9292'.freeze],
|
|
136
138
|
debug: false,
|
|
137
|
-
enable_keep_alives: true,
|
|
138
139
|
early_hints: nil,
|
|
140
|
+
enable_keep_alives: true,
|
|
139
141
|
environment: 'development'.freeze,
|
|
142
|
+
fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
|
|
140
143
|
# Number of seconds to wait until we get the first data for the request.
|
|
141
144
|
first_data_timeout: 30,
|
|
145
|
+
force_shutdown_after: -1,
|
|
146
|
+
http_content_length_limit: nil,
|
|
142
147
|
# Number of seconds to wait until the next request before shutting down.
|
|
143
148
|
idle_timeout: nil,
|
|
144
149
|
io_selector_backend: :auto,
|
|
@@ -147,6 +152,7 @@ module Puma
|
|
|
147
152
|
# Limits how many requests a keep alive connection can make.
|
|
148
153
|
# The connection will be closed after it reaches `max_keep_alive`
|
|
149
154
|
# requests.
|
|
155
|
+
max_io_threads: 0,
|
|
150
156
|
max_keep_alive: 999,
|
|
151
157
|
max_threads: Puma.mri? ? 5 : 16,
|
|
152
158
|
min_threads: 0,
|
|
@@ -161,10 +167,10 @@ module Puma
|
|
|
161
167
|
raise_exception_on_sigterm: true,
|
|
162
168
|
reaping_time: 1,
|
|
163
169
|
remote_address: :socket,
|
|
164
|
-
silence_single_worker_warning: false,
|
|
165
170
|
silence_fork_callback_warning: false,
|
|
171
|
+
silence_single_worker_warning: false,
|
|
166
172
|
tag: File.basename(Dir.getwd),
|
|
167
|
-
tcp_host: '
|
|
173
|
+
tcp_host: '::'.freeze,
|
|
168
174
|
tcp_port: 9292,
|
|
169
175
|
wait_for_less_busy_worker: 0.005,
|
|
170
176
|
worker_boot_timeout: 60,
|
|
@@ -173,11 +179,10 @@ module Puma
|
|
|
173
179
|
worker_shutdown_timeout: 30,
|
|
174
180
|
worker_timeout: 60,
|
|
175
181
|
workers: 0,
|
|
176
|
-
http_content_length_limit: nil
|
|
177
182
|
}
|
|
178
183
|
|
|
179
184
|
def initialize(user_options={}, default_options = {}, env = ENV, &block)
|
|
180
|
-
default_options = self.puma_default_options(env).merge(default_options)
|
|
185
|
+
default_options = self.puma_default_options(env).merge(events: Events.new).merge(default_options)
|
|
181
186
|
|
|
182
187
|
@_options = UserFileDefaultOptions.new(user_options, default_options)
|
|
183
188
|
@plugins = PluginLoader.new
|
|
@@ -230,6 +235,8 @@ module Puma
|
|
|
230
235
|
|
|
231
236
|
def puma_default_options(env = ENV)
|
|
232
237
|
defaults = DEFAULTS.dup
|
|
238
|
+
defaults[:tcp_host] = self.class.default_tcp_host
|
|
239
|
+
defaults[:binds] = [self.class.default_tcp_bind]
|
|
233
240
|
puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
|
|
234
241
|
defaults
|
|
235
242
|
end
|
|
@@ -277,8 +284,10 @@ module Puma
|
|
|
277
284
|
# This also calls load if it hasn't been called yet.
|
|
278
285
|
def clamp
|
|
279
286
|
load unless @loaded
|
|
287
|
+
run_mode_hooks
|
|
280
288
|
set_conditional_default_options
|
|
281
289
|
@_options.finalize_values
|
|
290
|
+
rewrite_unavailable_ipv6_binds!
|
|
282
291
|
@clamped = true
|
|
283
292
|
warn_hooks
|
|
284
293
|
options
|
|
@@ -357,6 +366,23 @@ module Puma
|
|
|
357
366
|
options.final_options
|
|
358
367
|
end
|
|
359
368
|
|
|
369
|
+
def self.default_tcp_host
|
|
370
|
+
ipv6_interface_available? ? Const::UNSPECIFIED_IPV6 : Const::UNSPECIFIED_IPV4
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def self.default_tcp_bind(port = DEFAULTS[:tcp_port])
|
|
374
|
+
URI::Generic.build(scheme: 'tcp', host: default_tcp_host, port: Integer(port)).to_s
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def self.ipv6_interface_available?
|
|
378
|
+
Socket.getifaddrs.any? do |ifaddr|
|
|
379
|
+
addr = ifaddr.addr
|
|
380
|
+
addr&.ipv6? && !addr&.ipv6_loopback?
|
|
381
|
+
end
|
|
382
|
+
rescue StandardError
|
|
383
|
+
false
|
|
384
|
+
end
|
|
385
|
+
|
|
360
386
|
def self.temp_path
|
|
361
387
|
require 'tmpdir'
|
|
362
388
|
|
|
@@ -372,6 +398,31 @@ module Puma
|
|
|
372
398
|
|
|
373
399
|
private
|
|
374
400
|
|
|
401
|
+
def rewrite_unavailable_ipv6_binds!
|
|
402
|
+
return if self.class.ipv6_interface_available?
|
|
403
|
+
|
|
404
|
+
tried_ipv6_bind = false
|
|
405
|
+
bind_schemes = ['tcp', 'ssl']
|
|
406
|
+
|
|
407
|
+
@_options[:binds] = Array(@_options[:binds]).map do |bind|
|
|
408
|
+
uri = URI.parse(bind)
|
|
409
|
+
next bind unless bind_schemes.include?(uri.scheme)
|
|
410
|
+
|
|
411
|
+
host = uri.host&.delete_prefix('[')&.delete_suffix(']')
|
|
412
|
+
next bind unless host&.include?(':')
|
|
413
|
+
|
|
414
|
+
tried_ipv6_bind = true
|
|
415
|
+
next bind unless host == Const::UNSPECIFIED_IPV6
|
|
416
|
+
|
|
417
|
+
uri.host = Const::UNSPECIFIED_IPV4
|
|
418
|
+
uri.to_s
|
|
419
|
+
rescue URI::InvalidURIError
|
|
420
|
+
bind
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
warn "WARNING: IPv6 bind requested but no non-loopback IPv6 interface was detected" if tried_ipv6_bind
|
|
424
|
+
end
|
|
425
|
+
|
|
375
426
|
def require_processor_counter
|
|
376
427
|
require 'concurrent/utility/processor_counter'
|
|
377
428
|
rescue LoadError
|
|
@@ -433,6 +484,17 @@ module Puma
|
|
|
433
484
|
rack_app
|
|
434
485
|
end
|
|
435
486
|
|
|
487
|
+
def run_mode_hooks
|
|
488
|
+
workers_before = @_options[:workers]
|
|
489
|
+
key = workers_before > 0 ? :cluster : :single
|
|
490
|
+
|
|
491
|
+
@_options.all_of(key).each(&:call)
|
|
492
|
+
|
|
493
|
+
unless @_options[:workers] == workers_before
|
|
494
|
+
raise "cannot change the number of workers inside a #{key} configuration hook"
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
436
498
|
def set_conditional_default_options
|
|
437
499
|
@_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
|
|
438
500
|
(@_options[:workers] > 1) && Puma.forkable?
|
data/lib/puma/const.rb
CHANGED
|
@@ -100,8 +100,8 @@ module Puma
|
|
|
100
100
|
# too taxing on performance.
|
|
101
101
|
module Const
|
|
102
102
|
|
|
103
|
-
PUMA_VERSION = VERSION = "
|
|
104
|
-
CODE_NAME = "
|
|
103
|
+
PUMA_VERSION = VERSION = "8.0.0"
|
|
104
|
+
CODE_NAME = "Into the Arena"
|
|
105
105
|
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
|
107
107
|
|
data/lib/puma/control_cli.rb
CHANGED