puma 7.1.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 +116 -0
- data/README.md +18 -11
- 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/deployment.md +58 -23
- 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/jungle/README.md +1 -1
- data/docs/kubernetes.md +3 -10
- data/docs/plugins.md +2 -2
- data/docs/signals.md +10 -10
- data/docs/stats.md +1 -1
- data/docs/systemd.md +3 -3
- 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/ext/puma_http11/puma_http11.c +101 -109
- data/lib/puma/app/status.rb +10 -2
- 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/worker.rb +10 -9
- data/lib/puma/cluster.rb +3 -4
- data/lib/puma/configuration.rb +85 -16
- 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 +90 -14
- data/lib/puma/launcher.rb +7 -7
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/reactor.rb +3 -12
- data/lib/puma/{request.rb → response.rb} +25 -194
- data/lib/puma/runner.rb +1 -1
- data/lib/puma/server.rb +72 -37
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +2 -2
- data/lib/puma/thread_pool.rb +129 -23
- data/lib/rack/handler/puma.rb +1 -1
- data/tools/Dockerfile +13 -5
- metadata +17 -7
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/app/status.rb
CHANGED
|
@@ -7,13 +7,16 @@ module Puma
|
|
|
7
7
|
# can respond to.
|
|
8
8
|
class Status
|
|
9
9
|
OK_STATUS = '{ "status": "ok" }'.freeze
|
|
10
|
+
READ_ONLY_COMMANDS = %w[gc-stats stats].freeze
|
|
10
11
|
|
|
11
12
|
# @param launcher [::Puma::Launcher]
|
|
12
13
|
# @param token [String, nil] the token used for authentication
|
|
14
|
+
# @param data_only [Boolean] if true, restrict to read-only data commands
|
|
13
15
|
#
|
|
14
|
-
def initialize(launcher, token
|
|
16
|
+
def initialize(launcher, token: nil, data_only: false)
|
|
15
17
|
@launcher = launcher
|
|
16
18
|
@auth_token = token
|
|
19
|
+
@enabled_commands = READ_ONLY_COMMANDS if data_only
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
# most commands call methods in `::Puma::Launcher` based on command in
|
|
@@ -25,8 +28,13 @@ module Puma
|
|
|
25
28
|
|
|
26
29
|
# resp_type is processed by following case statement, return
|
|
27
30
|
# is a number (status) or a string used as the body of a 200 response
|
|
31
|
+
command = env['PATH_INFO'][/\/([^\/]+)$/, 1]
|
|
32
|
+
if @enabled_commands && !@enabled_commands.include?(command)
|
|
33
|
+
return rack_response(404, "Command #{command.inspect} unavailable", 'text/plain')
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
resp_type =
|
|
29
|
-
case
|
|
37
|
+
case command
|
|
30
38
|
when 'stop'
|
|
31
39
|
@launcher.stop ; 200
|
|
32
40
|
|
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/worker.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Puma
|
|
|
14
14
|
class Worker < Puma::Runner # :nodoc:
|
|
15
15
|
attr_reader :index, :master
|
|
16
16
|
|
|
17
|
-
def initialize(index:, master:, launcher:, pipes:,
|
|
17
|
+
def initialize(index:, master:, launcher:, pipes:, app: nil)
|
|
18
18
|
super(launcher)
|
|
19
19
|
|
|
20
20
|
@index = index
|
|
@@ -23,7 +23,8 @@ module Puma
|
|
|
23
23
|
@worker_write = pipes[:worker_write]
|
|
24
24
|
@fork_pipe = pipes[:fork_pipe]
|
|
25
25
|
@wakeup = pipes[:wakeup]
|
|
26
|
-
@
|
|
26
|
+
@app = app
|
|
27
|
+
@server = nil
|
|
27
28
|
@hook_data = {}
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -57,7 +58,7 @@ module Puma
|
|
|
57
58
|
@config.run_hooks(:before_worker_boot, index, @log_writer, @hook_data)
|
|
58
59
|
|
|
59
60
|
begin
|
|
60
|
-
|
|
61
|
+
@server = start_server
|
|
61
62
|
rescue Exception => e
|
|
62
63
|
log "! Unable to start worker"
|
|
63
64
|
log e
|
|
@@ -85,7 +86,7 @@ module Puma
|
|
|
85
86
|
if idx == -1 # stop server
|
|
86
87
|
if restart_server.length > 0
|
|
87
88
|
restart_server.clear
|
|
88
|
-
server.begin_restart(true)
|
|
89
|
+
@server.begin_restart(true)
|
|
89
90
|
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
|
90
91
|
end
|
|
91
92
|
elsif idx == -2 # refork cycle is done
|
|
@@ -103,7 +104,7 @@ module Puma
|
|
|
103
104
|
Signal.trap "SIGTERM" do
|
|
104
105
|
@worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
|
|
105
106
|
restart_server.clear
|
|
106
|
-
server.stop
|
|
107
|
+
@server.stop
|
|
107
108
|
restart_server << false
|
|
108
109
|
end
|
|
109
110
|
|
|
@@ -115,7 +116,7 @@ module Puma
|
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
while restart_server.pop
|
|
118
|
-
server_thread = server.run
|
|
119
|
+
server_thread = @server.run
|
|
119
120
|
|
|
120
121
|
if @log_writer.debug? && index == 0
|
|
121
122
|
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
|
@@ -129,13 +130,13 @@ module Puma
|
|
|
129
130
|
begin
|
|
130
131
|
payload = base_payload.dup
|
|
131
132
|
|
|
132
|
-
hsh = server.stats
|
|
133
|
+
hsh = @server.stats
|
|
133
134
|
hsh.each do |k, v|
|
|
134
135
|
payload << %Q! "#{k}":#{v || 0},!
|
|
135
136
|
end
|
|
136
137
|
# sub call properly adds 'closing' string
|
|
137
138
|
io << payload.sub(/,\z/, " }\n")
|
|
138
|
-
server.reset_max
|
|
139
|
+
@server.reset_max
|
|
139
140
|
rescue IOError
|
|
140
141
|
break
|
|
141
142
|
end
|
|
@@ -164,7 +165,7 @@ module Puma
|
|
|
164
165
|
launcher: @launcher,
|
|
165
166
|
pipes: { check_pipe: @check_pipe,
|
|
166
167
|
worker_write: @worker_write },
|
|
167
|
-
|
|
168
|
+
app: @app
|
|
168
169
|
new_worker.run
|
|
169
170
|
end
|
|
170
171
|
|
data/lib/puma/cluster.rb
CHANGED
|
@@ -87,7 +87,7 @@ module Puma
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
debug "Spawned worker: #{pid}"
|
|
90
|
-
@workers
|
|
90
|
+
@workers.insert(idx, WorkerHandle.new(idx, pid, @phase, @options))
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
if @options[:fork_worker] && all_workers_in_phase?
|
|
@@ -186,7 +186,7 @@ module Puma
|
|
|
186
186
|
# we need to phase any workers out (which will restart
|
|
187
187
|
# in the right phase).
|
|
188
188
|
#
|
|
189
|
-
w = @workers.find { |x| x.phase
|
|
189
|
+
w = @workers.find { |x| x.phase < @phase }
|
|
190
190
|
|
|
191
191
|
if w
|
|
192
192
|
if refork
|
|
@@ -221,12 +221,11 @@ module Puma
|
|
|
221
221
|
pipes[:wakeup] = @wakeup
|
|
222
222
|
end
|
|
223
223
|
|
|
224
|
-
server = start_server if preload?
|
|
225
224
|
new_worker = Worker.new index: index,
|
|
226
225
|
master: master,
|
|
227
226
|
launcher: @launcher,
|
|
228
227
|
pipes: pipes,
|
|
229
|
-
|
|
228
|
+
app: (app if preload?)
|
|
230
229
|
new_worker.run
|
|
231
230
|
end
|
|
232
231
|
|