puma 5.6.8 → 6.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +332 -16
- data/README.md +79 -29
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/restart.md +1 -0
- data/docs/systemd.md +3 -6
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +16 -9
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +127 -19
- data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +157 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +50 -53
- data/lib/puma/cli.rb +16 -18
- data/lib/puma/client.rb +59 -19
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +102 -40
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +77 -59
- data/lib/puma/const.rb +137 -92
- data/lib/puma/control_cli.rb +15 -11
- data/lib/puma/detect.rb +7 -4
- data/lib/puma/dsl.rb +250 -56
- data/lib/puma/error_logger.rb +18 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +102 -175
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +26 -12
- data/lib/puma/minissl.rb +104 -11
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +380 -172
- data/lib/puma/runner.rb +56 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +137 -89
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +3 -6
- data/lib/puma/thread_pool.rb +57 -19
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +9 -10
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- metadata +11 -7
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
- data/lib/rack/version_restriction.rb +0 -15
data/lib/puma/request.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Puma
|
4
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
5
|
+
|
4
6
|
|
5
7
|
# The methods here are included in Server, but are separated into this file.
|
6
8
|
# All the methods here pertain to passing the request to the app, then
|
@@ -10,7 +12,24 @@ module Puma
|
|
10
12
|
# #handle_request, which is called in Server#process_client.
|
11
13
|
# @version 5.0.3
|
12
14
|
#
|
13
|
-
module Request
|
15
|
+
module Request # :nodoc:
|
16
|
+
|
17
|
+
# Single element array body: smaller bodies are written to io_buffer first,
|
18
|
+
# then a single write from io_buffer. Larger sizes are written separately.
|
19
|
+
# Also fixes max size of chunked file body read.
|
20
|
+
BODY_LEN_MAX = 1_024 * 256
|
21
|
+
|
22
|
+
# File body: smaller bodies are combined with io_buffer, then written to
|
23
|
+
# socket. Larger bodies are written separately using `copy_stream`
|
24
|
+
IO_BODY_MAX = 1_024 * 64
|
25
|
+
|
26
|
+
# Array body: elements are collected in io_buffer. When io_buffer's size
|
27
|
+
# exceeds value, they are written to the socket.
|
28
|
+
IO_BUFFER_LEN_MAX = 1_024 * 512
|
29
|
+
|
30
|
+
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
31
|
+
|
32
|
+
CUSTOM_STAT = 'CUSTOM'
|
14
33
|
|
15
34
|
include Puma::Const
|
16
35
|
|
@@ -25,40 +44,44 @@ module Puma
|
|
25
44
|
#
|
26
45
|
# Finally, it'll return +true+ on keep-alive connections.
|
27
46
|
# @param client [Puma::Client]
|
28
|
-
# @param lines [Puma::IOBuffer]
|
29
47
|
# @param requests [Integer]
|
30
48
|
# @return [Boolean,:async]
|
31
49
|
#
|
32
|
-
def handle_request(client,
|
50
|
+
def handle_request(client, requests)
|
33
51
|
env = client.env
|
34
|
-
|
52
|
+
io_buffer = client.io_buffer
|
53
|
+
socket = client.io # io may be a MiniSSL::Socket
|
54
|
+
app_body = nil
|
55
|
+
|
35
56
|
|
36
|
-
return false if closed_socket?(
|
57
|
+
return false if closed_socket?(socket)
|
58
|
+
|
59
|
+
if client.http_content_length_limit_exceeded
|
60
|
+
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
61
|
+
end
|
37
62
|
|
38
63
|
normalize_env env, client
|
39
64
|
|
40
|
-
env[PUMA_SOCKET] =
|
65
|
+
env[PUMA_SOCKET] = socket
|
41
66
|
|
42
|
-
if env[HTTPS_KEY] &&
|
43
|
-
env[PUMA_PEERCERT] =
|
67
|
+
if env[HTTPS_KEY] && socket.peercert
|
68
|
+
env[PUMA_PEERCERT] = socket.peercert
|
44
69
|
end
|
45
70
|
|
46
71
|
env[HIJACK_P] = true
|
47
72
|
env[HIJACK] = client
|
48
73
|
|
49
|
-
|
50
|
-
|
51
|
-
head = env[REQUEST_METHOD] == HEAD
|
52
|
-
|
53
|
-
env[RACK_INPUT] = body
|
74
|
+
env[RACK_INPUT] = client.body
|
54
75
|
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
55
76
|
|
56
77
|
if @early_hints
|
57
78
|
env[EARLY_HINTS] = lambda { |headers|
|
58
79
|
begin
|
59
|
-
|
80
|
+
unless (str = str_early_hints headers).empty?
|
81
|
+
fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
|
82
|
+
end
|
60
83
|
rescue ConnectionError => e
|
61
|
-
@
|
84
|
+
@log_writer.debug_error e
|
62
85
|
# noop, if we lost the socket we just won't send the early hints
|
63
86
|
end
|
64
87
|
}
|
@@ -69,123 +92,174 @@ module Puma
|
|
69
92
|
# A rack extension. If the app writes #call'ables to this
|
70
93
|
# array, we will invoke them when the request is done.
|
71
94
|
#
|
72
|
-
|
95
|
+
env[RACK_AFTER_REPLY] ||= []
|
73
96
|
|
74
97
|
begin
|
75
|
-
|
76
|
-
status, headers,
|
98
|
+
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
|
99
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
77
100
|
@app.call(env)
|
78
101
|
end
|
102
|
+
else
|
103
|
+
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
104
|
+
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
105
|
+
end
|
79
106
|
|
80
|
-
|
107
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
108
|
+
# is called
|
109
|
+
res_body = app_body
|
81
110
|
|
82
|
-
|
111
|
+
# full hijack, app called env['rack.hijack']
|
112
|
+
return :async if client.hijacked
|
83
113
|
|
84
|
-
|
85
|
-
unless headers.empty? and res_body == []
|
86
|
-
raise "async response must have empty headers and body"
|
87
|
-
end
|
114
|
+
status = status.to_i
|
88
115
|
|
89
|
-
|
116
|
+
if status == -1
|
117
|
+
unless headers.empty? and res_body == []
|
118
|
+
raise "async response must have empty headers and body"
|
90
119
|
end
|
91
|
-
rescue ThreadPool::ForceShutdown => e
|
92
|
-
@events.unknown_error e, client, "Rack app"
|
93
|
-
@events.log "Detected force shutdown of a thread"
|
94
|
-
|
95
|
-
status, headers, res_body = lowlevel_error(e, env, 503)
|
96
|
-
rescue Exception => e
|
97
|
-
@events.unknown_error e, client, "Rack app"
|
98
120
|
|
99
|
-
|
121
|
+
return :async
|
100
122
|
end
|
123
|
+
rescue ThreadPool::ForceShutdown => e
|
124
|
+
@log_writer.unknown_error e, client, "Rack app"
|
125
|
+
@log_writer.log "Detected force shutdown of a thread"
|
101
126
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
res_info[:content_length] = if res_body.kind_of? Array and res_body.size == 1
|
107
|
-
res_body[0].bytesize
|
108
|
-
else
|
109
|
-
nil
|
110
|
-
end
|
127
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
128
|
+
rescue Exception => e
|
129
|
+
@log_writer.unknown_error e, client, "Rack app"
|
111
130
|
|
112
|
-
|
131
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
132
|
+
end
|
133
|
+
prepare_response(status, headers, res_body, requests, client)
|
134
|
+
ensure
|
135
|
+
io_buffer.reset
|
136
|
+
uncork_socket client.io
|
137
|
+
app_body.close if app_body.respond_to? :close
|
138
|
+
client.tempfile&.unlink
|
139
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
140
|
+
begin
|
141
|
+
after_reply.each { |o| o.call }
|
142
|
+
rescue StandardError => e
|
143
|
+
@log_writer.debug_error e
|
144
|
+
end unless after_reply.empty?
|
145
|
+
end
|
113
146
|
|
114
|
-
|
147
|
+
# Assembles the headers and prepares the body for actually sending the
|
148
|
+
# response via `#fast_write_response`.
|
149
|
+
#
|
150
|
+
# @param status [Integer] the status returned by the Rack application
|
151
|
+
# @param headers [Hash] the headers returned by the Rack application
|
152
|
+
# @param res_body [Array] the body returned by the Rack application or
|
153
|
+
# a call to `Server#lowlevel_error`
|
154
|
+
# @param requests [Integer] number of inline requests handled
|
155
|
+
# @param client [Puma::Client]
|
156
|
+
# @return [Boolean,:async] keep-alive status or `:async`
|
157
|
+
def prepare_response(status, headers, res_body, requests, client)
|
158
|
+
env = client.env
|
159
|
+
socket = client.io
|
160
|
+
io_buffer = client.io_buffer
|
115
161
|
|
116
|
-
|
162
|
+
return false if closed_socket?(socket)
|
117
163
|
|
118
|
-
|
119
|
-
|
164
|
+
# Close the connection after a reasonable number of inline requests
|
165
|
+
# if the server is at capacity and the listener has a new connection ready.
|
166
|
+
# This allows Puma to service connections fairly when the number
|
167
|
+
# of concurrent connections exceeds the size of the threadpool.
|
168
|
+
force_keep_alive = requests < @max_fast_inline ||
|
169
|
+
@thread_pool.busy_threads < @max_threads ||
|
170
|
+
!client.listener.to_io.wait_readable(0)
|
120
171
|
|
121
|
-
|
122
|
-
|
123
|
-
|
172
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
173
|
+
|
174
|
+
close_body = false
|
175
|
+
response_hijack = nil
|
176
|
+
content_length = resp_info[:content_length]
|
177
|
+
keep_alive = resp_info[:keep_alive]
|
178
|
+
|
179
|
+
if res_body.respond_to?(:each) && !resp_info[:response_hijack]
|
180
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
181
|
+
# content_length will be set if it can be determined
|
182
|
+
if !content_length && !resp_info[:transfer_encoding] && status != 204
|
183
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
|
184
|
+
array_body.is_a?(Array)
|
185
|
+
body = array_body.compact
|
186
|
+
content_length = body.sum(&:bytesize)
|
187
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
188
|
+
body = res_body
|
189
|
+
content_length = body.size
|
190
|
+
elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
|
191
|
+
body = File.open fn, 'rb'
|
192
|
+
content_length = body.size
|
193
|
+
close_body = true
|
194
|
+
else
|
195
|
+
body = res_body
|
124
196
|
end
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
197
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
198
|
+
File.readable?(fn = res_body.to_path)
|
199
|
+
body = File.open fn, 'rb'
|
200
|
+
content_length = body.size
|
201
|
+
close_body = true
|
202
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
|
203
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
204
|
+
# Sprockets::Asset
|
205
|
+
content_length = res_body.bytesize unless content_length
|
206
|
+
if (body_str = res_body.to_hash[:source])
|
207
|
+
body = [body_str]
|
208
|
+
else # avoid each and use a File object
|
209
|
+
body = File.open fn, 'rb'
|
210
|
+
close_body = true
|
211
|
+
end
|
212
|
+
else
|
213
|
+
body = res_body
|
137
214
|
end
|
215
|
+
else
|
216
|
+
# partial hijack, from Rack spec:
|
217
|
+
# Servers must ignore the body part of the response tuple when the
|
218
|
+
# rack.hijack response header is present.
|
219
|
+
response_hijack = resp_info[:response_hijack] || res_body
|
220
|
+
end
|
138
221
|
|
139
|
-
|
140
|
-
|
141
|
-
fast_write io, lines.to_s
|
222
|
+
line_ending = LINE_END
|
142
223
|
|
143
|
-
|
144
|
-
response_hijack.call io
|
145
|
-
return :async
|
146
|
-
end
|
224
|
+
cork_socket socket
|
147
225
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
fast_write io, line_ending
|
155
|
-
else
|
156
|
-
fast_write io, part
|
157
|
-
end
|
158
|
-
io.flush
|
226
|
+
if resp_info[:no_body]
|
227
|
+
# 101 (Switching Protocols) doesn't return here or have content_length,
|
228
|
+
# it should be using `response_hijack`
|
229
|
+
unless status == 101
|
230
|
+
if content_length && status != 204
|
231
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
159
232
|
end
|
160
233
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
rescue SystemCallError, IOError
|
166
|
-
raise ConnectionError, "Connection error detected during write"
|
234
|
+
io_buffer << LINE_END
|
235
|
+
fast_write_str socket, io_buffer.read_and_reset
|
236
|
+
socket.flush
|
237
|
+
return keep_alive
|
167
238
|
end
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
ensure
|
176
|
-
# Whatever happens, we MUST call `close` on the response body.
|
177
|
-
# Otherwise Rack::BodyProxy callbacks may not fire and lead to various state leaks
|
178
|
-
res_body.close if res_body.respond_to? :close
|
239
|
+
else
|
240
|
+
if content_length
|
241
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
242
|
+
chunked = false
|
243
|
+
elsif !response_hijack && resp_info[:allow_chunked]
|
244
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
245
|
+
chunked = true
|
179
246
|
end
|
247
|
+
end
|
180
248
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
249
|
+
io_buffer << line_ending
|
250
|
+
|
251
|
+
# partial hijack, we write headers, then hand the socket to the app via
|
252
|
+
# response_hijack.call
|
253
|
+
if response_hijack
|
254
|
+
fast_write_str socket, io_buffer.read_and_reset
|
255
|
+
uncork_socket socket
|
256
|
+
response_hijack.call socket
|
257
|
+
return :async
|
186
258
|
end
|
187
259
|
|
188
|
-
|
260
|
+
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
261
|
+
body.close if close_body
|
262
|
+
keep_alive
|
189
263
|
end
|
190
264
|
|
191
265
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -199,45 +273,132 @@ module Puma
|
|
199
273
|
end
|
200
274
|
end
|
201
275
|
|
202
|
-
#
|
203
|
-
#
|
276
|
+
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
277
|
+
# and body segments (called by `fast_write_response`).
|
278
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
279
|
+
# Large strings may not be written in one pass, especially if `io` is a
|
280
|
+
# `MiniSSL::Socket`.
|
281
|
+
# @param socket [#write_nonblock] the request/response socket
|
204
282
|
# @param str [String] the string written to the io
|
205
283
|
# @raise [ConnectionError]
|
206
284
|
#
|
207
|
-
def
|
285
|
+
def fast_write_str(socket, str)
|
208
286
|
n = 0
|
209
|
-
|
287
|
+
byte_size = str.bytesize
|
288
|
+
while n < byte_size
|
210
289
|
begin
|
211
|
-
n
|
290
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
212
291
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
213
|
-
unless
|
214
|
-
raise ConnectionError,
|
292
|
+
unless socket.wait_writable WRITE_TIMEOUT
|
293
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
215
294
|
end
|
216
|
-
|
217
295
|
retry
|
218
296
|
rescue Errno::EPIPE, SystemCallError, IOError
|
219
|
-
raise ConnectionError,
|
297
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
220
298
|
end
|
221
|
-
|
222
|
-
return if n == str.bytesize
|
223
|
-
str = str.byteslice(n..-1)
|
224
299
|
end
|
225
300
|
end
|
226
|
-
private :fast_write
|
227
301
|
|
228
|
-
#
|
229
|
-
#
|
302
|
+
# Used to write headers and body.
|
303
|
+
# Writes to a socket (normally `Client#io`) using `#fast_write_str`.
|
304
|
+
# Accumulates `body` items into `io_buffer`, then writes to socket.
|
305
|
+
# @param socket [#write] the response socket
|
306
|
+
# @param body [Enumerable, File] the body object
|
307
|
+
# @param io_buffer [Puma::IOBuffer] contains headers
|
308
|
+
# @param chunked [Boolean]
|
309
|
+
# @paramn content_length [Integer
|
310
|
+
# @raise [ConnectionError]
|
230
311
|
#
|
231
|
-
def
|
232
|
-
|
312
|
+
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
313
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
314
|
+
if chunked # would this ever happen?
|
315
|
+
while chunk = body.read(BODY_LEN_MAX)
|
316
|
+
io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
|
317
|
+
end
|
318
|
+
fast_write_str socket, CLOSE_CHUNKED
|
319
|
+
else
|
320
|
+
if content_length <= IO_BODY_MAX
|
321
|
+
io_buffer.write body.read(content_length)
|
322
|
+
fast_write_str socket, io_buffer.read_and_reset
|
323
|
+
else
|
324
|
+
fast_write_str socket, io_buffer.read_and_reset
|
325
|
+
IO.copy_stream body, socket
|
326
|
+
end
|
327
|
+
end
|
328
|
+
elsif body.is_a?(::Array) && body.length == 1
|
329
|
+
body_first = nil
|
330
|
+
# using body_first = body.first causes issues?
|
331
|
+
body.each { |str| body_first ||= str }
|
332
|
+
|
333
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
334
|
+
# smaller body, write to io_buffer first
|
335
|
+
io_buffer.write body_first
|
336
|
+
fast_write_str socket, io_buffer.read_and_reset
|
337
|
+
else
|
338
|
+
# large body, write both header & body to socket
|
339
|
+
fast_write_str socket, io_buffer.read_and_reset
|
340
|
+
fast_write_str socket, body_first
|
341
|
+
end
|
342
|
+
elsif body.is_a?(::Array)
|
343
|
+
# for array bodies, flush io_buffer to socket when size is greater than
|
344
|
+
# IO_BUFFER_LEN_MAX
|
345
|
+
if chunked
|
346
|
+
body.each do |part|
|
347
|
+
next if (byte_size = part.bytesize).zero?
|
348
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
349
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
350
|
+
fast_write_str socket, io_buffer.read_and_reset
|
351
|
+
end
|
352
|
+
end
|
353
|
+
io_buffer.write CLOSE_CHUNKED
|
354
|
+
else
|
355
|
+
body.each do |part|
|
356
|
+
next if part.bytesize.zero?
|
357
|
+
io_buffer.write part
|
358
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
359
|
+
fast_write_str socket, io_buffer.read_and_reset
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
# may write last body part for non-chunked, also headers if array is empty
|
364
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
365
|
+
else
|
366
|
+
# for enum bodies
|
367
|
+
if chunked
|
368
|
+
empty_body = true
|
369
|
+
body.each do |part|
|
370
|
+
next if part.nil? || (byte_size = part.bytesize).zero?
|
371
|
+
empty_body = false
|
372
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
373
|
+
fast_write_str socket, io_buffer.read_and_reset
|
374
|
+
end
|
375
|
+
if empty_body
|
376
|
+
io_buffer << CLOSE_CHUNKED
|
377
|
+
fast_write_str socket, io_buffer.read_and_reset
|
378
|
+
else
|
379
|
+
fast_write_str socket, CLOSE_CHUNKED
|
380
|
+
end
|
381
|
+
else
|
382
|
+
fast_write_str socket, io_buffer.read_and_reset
|
383
|
+
body.each do |part|
|
384
|
+
next if part.bytesize.zero?
|
385
|
+
fast_write_str socket, part
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
socket.flush
|
390
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
391
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
392
|
+
rescue Errno::EPIPE, SystemCallError, IOError
|
393
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
233
394
|
end
|
234
|
-
|
395
|
+
|
396
|
+
private :fast_write_str, :fast_write_response
|
235
397
|
|
236
398
|
# Given a Hash +env+ for the request read from +client+, add
|
237
399
|
# and fixup keys to comply with Rack's env guidelines.
|
238
400
|
# @param env [Hash] see Puma::Client#env, from request
|
239
401
|
# @param client [Puma::Client] only needed for Client#peerip
|
240
|
-
# @todo make private in 6.0.0
|
241
402
|
#
|
242
403
|
def normalize_env(env, client)
|
243
404
|
if host = env[HTTP_HOST]
|
@@ -259,17 +420,19 @@ module Puma
|
|
259
420
|
|
260
421
|
unless env[REQUEST_PATH]
|
261
422
|
# it might be a dumbass full host request header
|
262
|
-
uri =
|
423
|
+
uri = begin
|
424
|
+
URI.parse(env[REQUEST_URI])
|
425
|
+
rescue URI::InvalidURIError
|
426
|
+
raise Puma::HttpParserError
|
427
|
+
end
|
263
428
|
env[REQUEST_PATH] = uri.path
|
264
429
|
|
265
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
266
|
-
|
267
430
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
268
431
|
# so only set the env value if there actually is a value.
|
269
432
|
env[QUERY_STRING] = uri.query if uri.query
|
270
433
|
end
|
271
434
|
|
272
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
435
|
+
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
273
436
|
|
274
437
|
# From https://www.ietf.org/rfc/rfc3875 :
|
275
438
|
# "Script authors should be aware that the REMOTE_ADDR and
|
@@ -285,17 +448,31 @@ module Puma
|
|
285
448
|
addr = client.peerip
|
286
449
|
rescue Errno::ENOTCONN
|
287
450
|
# Client disconnects can result in an inability to get the
|
288
|
-
# peeraddr from the socket; default to
|
289
|
-
|
451
|
+
# peeraddr from the socket; default to unspec.
|
452
|
+
if client.peer_family == Socket::AF_INET6
|
453
|
+
addr = UNSPECIFIED_IPV6
|
454
|
+
else
|
455
|
+
addr = UNSPECIFIED_IPV4
|
456
|
+
end
|
290
457
|
end
|
291
458
|
|
292
459
|
# Set unix socket addrs to localhost
|
293
|
-
|
460
|
+
if addr.empty?
|
461
|
+
if client.peer_family == Socket::AF_INET6
|
462
|
+
addr = LOCALHOST_IPV6
|
463
|
+
else
|
464
|
+
addr = LOCALHOST_IPV4
|
465
|
+
end
|
466
|
+
end
|
294
467
|
|
295
468
|
env[REMOTE_ADDR] = addr
|
296
469
|
end
|
470
|
+
|
471
|
+
# The legacy HTTP_VERSION header can be sent as a client header.
|
472
|
+
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
473
|
+
env[HTTP_VERSION] = env[SERVER_PROTOCOL]
|
297
474
|
end
|
298
|
-
|
475
|
+
private :normalize_env
|
299
476
|
|
300
477
|
# @param header_key [#to_s]
|
301
478
|
# @return [Boolean]
|
@@ -318,6 +495,11 @@ module Puma
|
|
318
495
|
# compatibility, we'll convert them back. This code is written to
|
319
496
|
# avoid allocation in the common case (ie there are no headers
|
320
497
|
# with `,` in their names), that's why it has the extra conditionals.
|
498
|
+
#
|
499
|
+
# @note If a normalized version of a `,` header already exists, we ignore
|
500
|
+
# the `,` version. This prevents clobbering headers managed by proxies
|
501
|
+
# but not by clients (Like X-Forwarded-For).
|
502
|
+
#
|
321
503
|
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
322
504
|
# @version 5.0.3
|
323
505
|
#
|
@@ -326,23 +508,31 @@ module Puma
|
|
326
508
|
to_add = nil
|
327
509
|
|
328
510
|
env.each do |k,v|
|
329
|
-
if k.start_with?("HTTP_")
|
511
|
+
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
330
512
|
if to_delete
|
331
513
|
to_delete << k
|
332
514
|
else
|
333
515
|
to_delete = [k]
|
334
516
|
end
|
335
517
|
|
518
|
+
new_k = k.tr(",", "_")
|
519
|
+
if env.key?(new_k)
|
520
|
+
next
|
521
|
+
end
|
522
|
+
|
336
523
|
unless to_add
|
337
524
|
to_add = {}
|
338
525
|
end
|
339
526
|
|
340
|
-
to_add[
|
527
|
+
to_add[new_k] = v
|
341
528
|
end
|
342
529
|
end
|
343
530
|
|
344
|
-
if to_delete
|
531
|
+
if to_delete # rubocop:disable Style/SafeNavigation
|
345
532
|
to_delete.each { |k| env.delete(k) }
|
533
|
+
end
|
534
|
+
|
535
|
+
if to_add
|
346
536
|
env.merge! to_add
|
347
537
|
end
|
348
538
|
end
|
@@ -354,7 +544,7 @@ module Puma
|
|
354
544
|
# @version 5.0.3
|
355
545
|
#
|
356
546
|
def str_early_hints(headers)
|
357
|
-
eh_str = "
|
547
|
+
eh_str = +""
|
358
548
|
headers.each_pair do |k, vs|
|
359
549
|
next if illegal_header_key?(k)
|
360
550
|
|
@@ -363,74 +553,82 @@ module Puma
|
|
363
553
|
next if illegal_header_value?(v)
|
364
554
|
eh_str << "#{k}: #{v}\r\n"
|
365
555
|
end
|
366
|
-
|
556
|
+
elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
|
367
557
|
eh_str << "#{k}: #{vs}\r\n"
|
368
558
|
end
|
369
559
|
end
|
370
|
-
|
560
|
+
eh_str.freeze
|
371
561
|
end
|
372
562
|
private :str_early_hints
|
373
563
|
|
564
|
+
# @param status [Integer] status from the app
|
565
|
+
# @return [String] the text description from Puma::HTTP_STATUS_CODES
|
566
|
+
#
|
567
|
+
def fetch_status_code(status)
|
568
|
+
HTTP_STATUS_CODES.fetch(status) { CUSTOM_STAT }
|
569
|
+
end
|
570
|
+
private :fetch_status_code
|
571
|
+
|
374
572
|
# Processes and write headers to the IOBuffer.
|
375
573
|
# @param env [Hash] see Puma::Client#env, from request
|
376
574
|
# @param status [Integer] the status returned by the Rack application
|
377
575
|
# @param headers [Hash] the headers returned by the Rack application
|
378
|
-
# @param
|
379
|
-
#
|
380
|
-
# @param
|
381
|
-
# @param
|
576
|
+
# @param content_length [Integer,nil] content length if it can be determined from the
|
577
|
+
# response body
|
578
|
+
# @param io_buffer [Puma::IOBuffer] modified inn place
|
579
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
580
|
+
# status and `@max_fast_inline`
|
581
|
+
# @return [Hash] resp_info
|
382
582
|
# @version 5.0.3
|
383
583
|
#
|
384
|
-
def str_headers(env, status, headers,
|
584
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
585
|
+
|
385
586
|
line_ending = LINE_END
|
386
587
|
colon = COLON
|
387
588
|
|
388
|
-
|
589
|
+
resp_info = {}
|
590
|
+
resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
|
591
|
+
|
592
|
+
http_11 = env[SERVER_PROTOCOL] == HTTP_11
|
389
593
|
if http_11
|
390
|
-
|
391
|
-
|
594
|
+
resp_info[:allow_chunked] = true
|
595
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
392
596
|
|
393
597
|
# An optimization. The most common response is 200, so we can
|
394
598
|
# reply with the proper 200 status without having to compute
|
395
599
|
# the response header.
|
396
600
|
#
|
397
601
|
if status == 200
|
398
|
-
|
602
|
+
io_buffer << HTTP_11_200
|
399
603
|
else
|
400
|
-
|
401
|
-
fetch_status_code(status), line_ending
|
604
|
+
io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
|
402
605
|
|
403
|
-
|
606
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
404
607
|
end
|
405
608
|
else
|
406
|
-
|
407
|
-
|
609
|
+
resp_info[:allow_chunked] = false
|
610
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
408
611
|
|
409
612
|
# Same optimization as above for HTTP/1.1
|
410
613
|
#
|
411
614
|
if status == 200
|
412
|
-
|
615
|
+
io_buffer << HTTP_10_200
|
413
616
|
else
|
414
|
-
|
617
|
+
io_buffer.append "HTTP/1.0 #{status} ",
|
415
618
|
fetch_status_code(status), line_ending
|
416
619
|
|
417
|
-
|
620
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
418
621
|
end
|
419
622
|
end
|
420
623
|
|
421
624
|
# regardless of what the client wants, we always close the connection
|
422
625
|
# if running without request queueing
|
423
|
-
|
626
|
+
resp_info[:keep_alive] &&= @queue_requests
|
424
627
|
|
425
|
-
#
|
426
|
-
|
427
|
-
# This allows Puma to service connections fairly when the number
|
428
|
-
# of concurrent connections exceeds the size of the threadpool.
|
429
|
-
res_info[:keep_alive] &&= requests < @max_fast_inline ||
|
430
|
-
@thread_pool.busy_threads < @max_threads ||
|
431
|
-
!client.listener.to_io.wait_readable(0)
|
628
|
+
# see prepare_response
|
629
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
432
630
|
|
433
|
-
|
631
|
+
resp_info[:response_hijack] = nil
|
434
632
|
|
435
633
|
headers.each do |k, vs|
|
436
634
|
next if illegal_header_key?(k)
|
@@ -438,25 +636,34 @@ module Puma
|
|
438
636
|
case k.downcase
|
439
637
|
when CONTENT_LENGTH2
|
440
638
|
next if illegal_header_value?(vs)
|
441
|
-
|
639
|
+
# nil.to_i is 0, nil&.to_i is nil
|
640
|
+
resp_info[:content_length] = vs&.to_i
|
442
641
|
next
|
443
642
|
when TRANSFER_ENCODING
|
444
|
-
|
445
|
-
|
643
|
+
resp_info[:allow_chunked] = false
|
644
|
+
resp_info[:content_length] = nil
|
645
|
+
resp_info[:transfer_encoding] = vs
|
446
646
|
when HIJACK
|
447
|
-
|
647
|
+
resp_info[:response_hijack] = vs
|
448
648
|
next
|
449
649
|
when BANNED_HEADER_KEY
|
450
650
|
next
|
451
651
|
end
|
452
652
|
|
453
|
-
if vs.
|
454
|
-
vs
|
653
|
+
ary = if vs.is_a?(::Array) && !vs.empty?
|
654
|
+
vs
|
655
|
+
elsif vs.respond_to?(:to_s) && !vs.to_s.empty?
|
656
|
+
vs.to_s.split NEWLINE
|
657
|
+
else
|
658
|
+
nil
|
659
|
+
end
|
660
|
+
if ary
|
661
|
+
ary.each do |v|
|
455
662
|
next if illegal_header_value?(v)
|
456
|
-
|
663
|
+
io_buffer.append k, colon, v, line_ending
|
457
664
|
end
|
458
665
|
else
|
459
|
-
|
666
|
+
io_buffer.append k, colon, line_ending
|
460
667
|
end
|
461
668
|
end
|
462
669
|
|
@@ -466,10 +673,11 @@ module Puma
|
|
466
673
|
# Only set the header if we're doing something which is not the default
|
467
674
|
# for this protocol version
|
468
675
|
if http_11
|
469
|
-
|
676
|
+
io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
|
470
677
|
else
|
471
|
-
|
678
|
+
io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
|
472
679
|
end
|
680
|
+
resp_info
|
473
681
|
end
|
474
682
|
private :str_headers
|
475
683
|
end
|