puma 5.6.5 → 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 +165 -11
- 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 +11 -8
- 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 +36 -15
- 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 +156 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +3 -3
- data/lib/puma/binder.rb +40 -45
- 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 +3 -6
- data/lib/puma/detect.rb +4 -0
- data/lib/puma/dsl.rb +110 -52
- 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 +100 -175
- data/lib/puma/log_writer.rb +141 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +82 -11
- 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 -165
- data/lib/puma/runner.rb +52 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +57 -71
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +1 -4
- data/lib/puma/thread_pool.rb +16 -16
- data/lib/puma/util.rb +0 -11
- 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,123 +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
|
110
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
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
186
252
|
end
|
187
253
|
|
188
|
-
|
254
|
+
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
255
|
+
body.close if close_body
|
256
|
+
keep_alive
|
189
257
|
end
|
190
258
|
|
191
259
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -199,45 +267,126 @@ module Puma
|
|
199
267
|
end
|
200
268
|
end
|
201
269
|
|
202
|
-
#
|
203
|
-
#
|
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
|
204
276
|
# @param str [String] the string written to the io
|
205
277
|
# @raise [ConnectionError]
|
206
278
|
#
|
207
|
-
def
|
279
|
+
def fast_write_str(socket, str)
|
208
280
|
n = 0
|
209
|
-
|
281
|
+
byte_size = str.bytesize
|
282
|
+
while n < byte_size
|
210
283
|
begin
|
211
|
-
n
|
284
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
212
285
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
213
|
-
unless
|
214
|
-
raise ConnectionError,
|
286
|
+
unless socket.wait_writable WRITE_TIMEOUT
|
287
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
215
288
|
end
|
216
|
-
|
217
289
|
retry
|
218
290
|
rescue Errno::EPIPE, SystemCallError, IOError
|
219
|
-
raise ConnectionError,
|
291
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
220
292
|
end
|
221
|
-
|
222
|
-
return if n == str.bytesize
|
223
|
-
str = str.byteslice(n..-1)
|
224
293
|
end
|
225
294
|
end
|
226
|
-
private :fast_write
|
227
295
|
|
228
|
-
#
|
229
|
-
#
|
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]
|
230
305
|
#
|
231
|
-
def
|
232
|
-
|
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
|
233
382
|
end
|
234
|
-
|
383
|
+
|
384
|
+
private :fast_write_str, :fast_write_response
|
235
385
|
|
236
386
|
# Given a Hash +env+ for the request read from +client+, add
|
237
387
|
# and fixup keys to comply with Rack's env guidelines.
|
238
388
|
# @param env [Hash] see Puma::Client#env, from request
|
239
389
|
# @param client [Puma::Client] only needed for Client#peerip
|
240
|
-
# @todo make private in 6.0.0
|
241
390
|
#
|
242
391
|
def normalize_env(env, client)
|
243
392
|
if host = env[HTTP_HOST]
|
@@ -262,14 +411,12 @@ module Puma
|
|
262
411
|
uri = URI.parse(env[REQUEST_URI])
|
263
412
|
env[REQUEST_PATH] = uri.path
|
264
413
|
|
265
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
266
|
-
|
267
414
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
268
415
|
# so only set the env value if there actually is a value.
|
269
416
|
env[QUERY_STRING] = uri.query if uri.query
|
270
417
|
end
|
271
418
|
|
272
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
419
|
+
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
273
420
|
|
274
421
|
# From https://www.ietf.org/rfc/rfc3875 :
|
275
422
|
# "Script authors should be aware that the REMOTE_ADDR and
|
@@ -285,17 +432,31 @@ module Puma
|
|
285
432
|
addr = client.peerip
|
286
433
|
rescue Errno::ENOTCONN
|
287
434
|
# Client disconnects can result in an inability to get the
|
288
|
-
# peeraddr from the socket; default to
|
289
|
-
|
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
|
290
441
|
end
|
291
442
|
|
292
443
|
# Set unix socket addrs to localhost
|
293
|
-
|
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
|
294
451
|
|
295
452
|
env[REMOTE_ADDR] = addr
|
296
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]
|
297
458
|
end
|
298
|
-
|
459
|
+
private :normalize_env
|
299
460
|
|
300
461
|
# @param header_key [#to_s]
|
301
462
|
# @return [Boolean]
|
@@ -326,7 +487,7 @@ module Puma
|
|
326
487
|
to_add = nil
|
327
488
|
|
328
489
|
env.each do |k,v|
|
329
|
-
if k.start_with?("HTTP_")
|
490
|
+
if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
|
330
491
|
if to_delete
|
331
492
|
to_delete << k
|
332
493
|
else
|
@@ -354,7 +515,7 @@ module Puma
|
|
354
515
|
# @version 5.0.3
|
355
516
|
#
|
356
517
|
def str_early_hints(headers)
|
357
|
-
eh_str = "HTTP/1.1 103 Early Hints\r\n"
|
518
|
+
eh_str = +"HTTP/1.1 103 Early Hints\r\n"
|
358
519
|
headers.each_pair do |k, vs|
|
359
520
|
next if illegal_header_key?(k)
|
360
521
|
|
@@ -371,66 +532,74 @@ module Puma
|
|
371
532
|
end
|
372
533
|
private :str_early_hints
|
373
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
|
+
|
374
543
|
# Processes and write headers to the IOBuffer.
|
375
544
|
# @param env [Hash] see Puma::Client#env, from request
|
376
545
|
# @param status [Integer] the status returned by the Rack application
|
377
546
|
# @param headers [Hash] the headers returned by the Rack application
|
378
|
-
# @param
|
379
|
-
#
|
380
|
-
# @param
|
381
|
-
# @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
|
382
553
|
# @version 5.0.3
|
383
554
|
#
|
384
|
-
def str_headers(env, status, headers,
|
555
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
556
|
+
|
385
557
|
line_ending = LINE_END
|
386
558
|
colon = COLON
|
387
559
|
|
388
|
-
|
560
|
+
resp_info = {}
|
561
|
+
resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
|
562
|
+
|
563
|
+
http_11 = env[SERVER_PROTOCOL] == HTTP_11
|
389
564
|
if http_11
|
390
|
-
|
391
|
-
|
565
|
+
resp_info[:allow_chunked] = true
|
566
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
392
567
|
|
393
568
|
# An optimization. The most common response is 200, so we can
|
394
569
|
# reply with the proper 200 status without having to compute
|
395
570
|
# the response header.
|
396
571
|
#
|
397
572
|
if status == 200
|
398
|
-
|
573
|
+
io_buffer << HTTP_11_200
|
399
574
|
else
|
400
|
-
|
401
|
-
fetch_status_code(status), line_ending
|
575
|
+
io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
|
402
576
|
|
403
|
-
|
577
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
404
578
|
end
|
405
579
|
else
|
406
|
-
|
407
|
-
|
580
|
+
resp_info[:allow_chunked] = false
|
581
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
408
582
|
|
409
583
|
# Same optimization as above for HTTP/1.1
|
410
584
|
#
|
411
585
|
if status == 200
|
412
|
-
|
586
|
+
io_buffer << HTTP_10_200
|
413
587
|
else
|
414
|
-
|
588
|
+
io_buffer.append "HTTP/1.0 #{status} ",
|
415
589
|
fetch_status_code(status), line_ending
|
416
590
|
|
417
|
-
|
591
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
418
592
|
end
|
419
593
|
end
|
420
594
|
|
421
595
|
# regardless of what the client wants, we always close the connection
|
422
596
|
# if running without request queueing
|
423
|
-
|
597
|
+
resp_info[:keep_alive] &&= @queue_requests
|
424
598
|
|
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)
|
599
|
+
# see prepare_response
|
600
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
432
601
|
|
433
|
-
|
602
|
+
resp_info[:response_hijack] = nil
|
434
603
|
|
435
604
|
headers.each do |k, vs|
|
436
605
|
next if illegal_header_key?(k)
|
@@ -438,25 +607,34 @@ module Puma
|
|
438
607
|
case k.downcase
|
439
608
|
when CONTENT_LENGTH2
|
440
609
|
next if illegal_header_value?(vs)
|
441
|
-
|
610
|
+
# nil.to_i is 0, nil&.to_i is nil
|
611
|
+
resp_info[:content_length] = vs&.to_i
|
442
612
|
next
|
443
613
|
when TRANSFER_ENCODING
|
444
|
-
|
445
|
-
|
614
|
+
resp_info[:allow_chunked] = false
|
615
|
+
resp_info[:content_length] = nil
|
616
|
+
resp_info[:transfer_encoding] = vs
|
446
617
|
when HIJACK
|
447
|
-
|
618
|
+
resp_info[:response_hijack] = vs
|
448
619
|
next
|
449
620
|
when BANNED_HEADER_KEY
|
450
621
|
next
|
451
622
|
end
|
452
623
|
|
453
|
-
if vs.
|
454
|
-
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|
|
455
633
|
next if illegal_header_value?(v)
|
456
|
-
|
634
|
+
io_buffer.append k, colon, v, line_ending
|
457
635
|
end
|
458
636
|
else
|
459
|
-
|
637
|
+
io_buffer.append k, colon, line_ending
|
460
638
|
end
|
461
639
|
end
|
462
640
|
|
@@ -466,10 +644,11 @@ module Puma
|
|
466
644
|
# Only set the header if we're doing something which is not the default
|
467
645
|
# for this protocol version
|
468
646
|
if http_11
|
469
|
-
|
647
|
+
io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
|
470
648
|
else
|
471
|
-
|
649
|
+
io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
|
472
650
|
end
|
651
|
+
resp_info
|
473
652
|
end
|
474
653
|
private :str_headers
|
475
654
|
end
|