puma 7.2.1 → 8.0.0
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 +42 -4
- 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 +101 -93
- 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 +3 -4
- 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/{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 +13 -2
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
|
|
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,53 +213,26 @@ 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
|
-
|
|
218
|
-
return false
|
|
219
|
-
elsif @buffer.start_with? "PROXY "
|
|
220
|
-
if @buffer.bytesize >= PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
221
|
-
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
216
|
+
if @buffer.include? "\r\n"
|
|
217
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
|
218
|
+
if md[1]
|
|
219
|
+
@peerip = md[1].split(" ")[0]
|
|
222
220
|
end
|
|
223
|
-
|
|
221
|
+
@buffer = md.post_match
|
|
224
222
|
end
|
|
225
|
-
|
|
223
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
|
224
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
|
226
225
|
@read_proxy = false
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if @buffer.start_with?("PROXY ") && crlf_index + 2 > PROXY_PROTOCOL_V1_MAX_LENGTH
|
|
231
|
-
raise ConnectionError, "PROXY protocol v1 line is too long"
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
|
235
|
-
if md[1]
|
|
236
|
-
@peerip = md[1].split(" ")[0]
|
|
237
|
-
end
|
|
238
|
-
@buffer = md.post_match
|
|
226
|
+
return @buffer.size > 0
|
|
227
|
+
else
|
|
228
|
+
return false
|
|
239
229
|
end
|
|
240
|
-
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
|
241
|
-
# request, this is just HTTP from a non-PROXY client; move on
|
|
242
|
-
@read_proxy = false
|
|
243
|
-
return @buffer.size > 0
|
|
244
230
|
end
|
|
245
231
|
end
|
|
246
232
|
true
|
|
247
233
|
end
|
|
248
234
|
|
|
249
235
|
def try_to_finish
|
|
250
|
-
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
|
251
|
-
@http_content_length_limit_exceeded = true
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
if @http_content_length_limit_exceeded
|
|
255
|
-
@buffer = nil
|
|
256
|
-
@body = EmptyBody
|
|
257
|
-
set_ready
|
|
258
|
-
return true
|
|
259
|
-
end
|
|
260
|
-
|
|
261
236
|
return read_body if in_data_phase
|
|
262
237
|
|
|
263
238
|
data = nil
|
|
@@ -288,18 +263,7 @@ module Puma
|
|
|
288
263
|
|
|
289
264
|
@parsed_bytes = parser_execute
|
|
290
265
|
|
|
291
|
-
|
|
292
|
-
@http_content_length_limit_exceeded = true
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
if @parser.finished?
|
|
296
|
-
setup_body
|
|
297
|
-
elsif @parsed_bytes >= MAX_HEADER
|
|
298
|
-
raise HttpParserError,
|
|
299
|
-
"HEADER is longer than allowed, aborting client early."
|
|
300
|
-
else
|
|
301
|
-
false
|
|
302
|
-
end
|
|
266
|
+
@parser.finished? ? process_env_body : false
|
|
303
267
|
end
|
|
304
268
|
|
|
305
269
|
def eagerly_finish
|
|
@@ -319,7 +283,12 @@ module Puma
|
|
|
319
283
|
# @return [Integer] bytes of buffer read by parser
|
|
320
284
|
#
|
|
321
285
|
def parser_execute
|
|
322
|
-
@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
|
|
323
292
|
rescue => e
|
|
324
293
|
@env[HTTP_CONNECTION] = 'close'
|
|
325
294
|
raise e unless HttpParserError === e && e.message.include?('non-SSL')
|
|
@@ -353,6 +322,15 @@ module Puma
|
|
|
353
322
|
end
|
|
354
323
|
end
|
|
355
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
|
+
|
|
356
334
|
def timeout!
|
|
357
335
|
write_error(408) if in_data_phase
|
|
358
336
|
raise ConnectionError
|
|
@@ -369,12 +347,12 @@ module Puma
|
|
|
369
347
|
return @peerip if @peerip
|
|
370
348
|
|
|
371
349
|
if @remote_addr_header
|
|
372
|
-
hdr = (@env[@remote_addr_header] ||
|
|
350
|
+
hdr = (@env[@remote_addr_header] || socket_peerip).split(/[\s,]/).first
|
|
373
351
|
@peerip = hdr
|
|
374
352
|
return hdr
|
|
375
353
|
end
|
|
376
354
|
|
|
377
|
-
@peerip ||=
|
|
355
|
+
@peerip ||= socket_peerip
|
|
378
356
|
end
|
|
379
357
|
|
|
380
358
|
def peer_family
|
|
@@ -409,19 +387,36 @@ module Puma
|
|
|
409
387
|
|
|
410
388
|
private
|
|
411
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
|
+
#
|
|
412
408
|
def setup_body
|
|
413
409
|
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
|
414
410
|
|
|
415
411
|
if @env[HTTP_EXPECT] == CONTINUE
|
|
416
|
-
# TODO allow a hook here to check the headers before
|
|
417
|
-
# going forward
|
|
412
|
+
# TODO allow a hook here to check the headers before going forward
|
|
418
413
|
@io << HTTP_11_100
|
|
419
414
|
@io.flush
|
|
420
415
|
end
|
|
421
416
|
|
|
422
417
|
@read_header = false
|
|
423
418
|
|
|
424
|
-
|
|
419
|
+
parser_body = @parser.body
|
|
425
420
|
|
|
426
421
|
te = @env[TRANSFER_ENCODING2]
|
|
427
422
|
if te
|
|
@@ -438,10 +433,10 @@ module Puma
|
|
|
438
433
|
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
|
439
434
|
end
|
|
440
435
|
@env.delete TRANSFER_ENCODING2
|
|
441
|
-
return setup_chunked_body
|
|
436
|
+
return setup_chunked_body parser_body
|
|
442
437
|
elsif te_lwr == CHUNKED
|
|
443
438
|
@env.delete TRANSFER_ENCODING2
|
|
444
|
-
return setup_chunked_body
|
|
439
|
+
return setup_chunked_body parser_body
|
|
445
440
|
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
|
446
441
|
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
|
447
442
|
else
|
|
@@ -456,10 +451,12 @@ module Puma
|
|
|
456
451
|
if cl
|
|
457
452
|
# cannot contain characters that are not \d, or be empty
|
|
458
453
|
if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
|
|
454
|
+
@error_status_code = 400
|
|
455
|
+
@env[HTTP_CONNECTION] = 'close'
|
|
459
456
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
|
460
457
|
end
|
|
461
458
|
else
|
|
462
|
-
@buffer =
|
|
459
|
+
@buffer = parser_body.empty? ? nil : parser_body
|
|
463
460
|
@body = EmptyBody
|
|
464
461
|
set_ready
|
|
465
462
|
return true
|
|
@@ -467,23 +464,25 @@ module Puma
|
|
|
467
464
|
|
|
468
465
|
content_length = cl.to_i
|
|
469
466
|
|
|
470
|
-
|
|
467
|
+
raise_above_http_content_limit if @http_content_length_limit&.< content_length
|
|
468
|
+
|
|
469
|
+
remain = content_length - parser_body.bytesize
|
|
471
470
|
|
|
472
471
|
if remain <= 0
|
|
473
|
-
# Part of the
|
|
472
|
+
# Part of the parser_body is a pipelined request OR garbage. We'll deal with that later.
|
|
474
473
|
if content_length == 0
|
|
475
474
|
@body = EmptyBody
|
|
476
|
-
if
|
|
475
|
+
if parser_body.empty?
|
|
477
476
|
@buffer = nil
|
|
478
477
|
else
|
|
479
|
-
@buffer =
|
|
478
|
+
@buffer = parser_body
|
|
480
479
|
end
|
|
481
480
|
elsif remain == 0
|
|
482
|
-
@body = StringIO.new
|
|
481
|
+
@body = StringIO.new parser_body
|
|
483
482
|
@buffer = nil
|
|
484
483
|
else
|
|
485
|
-
@body = StringIO.new(
|
|
486
|
-
@buffer =
|
|
484
|
+
@body = StringIO.new(parser_body[0,content_length])
|
|
485
|
+
@buffer = parser_body[content_length..-1]
|
|
487
486
|
end
|
|
488
487
|
set_ready
|
|
489
488
|
return true
|
|
@@ -495,12 +494,12 @@ module Puma
|
|
|
495
494
|
@body.binmode
|
|
496
495
|
@tempfile = @body
|
|
497
496
|
else
|
|
498
|
-
# The
|
|
499
|
-
# encoding as
|
|
500
|
-
@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]
|
|
501
500
|
end
|
|
502
501
|
|
|
503
|
-
@body.write
|
|
502
|
+
@body.write parser_body
|
|
504
503
|
|
|
505
504
|
@body_remain = remain
|
|
506
505
|
|
|
@@ -593,6 +592,10 @@ module Puma
|
|
|
593
592
|
|
|
594
593
|
# @version 5.0.0
|
|
595
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
|
+
|
|
596
599
|
@chunked_content_length += @body.write(str)
|
|
597
600
|
end
|
|
598
601
|
|
|
@@ -725,8 +728,13 @@ module Puma
|
|
|
725
728
|
@ready = true
|
|
726
729
|
end
|
|
727
730
|
|
|
728
|
-
def
|
|
729
|
-
@
|
|
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"
|
|
730
738
|
end
|
|
731
739
|
end
|
|
732
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