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