puma 7.2.0 → 8.0.2
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 +68 -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/binder.rb +2 -2
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +117 -77
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster.rb +1 -1
- data/lib/puma/configuration.rb +70 -8
- data/lib/puma/const.rb +4 -3
- 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/bundle_pruner.rb +2 -4
- data/lib/puma/launcher.rb +3 -4
- data/lib/puma/log_writer.rb +8 -2
- data/lib/puma/{request.rb → response.rb} +15 -186
- data/lib/puma/server.rb +70 -35
- 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/binder.rb
CHANGED
|
@@ -90,7 +90,7 @@ module Puma
|
|
|
90
90
|
# @version 5.0.0
|
|
91
91
|
#
|
|
92
92
|
def create_activated_fds(env_hash)
|
|
93
|
-
@log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
|
93
|
+
@log_writer.debug { "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}" }
|
|
94
94
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
|
95
95
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
|
96
96
|
sock = TCPServer.for_fd(socket_activation_fd(index))
|
|
@@ -102,7 +102,7 @@ module Puma
|
|
|
102
102
|
[:tcp, addr, port]
|
|
103
103
|
end
|
|
104
104
|
@activated_sockets[key] = sock
|
|
105
|
-
@log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
|
105
|
+
@log_writer.debug { "Registered #{key.join ':'} for activation from LISTEN_FDS" }
|
|
106
106
|
end
|
|
107
107
|
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
|
108
108
|
end
|
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?
|
|
@@ -157,14 +163,15 @@ module Puma
|
|
|
157
163
|
@parser.reset
|
|
158
164
|
@io_buffer.reset
|
|
159
165
|
@read_header = true
|
|
160
|
-
@read_proxy = !!@expect_proxy_proto
|
|
166
|
+
@read_proxy = !!@expect_proxy_proto && @requests_served.zero?
|
|
161
167
|
@env = @proto_env.dup
|
|
162
168
|
@parsed_bytes = 0
|
|
163
169
|
@ready = false
|
|
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
|
|
|
@@ -211,37 +213,42 @@ module Puma
|
|
|
211
213
|
def try_to_parse_proxy_protocol
|
|
212
214
|
if @read_proxy
|
|
213
215
|
if @expect_proxy_proto == :v1
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
crlf_index = @buffer.index "\r\n"
|
|
217
|
+
|
|
218
|
+
unless crlf_index
|
|
219
|
+
if "PROXY ".start_with? @buffer
|
|
220
|
+
return false
|
|
221
|
+
elsif @buffer.start_with? "PROXY "
|
|
222
|
+
if @buffer.bytesize >= PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
223
|
+
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
218
224
|
end
|
|
219
|
-
|
|
225
|
+
return false
|
|
220
226
|
end
|
|
221
|
-
|
|
222
|
-
# request, this is just HTTP from a non-PROXY client; move on
|
|
227
|
+
|
|
223
228
|
@read_proxy = false
|
|
224
|
-
return
|
|
225
|
-
|
|
226
|
-
|
|
229
|
+
return true
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
if @buffer.start_with?("PROXY ") && crlf_index + 2 > PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
233
|
+
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
227
234
|
end
|
|
235
|
+
|
|
236
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
|
237
|
+
if md[1]
|
|
238
|
+
@peerip = md[1].split(" ")[0]
|
|
239
|
+
end
|
|
240
|
+
@buffer = md.post_match
|
|
241
|
+
end
|
|
242
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
|
243
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
|
244
|
+
@read_proxy = false
|
|
245
|
+
return @buffer.size > 0
|
|
228
246
|
end
|
|
229
247
|
end
|
|
230
248
|
true
|
|
231
249
|
end
|
|
232
250
|
|
|
233
251
|
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
252
|
return read_body if in_data_phase
|
|
246
253
|
|
|
247
254
|
data = nil
|
|
@@ -272,18 +279,7 @@ module Puma
|
|
|
272
279
|
|
|
273
280
|
@parsed_bytes = parser_execute
|
|
274
281
|
|
|
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
|
|
282
|
+
@parser.finished? ? process_env_body : false
|
|
287
283
|
end
|
|
288
284
|
|
|
289
285
|
def eagerly_finish
|
|
@@ -303,7 +299,12 @@ module Puma
|
|
|
303
299
|
# @return [Integer] bytes of buffer read by parser
|
|
304
300
|
#
|
|
305
301
|
def parser_execute
|
|
306
|
-
@parser.execute(@env, @buffer, @parsed_bytes)
|
|
302
|
+
ret = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
303
|
+
|
|
304
|
+
if @env[REQUEST_METHOD] && @supported_http_methods != :any && !@supported_http_methods.key?(@env[REQUEST_METHOD])
|
|
305
|
+
raise HttpParserError501, "#{@env[REQUEST_METHOD]} method is not supported"
|
|
306
|
+
end
|
|
307
|
+
ret
|
|
307
308
|
rescue => e
|
|
308
309
|
@env[HTTP_CONNECTION] = 'close'
|
|
309
310
|
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
|
@@ -337,6 +338,15 @@ module Puma
|
|
|
337
338
|
end
|
|
338
339
|
end
|
|
339
340
|
|
|
341
|
+
# processes the `env` and the request body
|
|
342
|
+
def process_env_body
|
|
343
|
+
temp = setup_body
|
|
344
|
+
normalize_env
|
|
345
|
+
req_env_post_parse
|
|
346
|
+
raise HttpParserError if @error_status_code
|
|
347
|
+
temp
|
|
348
|
+
end
|
|
349
|
+
|
|
340
350
|
def timeout!
|
|
341
351
|
write_error(408) if in_data_phase
|
|
342
352
|
raise ConnectionError
|
|
@@ -353,12 +363,12 @@ module Puma
|
|
|
353
363
|
return @peerip if @peerip
|
|
354
364
|
|
|
355
365
|
if @remote_addr_header
|
|
356
|
-
hdr = (@env[@remote_addr_header] ||
|
|
366
|
+
hdr = (@env[@remote_addr_header] || socket_peerip).split(/[\s,]/).first
|
|
357
367
|
@peerip = hdr
|
|
358
368
|
return hdr
|
|
359
369
|
end
|
|
360
370
|
|
|
361
|
-
@peerip ||=
|
|
371
|
+
@peerip ||= socket_peerip
|
|
362
372
|
end
|
|
363
373
|
|
|
364
374
|
def peer_family
|
|
@@ -393,19 +403,36 @@ module Puma
|
|
|
393
403
|
|
|
394
404
|
private
|
|
395
405
|
|
|
406
|
+
IPV4_MAPPED_IPV6_PREFIX = "::ffff:"
|
|
407
|
+
private_constant :IPV4_MAPPED_IPV6_PREFIX
|
|
408
|
+
|
|
409
|
+
def socket_peerip
|
|
410
|
+
unmap_ipv6(@io.peeraddr.last)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Converts IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1) back to
|
|
414
|
+
# their IPv4 form. These addresses appear when IPv4 clients connect to
|
|
415
|
+
# a dual-stack IPv6 socket.
|
|
416
|
+
def unmap_ipv6(addr)
|
|
417
|
+
addr.delete_prefix(IPV4_MAPPED_IPV6_PREFIX)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Checks the request `Transfer-Encoding` and/or `Content-Length` to see if
|
|
421
|
+
# they are valid. Raises errors if not, otherwise reads the body.
|
|
422
|
+
# @return [Boolean] true if the body can be completely read, false otherwise
|
|
423
|
+
#
|
|
396
424
|
def setup_body
|
|
397
425
|
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
398
426
|
|
|
399
427
|
if @env[HTTP_EXPECT] == CONTINUE
|
|
400
|
-
# TODO allow a hook here to check the headers before
|
|
401
|
-
# going forward
|
|
428
|
+
# TODO allow a hook here to check the headers before going forward
|
|
402
429
|
@io << HTTP_11_100
|
|
403
430
|
@io.flush
|
|
404
431
|
end
|
|
405
432
|
|
|
406
433
|
@read_header = false
|
|
407
434
|
|
|
408
|
-
|
|
435
|
+
parser_body = @parser.body
|
|
409
436
|
|
|
410
437
|
te = @env[TRANSFER_ENCODING2]
|
|
411
438
|
if te
|
|
@@ -422,10 +449,10 @@ module Puma
|
|
|
422
449
|
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
|
423
450
|
end
|
|
424
451
|
@env.delete TRANSFER_ENCODING2
|
|
425
|
-
return setup_chunked_body
|
|
452
|
+
return setup_chunked_body parser_body
|
|
426
453
|
elsif te_lwr == CHUNKED
|
|
427
454
|
@env.delete TRANSFER_ENCODING2
|
|
428
|
-
return setup_chunked_body
|
|
455
|
+
return setup_chunked_body parser_body
|
|
429
456
|
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
|
430
457
|
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
|
431
458
|
else
|
|
@@ -440,10 +467,12 @@ module Puma
|
|
|
440
467
|
if cl
|
|
441
468
|
# cannot contain characters that are not \d, or be empty
|
|
442
469
|
if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
|
|
470
|
+
@error_status_code = 400
|
|
471
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
443
472
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
|
444
473
|
end
|
|
445
474
|
else
|
|
446
|
-
@buffer =
|
|
475
|
+
@buffer = parser_body.empty? ? nil : parser_body
|
|
447
476
|
@body = EmptyBody
|
|
448
477
|
set_ready
|
|
449
478
|
return true
|
|
@@ -451,23 +480,25 @@ module Puma
|
|
|
451
480
|
|
|
452
481
|
content_length = cl.to_i
|
|
453
482
|
|
|
454
|
-
|
|
483
|
+
raise_above_http_content_limit if @http_content_length_limit&.< content_length
|
|
484
|
+
|
|
485
|
+
remain = content_length - parser_body.bytesize
|
|
455
486
|
|
|
456
487
|
if remain <= 0
|
|
457
|
-
# Part of the
|
|
488
|
+
# Part of the parser_body is a pipelined request OR garbage. We'll deal with that later.
|
|
458
489
|
if content_length == 0
|
|
459
490
|
@body = EmptyBody
|
|
460
|
-
if
|
|
491
|
+
if parser_body.empty?
|
|
461
492
|
@buffer = nil
|
|
462
493
|
else
|
|
463
|
-
@buffer =
|
|
494
|
+
@buffer = parser_body
|
|
464
495
|
end
|
|
465
496
|
elsif remain == 0
|
|
466
|
-
@body = StringIO.new
|
|
497
|
+
@body = StringIO.new parser_body
|
|
467
498
|
@buffer = nil
|
|
468
499
|
else
|
|
469
|
-
@body = StringIO.new(
|
|
470
|
-
@buffer =
|
|
500
|
+
@body = StringIO.new(parser_body[0,content_length])
|
|
501
|
+
@buffer = parser_body[content_length..-1]
|
|
471
502
|
end
|
|
472
503
|
set_ready
|
|
473
504
|
return true
|
|
@@ -479,12 +510,12 @@ module Puma
|
|
|
479
510
|
@body.binmode
|
|
480
511
|
@tempfile = @body
|
|
481
512
|
else
|
|
482
|
-
# The
|
|
483
|
-
# encoding as
|
|
484
|
-
@body = StringIO.new
|
|
513
|
+
# The parser_body[0,0] trick is to get an empty string in the same
|
|
514
|
+
# encoding as parser_body.
|
|
515
|
+
@body = StringIO.new parser_body[0,0]
|
|
485
516
|
end
|
|
486
517
|
|
|
487
|
-
@body.write
|
|
518
|
+
@body.write parser_body
|
|
488
519
|
|
|
489
520
|
@body_remain = remain
|
|
490
521
|
|
|
@@ -577,6 +608,10 @@ module Puma
|
|
|
577
608
|
|
|
578
609
|
# @version 5.0.0
|
|
579
610
|
def write_chunk(str)
|
|
611
|
+
if @http_content_length_limit&.< @chunked_content_length + str.bytesize
|
|
612
|
+
raise_above_http_content_limit
|
|
613
|
+
end
|
|
614
|
+
|
|
580
615
|
@chunked_content_length += @body.write(str)
|
|
581
616
|
end
|
|
582
617
|
|
|
@@ -709,8 +744,13 @@ module Puma
|
|
|
709
744
|
@ready = true
|
|
710
745
|
end
|
|
711
746
|
|
|
712
|
-
def
|
|
713
|
-
@
|
|
747
|
+
def raise_above_http_content_limit
|
|
748
|
+
@http_content_length_limit_exceeded = true
|
|
749
|
+
@buffer = nil
|
|
750
|
+
@body = EmptyBody
|
|
751
|
+
@error_status_code = 413
|
|
752
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
753
|
+
raise HttpParserError, "Payload Too Large"
|
|
714
754
|
end
|
|
715
755
|
end
|
|
716
756
|
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