puma 4.3.12 → 6.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +1729 -521
- data/LICENSE +23 -20
- data/README.md +169 -45
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +31 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +84 -128
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +49 -12
- data/ext/puma_http11/http11_parser.c +46 -48
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +3 -3
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +278 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
- data/ext/puma_http11/puma_http11.c +46 -57
- data/lib/puma/app/status.rb +53 -39
- data/lib/puma/binder.rb +237 -121
- data/lib/puma/cli.rb +34 -34
- data/lib/puma/client.rb +172 -98
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +226 -231
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +114 -87
- data/lib/puma/const.rb +139 -95
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +516 -110
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +44 -2
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +164 -155
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +36 -19
- data/lib/puma/minissl.rb +230 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +7 -11
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +93 -368
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +92 -75
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +321 -794
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +140 -68
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -7
- data/lib/rack/handler/puma.rb +113 -87
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +33 -24
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/accept_nonblock.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/cli.rb
CHANGED
@@ -3,36 +3,31 @@
|
|
3
3
|
require 'optparse'
|
4
4
|
require 'uri'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
require_relative '../puma'
|
7
|
+
require_relative 'configuration'
|
8
|
+
require_relative 'launcher'
|
9
|
+
require_relative 'const'
|
10
|
+
require_relative 'log_writer'
|
11
11
|
|
12
12
|
module Puma
|
13
13
|
class << self
|
14
|
-
# The CLI exports
|
15
|
-
# apps to pick it up. An app
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# The CLI exports a Puma::Configuration instance here to allow
|
15
|
+
# apps to pick it up. An app must load this object conditionally
|
16
|
+
# because it is not set if the app is launched via any mechanism
|
17
|
+
# other than the CLI class.
|
18
18
|
attr_accessor :cli_config
|
19
19
|
end
|
20
20
|
|
21
21
|
# Handles invoke a Puma::Server in a command line style.
|
22
22
|
#
|
23
23
|
class CLI
|
24
|
-
KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
|
25
|
-
|
26
24
|
# Create a new CLI object using +argv+ as the command line
|
27
25
|
# arguments.
|
28
26
|
#
|
29
|
-
|
30
|
-
# this object will report status on.
|
31
|
-
#
|
32
|
-
def initialize(argv, events=Events.stdio)
|
27
|
+
def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
|
33
28
|
@debug = false
|
34
29
|
@argv = argv.dup
|
35
|
-
|
30
|
+
@log_writer = log_writer
|
36
31
|
@events = events
|
37
32
|
|
38
33
|
@conf = nil
|
@@ -68,7 +63,7 @@ module Puma
|
|
68
63
|
end
|
69
64
|
end
|
70
65
|
|
71
|
-
@launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
|
66
|
+
@launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
|
72
67
|
end
|
73
68
|
|
74
69
|
attr_reader :launcher
|
@@ -80,9 +75,9 @@ module Puma
|
|
80
75
|
@launcher.run
|
81
76
|
end
|
82
77
|
|
83
|
-
|
78
|
+
private
|
84
79
|
def unsupported(str)
|
85
|
-
@
|
80
|
+
@log_writer.error(str)
|
86
81
|
raise UnsupportedOption
|
87
82
|
end
|
88
83
|
|
@@ -98,22 +93,26 @@ module Puma
|
|
98
93
|
#
|
99
94
|
|
100
95
|
def setup_options
|
101
|
-
@conf = Configuration.new do |user_config, file_config|
|
96
|
+
@conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
|
102
97
|
@parser = OptionParser.new do |o|
|
103
98
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
104
99
|
user_config.bind arg
|
105
100
|
end
|
106
101
|
|
102
|
+
o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
|
103
|
+
user_config.bind_to_activated_sockets(arg || true)
|
104
|
+
end
|
105
|
+
|
107
106
|
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
108
107
|
file_config.load arg
|
109
108
|
end
|
110
109
|
|
111
|
-
|
112
|
-
|
110
|
+
# Identical to supplying --config "-", but more semantic
|
111
|
+
o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
|
112
|
+
file_config.load "-"
|
113
113
|
end
|
114
114
|
|
115
|
-
|
116
|
-
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
115
|
+
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
117
116
|
configure_control_url(arg)
|
118
117
|
end
|
119
118
|
|
@@ -122,11 +121,6 @@ module Puma
|
|
122
121
|
@control_options[:auth_token] = arg
|
123
122
|
end
|
124
123
|
|
125
|
-
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
126
|
-
user_config.daemonize
|
127
|
-
user_config.quiet
|
128
|
-
end
|
129
|
-
|
130
124
|
o.on "--debug", "Log lowlevel debugging information" do
|
131
125
|
user_config.debug
|
132
126
|
end
|
@@ -140,13 +134,19 @@ module Puma
|
|
140
134
|
user_config.environment arg
|
141
135
|
end
|
142
136
|
|
137
|
+
o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
|
138
|
+
"Fork new workers from existing worker. Cluster mode only",
|
139
|
+
"Auto-refork after REQUESTS (default 1000)" do |*args|
|
140
|
+
user_config.fork_worker(*args.compact)
|
141
|
+
end
|
142
|
+
|
143
143
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
144
144
|
$LOAD_PATH.unshift(*arg.split(':'))
|
145
145
|
end
|
146
146
|
|
147
147
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
148
148
|
"Use -b for more advanced options" do |arg|
|
149
|
-
user_config.bind "tcp://#{Configuration::
|
149
|
+
user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
|
150
150
|
end
|
151
151
|
|
152
152
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
@@ -179,6 +179,10 @@ module Puma
|
|
179
179
|
user_config.restart_command cmd
|
180
180
|
end
|
181
181
|
|
182
|
+
o.on "-s", "--silent", "Do not log prompt messages other than errors" do
|
183
|
+
@log_writer = LogWriter.new(NullIO.new, $stderr)
|
184
|
+
end
|
185
|
+
|
182
186
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
183
187
|
user_config.state_path arg
|
184
188
|
end
|
@@ -192,10 +196,6 @@ module Puma
|
|
192
196
|
end
|
193
197
|
end
|
194
198
|
|
195
|
-
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
196
|
-
user_config.tcp_mode!
|
197
|
-
end
|
198
|
-
|
199
199
|
o.on "--early-hints", "Enable early hints support" do
|
200
200
|
user_config.early_hints
|
201
201
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -8,7 +8,8 @@ class IO
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
require_relative 'detect'
|
12
|
+
require_relative 'io_buffer'
|
12
13
|
require 'tempfile'
|
13
14
|
require 'forwardable'
|
14
15
|
|
@@ -25,6 +26,9 @@ module Puma
|
|
25
26
|
|
26
27
|
class HttpParserError501 < IOError; end
|
27
28
|
|
29
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
30
|
+
|
31
|
+
|
28
32
|
# An instance of this class represents a unique request from a client.
|
29
33
|
# For example, this could be a web request from a browser or from CURL.
|
30
34
|
#
|
@@ -38,14 +42,15 @@ module Puma
|
|
38
42
|
# the header and body are fully buffered via the `try_to_finish` method.
|
39
43
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
40
44
|
#
|
41
|
-
class Client
|
45
|
+
class Client # :nodoc:
|
42
46
|
|
43
47
|
# this tests all values but the last, which must be chunked
|
44
48
|
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
45
49
|
|
46
50
|
# chunked body validation
|
47
51
|
CHUNK_SIZE_INVALID = /[^\h]/.freeze
|
48
|
-
CHUNK_VALID_ENDING =
|
52
|
+
CHUNK_VALID_ENDING = Const::LINE_END
|
53
|
+
CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
|
49
54
|
|
50
55
|
# Content-Length header value validation
|
51
56
|
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
@@ -62,16 +67,14 @@ module Puma
|
|
62
67
|
def initialize(io, env=nil)
|
63
68
|
@io = io
|
64
69
|
@to_io = io.to_io
|
70
|
+
@io_buffer = IOBuffer.new
|
65
71
|
@proto_env = env
|
66
|
-
|
67
|
-
@env = nil
|
68
|
-
else
|
69
|
-
@env = env.dup
|
70
|
-
end
|
72
|
+
@env = env&.dup
|
71
73
|
|
72
74
|
@parser = HttpParser.new
|
73
75
|
@parsed_bytes = 0
|
74
76
|
@read_header = true
|
77
|
+
@read_proxy = false
|
75
78
|
@ready = false
|
76
79
|
|
77
80
|
@body = nil
|
@@ -84,23 +87,39 @@ module Puma
|
|
84
87
|
@requests_served = 0
|
85
88
|
@hijacked = false
|
86
89
|
|
90
|
+
@http_content_length_limit = nil
|
91
|
+
@http_content_length_limit_exceeded = false
|
92
|
+
|
87
93
|
@peerip = nil
|
94
|
+
@peer_family = nil
|
95
|
+
@listener = nil
|
88
96
|
@remote_addr_header = nil
|
97
|
+
@expect_proxy_proto = false
|
89
98
|
|
90
99
|
@body_remain = 0
|
91
100
|
|
92
101
|
@in_last_chunk = false
|
102
|
+
|
103
|
+
# need unfrozen ASCII-8BIT, +'' is UTF-8
|
104
|
+
@read_buffer = String.new # rubocop: disable Performance/UnfreezeString
|
93
105
|
end
|
94
106
|
|
95
107
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
96
|
-
:tempfile
|
108
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
97
109
|
|
98
|
-
attr_writer :peerip
|
110
|
+
attr_writer :peerip, :http_content_length_limit
|
99
111
|
|
100
|
-
attr_accessor :remote_addr_header
|
112
|
+
attr_accessor :remote_addr_header, :listener
|
101
113
|
|
102
114
|
def_delegators :@io, :closed?
|
103
115
|
|
116
|
+
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
117
|
+
# used for MiniSSL::Socket
|
118
|
+
def io_ok?
|
119
|
+
@to_io.is_a?(::BasicSocket) && !closed?
|
120
|
+
end
|
121
|
+
|
122
|
+
# @!attribute [r] inspect
|
104
123
|
def inspect
|
105
124
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
106
125
|
end
|
@@ -112,27 +131,38 @@ module Puma
|
|
112
131
|
env[HIJACK_IO] ||= @io
|
113
132
|
end
|
114
133
|
|
134
|
+
# @!attribute [r] in_data_phase
|
115
135
|
def in_data_phase
|
116
|
-
|
136
|
+
!(@read_header || @read_proxy)
|
117
137
|
end
|
118
138
|
|
119
139
|
def set_timeout(val)
|
120
|
-
@timeout_at =
|
140
|
+
@timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
|
141
|
+
end
|
142
|
+
|
143
|
+
# Number of seconds until the timeout elapses.
|
144
|
+
def timeout
|
145
|
+
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
121
146
|
end
|
122
147
|
|
123
148
|
def reset(fast_check=true)
|
124
149
|
@parser.reset
|
150
|
+
@io_buffer.reset
|
125
151
|
@read_header = true
|
152
|
+
@read_proxy = !!@expect_proxy_proto
|
126
153
|
@env = @proto_env.dup
|
127
154
|
@body = nil
|
128
155
|
@tempfile = nil
|
129
156
|
@parsed_bytes = 0
|
130
157
|
@ready = false
|
131
158
|
@body_remain = 0
|
132
|
-
@peerip = nil
|
159
|
+
@peerip = nil if @remote_addr_header
|
133
160
|
@in_last_chunk = false
|
161
|
+
@http_content_length_limit_exceeded = false
|
134
162
|
|
135
163
|
if @buffer
|
164
|
+
return false unless try_to_parse_proxy_protocol
|
165
|
+
|
136
166
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
137
167
|
|
138
168
|
if @parser.finished?
|
@@ -145,8 +175,7 @@ module Puma
|
|
145
175
|
return false
|
146
176
|
else
|
147
177
|
begin
|
148
|
-
if fast_check &&
|
149
|
-
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
178
|
+
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
150
179
|
return try_to_finish
|
151
180
|
end
|
152
181
|
rescue IOError
|
@@ -159,19 +188,56 @@ module Puma
|
|
159
188
|
def close
|
160
189
|
begin
|
161
190
|
@io.close
|
162
|
-
rescue IOError
|
163
|
-
|
191
|
+
rescue IOError, Errno::EBADF
|
192
|
+
Puma::Util.purge_interrupt_queue
|
164
193
|
end
|
165
194
|
end
|
166
195
|
|
196
|
+
# If necessary, read the PROXY protocol from the buffer. Returns
|
197
|
+
# false if more data is needed.
|
198
|
+
def try_to_parse_proxy_protocol
|
199
|
+
if @read_proxy
|
200
|
+
if @expect_proxy_proto == :v1
|
201
|
+
if @buffer.include? "\r\n"
|
202
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
203
|
+
if md[1]
|
204
|
+
@peerip = md[1].split(" ")[0]
|
205
|
+
end
|
206
|
+
@buffer = md.post_match
|
207
|
+
end
|
208
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
209
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
210
|
+
@read_proxy = false
|
211
|
+
return @buffer.size > 0
|
212
|
+
else
|
213
|
+
return false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
true
|
218
|
+
end
|
219
|
+
|
167
220
|
def try_to_finish
|
168
|
-
|
221
|
+
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
222
|
+
@http_content_length_limit_exceeded = true
|
223
|
+
end
|
224
|
+
|
225
|
+
if @http_content_length_limit_exceeded
|
226
|
+
@buffer = nil
|
227
|
+
@body = EmptyBody
|
228
|
+
set_ready
|
229
|
+
return true
|
230
|
+
end
|
231
|
+
|
232
|
+
return read_body if in_data_phase
|
169
233
|
|
170
234
|
begin
|
171
235
|
data = @io.read_nonblock(CHUNK_SIZE)
|
172
236
|
rescue IO::WaitReadable
|
173
237
|
return false
|
174
|
-
rescue
|
238
|
+
rescue EOFError
|
239
|
+
# Swallow error, don't log
|
240
|
+
rescue SystemCallError, IOError
|
175
241
|
raise ConnectionError, "Connection error detected during read"
|
176
242
|
end
|
177
243
|
|
@@ -188,8 +254,14 @@ module Puma
|
|
188
254
|
@buffer = data
|
189
255
|
end
|
190
256
|
|
257
|
+
return false unless try_to_parse_proxy_protocol
|
258
|
+
|
191
259
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
192
260
|
|
261
|
+
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
262
|
+
@http_content_length_limit_exceeded = true
|
263
|
+
end
|
264
|
+
|
193
265
|
if @parser.finished?
|
194
266
|
return setup_body
|
195
267
|
elsif @parsed_bytes >= MAX_HEADER
|
@@ -200,68 +272,20 @@ module Puma
|
|
200
272
|
false
|
201
273
|
end
|
202
274
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
data = @io.sysread_nonblock(CHUNK_SIZE)
|
209
|
-
rescue OpenSSL::SSL::SSLError => e
|
210
|
-
return false if e.kind_of? IO::WaitReadable
|
211
|
-
raise e
|
212
|
-
end
|
213
|
-
|
214
|
-
# No data means a closed socket
|
215
|
-
unless data
|
216
|
-
@buffer = nil
|
217
|
-
set_ready
|
218
|
-
raise EOFError
|
219
|
-
end
|
220
|
-
|
221
|
-
if @buffer
|
222
|
-
@buffer << data
|
223
|
-
else
|
224
|
-
@buffer = data
|
225
|
-
end
|
226
|
-
|
227
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
228
|
-
|
229
|
-
if @parser.finished?
|
230
|
-
return setup_body
|
231
|
-
elsif @parsed_bytes >= MAX_HEADER
|
232
|
-
raise HttpParserError,
|
233
|
-
"HEADER is longer than allowed, aborting client early."
|
234
|
-
end
|
235
|
-
|
236
|
-
false
|
237
|
-
end
|
238
|
-
|
239
|
-
def eagerly_finish
|
240
|
-
return true if @ready
|
241
|
-
|
242
|
-
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
243
|
-
return true if jruby_start_try_to_finish
|
244
|
-
end
|
245
|
-
|
246
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
247
|
-
try_to_finish
|
248
|
-
end
|
249
|
-
|
250
|
-
else
|
275
|
+
def eagerly_finish
|
276
|
+
return true if @ready
|
277
|
+
return false unless @to_io.wait_readable(0)
|
278
|
+
try_to_finish
|
279
|
+
end
|
251
280
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
end
|
257
|
-
end # IS_JRUBY
|
281
|
+
def finish(timeout)
|
282
|
+
return if @ready
|
283
|
+
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
284
|
+
end
|
258
285
|
|
259
|
-
def
|
260
|
-
|
261
|
-
|
262
|
-
IO.select([@to_io], nil, nil)
|
263
|
-
end
|
264
|
-
true
|
286
|
+
def timeout!
|
287
|
+
write_error(408) if in_data_phase
|
288
|
+
raise ConnectionError
|
265
289
|
end
|
266
290
|
|
267
291
|
def write_error(status_code)
|
@@ -275,7 +299,7 @@ module Puma
|
|
275
299
|
return @peerip if @peerip
|
276
300
|
|
277
301
|
if @remote_addr_header
|
278
|
-
hdr = (@env[@remote_addr_header] ||
|
302
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
279
303
|
@peerip = hdr
|
280
304
|
return hdr
|
281
305
|
end
|
@@ -283,10 +307,40 @@ module Puma
|
|
283
307
|
@peerip ||= @io.peeraddr.last
|
284
308
|
end
|
285
309
|
|
310
|
+
def peer_family
|
311
|
+
return @peer_family if @peer_family
|
312
|
+
|
313
|
+
@peer_family ||= begin
|
314
|
+
@io.local_address.afamily
|
315
|
+
rescue
|
316
|
+
Socket::AF_INET
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Returns true if the persistent connection can be closed immediately
|
321
|
+
# without waiting for the configured idle/shutdown timeout.
|
322
|
+
# @version 5.0.0
|
323
|
+
#
|
324
|
+
def can_close?
|
325
|
+
# Allow connection to close if we're not in the middle of parsing a request.
|
326
|
+
@parsed_bytes == 0
|
327
|
+
end
|
328
|
+
|
329
|
+
def expect_proxy_proto=(val)
|
330
|
+
if val
|
331
|
+
if @read_header
|
332
|
+
@read_proxy = true
|
333
|
+
end
|
334
|
+
else
|
335
|
+
@read_proxy = false
|
336
|
+
end
|
337
|
+
@expect_proxy_proto = val
|
338
|
+
end
|
339
|
+
|
286
340
|
private
|
287
341
|
|
288
342
|
def setup_body
|
289
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
343
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
290
344
|
|
291
345
|
if @env[HTTP_EXPECT] == CONTINUE
|
292
346
|
# TODO allow a hook here to check the headers before
|
@@ -329,8 +383,8 @@ module Puma
|
|
329
383
|
cl = @env[CONTENT_LENGTH]
|
330
384
|
|
331
385
|
if cl
|
332
|
-
# cannot contain characters that are not \d
|
333
|
-
if cl
|
386
|
+
# cannot contain characters that are not \d, or be empty
|
387
|
+
if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
|
334
388
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
335
389
|
end
|
336
390
|
else
|
@@ -351,6 +405,7 @@ module Puma
|
|
351
405
|
|
352
406
|
if remain > MAX_BODY
|
353
407
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
408
|
+
@body.unlink
|
354
409
|
@body.binmode
|
355
410
|
@tempfile = @body
|
356
411
|
else
|
@@ -363,7 +418,7 @@ module Puma
|
|
363
418
|
|
364
419
|
@body_remain = remain
|
365
420
|
|
366
|
-
|
421
|
+
false
|
367
422
|
end
|
368
423
|
|
369
424
|
def read_body
|
@@ -382,7 +437,7 @@ module Puma
|
|
382
437
|
end
|
383
438
|
|
384
439
|
begin
|
385
|
-
chunk = @io.read_nonblock(want)
|
440
|
+
chunk = @io.read_nonblock(want, @read_buffer)
|
386
441
|
rescue IO::WaitReadable
|
387
442
|
return false
|
388
443
|
rescue SystemCallError, IOError
|
@@ -414,7 +469,7 @@ module Puma
|
|
414
469
|
def read_chunked_body
|
415
470
|
while true
|
416
471
|
begin
|
417
|
-
chunk = @io.read_nonblock(4096)
|
472
|
+
chunk = @io.read_nonblock(4096, @read_buffer)
|
418
473
|
rescue IO::WaitReadable
|
419
474
|
return false
|
420
475
|
rescue SystemCallError, IOError
|
@@ -430,7 +485,7 @@ module Puma
|
|
430
485
|
end
|
431
486
|
|
432
487
|
if decode_chunk(chunk)
|
433
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
488
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
434
489
|
return true
|
435
490
|
end
|
436
491
|
end
|
@@ -442,17 +497,18 @@ module Puma
|
|
442
497
|
@prev_chunk = ""
|
443
498
|
|
444
499
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
500
|
+
@body.unlink
|
445
501
|
@body.binmode
|
446
502
|
@tempfile = @body
|
447
|
-
|
448
503
|
@chunked_content_length = 0
|
449
504
|
|
450
505
|
if decode_chunk(body)
|
451
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
506
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
452
507
|
return true
|
453
508
|
end
|
454
509
|
end
|
455
510
|
|
511
|
+
# @version 5.0.0
|
456
512
|
def write_chunk(str)
|
457
513
|
@chunked_content_length += @body.write(str)
|
458
514
|
end
|
@@ -466,7 +522,15 @@ module Puma
|
|
466
522
|
chunk = chunk[@partial_part_left..-1]
|
467
523
|
@partial_part_left = 0
|
468
524
|
else
|
469
|
-
|
525
|
+
if @partial_part_left > 2
|
526
|
+
if @partial_part_left == chunk.size + 1
|
527
|
+
# Don't include the last \r
|
528
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
529
|
+
else
|
530
|
+
# don't include the last \r\n
|
531
|
+
write_chunk(chunk)
|
532
|
+
end
|
533
|
+
end
|
470
534
|
@partial_part_left -= chunk.size
|
471
535
|
return false
|
472
536
|
end
|
@@ -481,11 +545,11 @@ module Puma
|
|
481
545
|
|
482
546
|
while !io.eof?
|
483
547
|
line = io.gets
|
484
|
-
if line.end_with?(
|
548
|
+
if line.end_with?(CHUNK_VALID_ENDING)
|
485
549
|
# Puma doesn't process chunk extensions, but should parse if they're
|
486
550
|
# present, which is the reason for the semicolon regex
|
487
551
|
chunk_hex = line.strip[/\A[^;]+/]
|
488
|
-
if chunk_hex
|
552
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
489
553
|
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
490
554
|
end
|
491
555
|
len = chunk_hex.to_i(16)
|
@@ -493,13 +557,19 @@ module Puma
|
|
493
557
|
@in_last_chunk = true
|
494
558
|
@body.rewind
|
495
559
|
rest = io.read
|
496
|
-
|
497
|
-
if rest.bytesize < last_crlf_size
|
560
|
+
if rest.bytesize < CHUNK_VALID_ENDING_SIZE
|
498
561
|
@buffer = nil
|
499
|
-
@partial_part_left =
|
562
|
+
@partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
|
500
563
|
return false
|
501
564
|
else
|
502
|
-
|
565
|
+
# if the next character is a CRLF, set buffer to everything after that CRLF
|
566
|
+
start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
|
567
|
+
CHUNK_VALID_ENDING_SIZE
|
568
|
+
else # we have started a trailer section, which we do not support. skip it!
|
569
|
+
rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
|
570
|
+
end
|
571
|
+
|
572
|
+
@buffer = rest[start_of_rest..-1]
|
503
573
|
@buffer = nil if @buffer.empty?
|
504
574
|
set_ready
|
505
575
|
return true
|
@@ -548,10 +618,14 @@ module Puma
|
|
548
618
|
|
549
619
|
def set_ready
|
550
620
|
if @body_read_start
|
551
|
-
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :
|
621
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
552
622
|
end
|
553
623
|
@requests_served += 1
|
554
624
|
@ready = true
|
555
625
|
end
|
626
|
+
|
627
|
+
def above_http_content_limit(value)
|
628
|
+
@http_content_length_limit&.< value
|
629
|
+
end
|
556
630
|
end
|
557
631
|
end
|