puma 3.11.2 → 6.0.0
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 +5 -5
- data/History.md +1708 -422
- data/LICENSE +23 -20
- data/README.md +190 -64
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +69 -58
- 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/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +100 -115
- 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 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +61 -3
- data/ext/puma_http11/http11_parser.c +106 -118
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +6 -4
- data/ext/puma_http11/http11_parser_common.rl +6 -6
- data/ext/puma_http11/mini_ssl.c +376 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
- data/ext/puma_http11/puma_http11.c +49 -57
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +243 -148
- data/lib/puma/cli.rb +50 -35
- data/lib/puma/client.rb +369 -232
- data/lib/puma/cluster/worker.rb +175 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +268 -235
- data/lib/puma/commonlogger.rb +4 -2
- data/lib/puma/configuration.rb +116 -88
- data/lib/puma/const.rb +49 -30
- data/lib/puma/control_cli.rb +124 -77
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +685 -138
- data/lib/puma/error_logger.rb +112 -0
- data/lib/puma/events.rb +17 -111
- data/lib/puma/io_buffer.rb +34 -5
- data/lib/puma/jruby_restart.rb +4 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +197 -130
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +92 -0
- data/lib/puma/minissl.rb +256 -70
- data/lib/puma/null_io.rb +20 -1
- data/lib/puma/plugin/tmp_restart.rb +3 -1
- data/lib/puma/plugin.rb +9 -13
- data/lib/puma/rack/builder.rb +8 -9
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +3 -1
- data/lib/puma/reactor.rb +90 -187
- data/lib/puma/request.rb +607 -0
- data/lib/puma/runner.rb +94 -71
- data/lib/puma/server.rb +336 -703
- data/lib/puma/single.rb +27 -72
- data/lib/puma/state_file.rb +46 -7
- data/lib/puma/systemd.rb +47 -0
- data/lib/puma/thread_pool.rb +185 -91
- data/lib/puma/util.rb +23 -10
- data/lib/puma.rb +68 -3
- data/lib/rack/handler/puma.rb +17 -14
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +53 -30
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -39
- data/tools/jungle/README.md +0 -13
- data/tools/jungle/init.d/README.md +0 -59
- 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/request.rb
ADDED
@@ -0,0 +1,607 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
5
|
+
|
6
|
+
|
7
|
+
# The methods here are included in Server, but are separated into this file.
|
8
|
+
# All the methods here pertain to passing the request to the app, then
|
9
|
+
# writing the response back to the client.
|
10
|
+
#
|
11
|
+
# None of the methods here are called externally, with the exception of
|
12
|
+
# #handle_request, which is called in Server#process_client.
|
13
|
+
# @version 5.0.3
|
14
|
+
#
|
15
|
+
module Request # :nodoc:
|
16
|
+
|
17
|
+
# determines whether to write body to io_buffer first, or straight to socket
|
18
|
+
# also fixes max size of chunked body read when bosy is an IO.
|
19
|
+
BODY_LEN_MAX = 1_024 * 256
|
20
|
+
|
21
|
+
# size divide for using copy_stream on body
|
22
|
+
IO_BODY_MAX = 1_024 * 64
|
23
|
+
|
24
|
+
# max size for io_buffer, force write when exceeded
|
25
|
+
IO_BUFFER_LEN_MAX = 1_024 * 1_024 * 4
|
26
|
+
|
27
|
+
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
28
|
+
|
29
|
+
CUSTOM_STAT = 'CUSTOM'
|
30
|
+
|
31
|
+
include Puma::Const
|
32
|
+
|
33
|
+
# Takes the request contained in +client+, invokes the Rack application to construct
|
34
|
+
# the response and writes it back to +client.io+.
|
35
|
+
#
|
36
|
+
# It'll return +false+ when the connection is closed, this doesn't mean
|
37
|
+
# that the response wasn't successful.
|
38
|
+
#
|
39
|
+
# It'll return +:async+ if the connection remains open but will be handled
|
40
|
+
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
41
|
+
#
|
42
|
+
# Finally, it'll return +true+ on keep-alive connections.
|
43
|
+
# @param client [Puma::Client]
|
44
|
+
# @param io_buffer [Puma::IOBuffer]
|
45
|
+
# @param requests [Integer]
|
46
|
+
# @return [Boolean,:async]
|
47
|
+
#
|
48
|
+
def handle_request(client, io_buffer, requests)
|
49
|
+
env = client.env
|
50
|
+
socket = client.io # io may be a MiniSSL::Socket
|
51
|
+
|
52
|
+
return false if closed_socket?(socket)
|
53
|
+
|
54
|
+
normalize_env env, client
|
55
|
+
|
56
|
+
env[PUMA_SOCKET] = socket
|
57
|
+
|
58
|
+
if env[HTTPS_KEY] && socket.peercert
|
59
|
+
env[PUMA_PEERCERT] = socket.peercert
|
60
|
+
end
|
61
|
+
|
62
|
+
env[HIJACK_P] = true
|
63
|
+
env[HIJACK] = client
|
64
|
+
|
65
|
+
env[RACK_INPUT] = client.body
|
66
|
+
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
67
|
+
|
68
|
+
if @early_hints
|
69
|
+
env[EARLY_HINTS] = lambda { |headers|
|
70
|
+
begin
|
71
|
+
fast_write_str socket, str_early_hints(headers)
|
72
|
+
rescue ConnectionError => e
|
73
|
+
@log_writer.debug_error e
|
74
|
+
# noop, if we lost the socket we just won't send the early hints
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
req_env_post_parse env
|
80
|
+
|
81
|
+
# A rack extension. If the app writes #call'ables to this
|
82
|
+
# array, we will invoke them when the request is done.
|
83
|
+
#
|
84
|
+
env[RACK_AFTER_REPLY] ||= []
|
85
|
+
|
86
|
+
begin
|
87
|
+
if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
|
88
|
+
status, headers, res_body = @thread_pool.with_force_shutdown do
|
89
|
+
@app.call(env)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
93
|
+
status, headers, res_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
94
|
+
end
|
95
|
+
|
96
|
+
return :async if client.hijacked
|
97
|
+
|
98
|
+
status = status.to_i
|
99
|
+
|
100
|
+
if status == -1
|
101
|
+
unless headers.empty? and res_body == []
|
102
|
+
raise "async response must have empty headers and body"
|
103
|
+
end
|
104
|
+
|
105
|
+
return :async
|
106
|
+
end
|
107
|
+
rescue ThreadPool::ForceShutdown => e
|
108
|
+
@log_writer.unknown_error e, client, "Rack app"
|
109
|
+
@log_writer.log "Detected force shutdown of a thread"
|
110
|
+
|
111
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
112
|
+
rescue Exception => e
|
113
|
+
@log_writer.unknown_error e, client, "Rack app"
|
114
|
+
|
115
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
116
|
+
end
|
117
|
+
prepare_response(status, headers, res_body, io_buffer, requests, client)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Assembles the headers and prepares the body for actually sending the
|
121
|
+
# response via #fast_write_response.
|
122
|
+
#
|
123
|
+
# @param status [Integer] the status returned by the Rack application
|
124
|
+
# @param headers [Hash] the headers returned by the Rack application
|
125
|
+
# @param app_body [Array] the body returned by the Rack application or
|
126
|
+
# a call to `lowlevel_error`
|
127
|
+
# @param io_buffer [Puma::IOBuffer] modified in place
|
128
|
+
# @param requests [Integer] number of inline requests handled
|
129
|
+
# @param client [Puma::Client]
|
130
|
+
# @return [Boolean,:async]
|
131
|
+
def prepare_response(status, headers, app_body, io_buffer, requests, client)
|
132
|
+
env = client.env
|
133
|
+
socket = client.io
|
134
|
+
|
135
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
136
|
+
|
137
|
+
return false if closed_socket?(socket)
|
138
|
+
|
139
|
+
resp_info = str_headers(env, status, headers, app_body, io_buffer, requests, client)
|
140
|
+
|
141
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
142
|
+
# resp_info[:content_length] will be set if it can be determined
|
143
|
+
if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
|
144
|
+
if app_body.respond_to?(:to_ary)
|
145
|
+
length = 0
|
146
|
+
if array_body = app_body.to_ary
|
147
|
+
body = array_body.map { |part| length += part.bytesize; part }
|
148
|
+
elsif app_body.is_a?(::File) && app_body.respond_to?(:size)
|
149
|
+
length = app_body.size
|
150
|
+
elsif app_body.respond_to?(:each)
|
151
|
+
body = []
|
152
|
+
app_body.each { |part| length += part.bytesize; body << part }
|
153
|
+
end
|
154
|
+
resp_info[:content_length] = length
|
155
|
+
elsif app_body.is_a?(File) && app_body.respond_to?(:size)
|
156
|
+
resp_info[:content_length] = app_body.size
|
157
|
+
body = app_body
|
158
|
+
elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
159
|
+
File.readable?(fn = app_body.to_path)
|
160
|
+
body = File.open fn, 'rb'
|
161
|
+
resp_info[:content_length] = body.size
|
162
|
+
else
|
163
|
+
body = app_body
|
164
|
+
end
|
165
|
+
elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
166
|
+
File.readable?(fn = app_body.to_path)
|
167
|
+
body = File.open fn, 'rb'
|
168
|
+
resp_info[:content_length] = body.size
|
169
|
+
else
|
170
|
+
body = app_body
|
171
|
+
end
|
172
|
+
|
173
|
+
line_ending = LINE_END
|
174
|
+
|
175
|
+
content_length = resp_info[:content_length]
|
176
|
+
keep_alive = resp_info[:keep_alive]
|
177
|
+
|
178
|
+
if app_body && !app_body.respond_to?(:each)
|
179
|
+
response_hijack = app_body
|
180
|
+
else
|
181
|
+
response_hijack = resp_info[:response_hijack]
|
182
|
+
end
|
183
|
+
|
184
|
+
cork_socket socket
|
185
|
+
|
186
|
+
if resp_info[:no_body]
|
187
|
+
if content_length and status != 204
|
188
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
189
|
+
end
|
190
|
+
|
191
|
+
io_buffer << LINE_END
|
192
|
+
fast_write_str socket, io_buffer.to_s
|
193
|
+
return keep_alive
|
194
|
+
end
|
195
|
+
if content_length
|
196
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
197
|
+
chunked = false
|
198
|
+
elsif !response_hijack and resp_info[:allow_chunked]
|
199
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
200
|
+
chunked = true
|
201
|
+
end
|
202
|
+
|
203
|
+
io_buffer << line_ending
|
204
|
+
|
205
|
+
if response_hijack
|
206
|
+
fast_write_str socket, io_buffer.to_s
|
207
|
+
response_hijack.call socket
|
208
|
+
return :async
|
209
|
+
end
|
210
|
+
|
211
|
+
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
212
|
+
keep_alive
|
213
|
+
ensure
|
214
|
+
io_buffer.reset
|
215
|
+
resp_info = nil
|
216
|
+
uncork_socket socket
|
217
|
+
app_body.close if app_body.respond_to? :close
|
218
|
+
client.tempfile&.unlink
|
219
|
+
|
220
|
+
begin
|
221
|
+
after_reply.each { |o| o.call }
|
222
|
+
rescue StandardError => e
|
223
|
+
@log_writer.debug_error e
|
224
|
+
end unless after_reply.empty?
|
225
|
+
end
|
226
|
+
|
227
|
+
# @param env [Hash] see Puma::Client#env, from request
|
228
|
+
# @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
|
229
|
+
#
|
230
|
+
def default_server_port(env)
|
231
|
+
if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
|
232
|
+
PORT_443
|
233
|
+
else
|
234
|
+
PORT_80
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
239
|
+
# and body segments (called by `fast_write_response`).
|
240
|
+
# Writes a string to an io (normally `Client#io`) using `write_nonblock`.
|
241
|
+
# Large strings may not be written in one pass, especially if `io` is a
|
242
|
+
# `MiniSSL::Socket`.
|
243
|
+
# @param io [#write_nonblock] the io to write to
|
244
|
+
# @param str [String] the string written to the io
|
245
|
+
# @raise [ConnectionError]
|
246
|
+
#
|
247
|
+
def fast_write_str(socket, str)
|
248
|
+
n = 0
|
249
|
+
byte_size = str.bytesize
|
250
|
+
while n < byte_size
|
251
|
+
begin
|
252
|
+
n += socket.syswrite(n.zero? ? str : str.byteslice(n..-1))
|
253
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
254
|
+
unless socket.wait_writable WRITE_TIMEOUT
|
255
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
256
|
+
end
|
257
|
+
retry
|
258
|
+
rescue Errno::EPIPE, SystemCallError, IOError
|
259
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Used to write headers and body.
|
265
|
+
# Writes to a socket (normally `Client#io`) using `#fast_write_str`.
|
266
|
+
# Accumulates `body` items into `io_buffer`, then writes to socket.
|
267
|
+
# @param socket [#write] the response socket
|
268
|
+
# @param body [Enumerable, File] the body object
|
269
|
+
# @param io_buffer [Puma::IOBuffer] contains headers
|
270
|
+
# @param chunk [Boolean]
|
271
|
+
# @raise [ConnectionError]
|
272
|
+
#
|
273
|
+
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
274
|
+
if body.is_a?(::File) || body.respond_to?(:read) || body.respond_to?(:readpartial)
|
275
|
+
if chunked # would this ever happen?
|
276
|
+
while part = body.read(BODY_LEN_MAX)
|
277
|
+
io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
|
278
|
+
end
|
279
|
+
io_buffer << CLOSE_CHUNKED
|
280
|
+
fast_write_str socket, io_buffer.to_s
|
281
|
+
else
|
282
|
+
if content_length <= IO_BODY_MAX
|
283
|
+
io_buffer.write body.sysread(content_length)
|
284
|
+
fast_write_str socket, io_buffer.to_s
|
285
|
+
else
|
286
|
+
fast_write_str socket, io_buffer.to_s
|
287
|
+
IO.copy_stream body, socket
|
288
|
+
end
|
289
|
+
end
|
290
|
+
body.close
|
291
|
+
elsif body.is_a?(::Array) && body.length == 1
|
292
|
+
body_first = body.first
|
293
|
+
if body_first.is_a?(::String) && body_first.bytesize >= BODY_LEN_MAX
|
294
|
+
# large body, write both header & body to socket
|
295
|
+
fast_write_str socket, io_buffer.to_s
|
296
|
+
fast_write_str socket, body_first
|
297
|
+
else
|
298
|
+
# smaller body, write to stream first
|
299
|
+
io_buffer.write body_first
|
300
|
+
fast_write_str socket, io_buffer.to_s
|
301
|
+
end
|
302
|
+
else
|
303
|
+
# for array bodies, flush io_buffer to socket when size is greater than
|
304
|
+
# IO_BUFFER_LEN_MAX
|
305
|
+
if chunked
|
306
|
+
body.each do |part|
|
307
|
+
next if (byte_size = part.bytesize).zero?
|
308
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
309
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
310
|
+
fast_write_str socket, io_buffer.to_s
|
311
|
+
io_buffer.reset
|
312
|
+
end
|
313
|
+
end
|
314
|
+
io_buffer.write CLOSE_CHUNKED
|
315
|
+
else
|
316
|
+
body.each do |part|
|
317
|
+
next if part.bytesize.zero?
|
318
|
+
io_buffer.write part
|
319
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
320
|
+
fast_write_str socket, io_buffer.to_s
|
321
|
+
io_buffer.reset
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
fast_write_str(socket, io_buffer.to_s) unless io_buffer.length.zero?
|
326
|
+
end
|
327
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
328
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
329
|
+
rescue Errno::EPIPE, SystemCallError, IOError
|
330
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
331
|
+
end
|
332
|
+
|
333
|
+
private :fast_write_str, :fast_write_response
|
334
|
+
|
335
|
+
# Given a Hash +env+ for the request read from +client+, add
|
336
|
+
# and fixup keys to comply with Rack's env guidelines.
|
337
|
+
# @param env [Hash] see Puma::Client#env, from request
|
338
|
+
# @param client [Puma::Client] only needed for Client#peerip
|
339
|
+
#
|
340
|
+
def normalize_env(env, client)
|
341
|
+
if host = env[HTTP_HOST]
|
342
|
+
# host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
|
343
|
+
if colon = host.rindex("]:") # IPV6 with port
|
344
|
+
env[SERVER_NAME] = host[0, colon+1]
|
345
|
+
env[SERVER_PORT] = host[colon+2, host.bytesize]
|
346
|
+
elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
|
347
|
+
env[SERVER_NAME] = host[0, colon]
|
348
|
+
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
349
|
+
else
|
350
|
+
env[SERVER_NAME] = host
|
351
|
+
env[SERVER_PORT] = default_server_port(env)
|
352
|
+
end
|
353
|
+
else
|
354
|
+
env[SERVER_NAME] = LOCALHOST
|
355
|
+
env[SERVER_PORT] = default_server_port(env)
|
356
|
+
end
|
357
|
+
|
358
|
+
unless env[REQUEST_PATH]
|
359
|
+
# it might be a dumbass full host request header
|
360
|
+
uri = URI.parse(env[REQUEST_URI])
|
361
|
+
env[REQUEST_PATH] = uri.path
|
362
|
+
|
363
|
+
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
364
|
+
# so only set the env value if there actually is a value.
|
365
|
+
env[QUERY_STRING] = uri.query if uri.query
|
366
|
+
end
|
367
|
+
|
368
|
+
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
369
|
+
|
370
|
+
# From https://www.ietf.org/rfc/rfc3875 :
|
371
|
+
# "Script authors should be aware that the REMOTE_ADDR and
|
372
|
+
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
373
|
+
# may not identify the ultimate source of the request.
|
374
|
+
# They identify the client for the immediate request to the
|
375
|
+
# server; that client may be a proxy, gateway, or other
|
376
|
+
# intermediary acting on behalf of the actual source client."
|
377
|
+
#
|
378
|
+
|
379
|
+
unless env.key?(REMOTE_ADDR)
|
380
|
+
begin
|
381
|
+
addr = client.peerip
|
382
|
+
rescue Errno::ENOTCONN
|
383
|
+
# Client disconnects can result in an inability to get the
|
384
|
+
# peeraddr from the socket; default to unspec.
|
385
|
+
if client.peer_family == Socket::AF_INET6
|
386
|
+
addr = UNSPECIFIED_IPV6
|
387
|
+
else
|
388
|
+
addr = UNSPECIFIED_IPV4
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# Set unix socket addrs to localhost
|
393
|
+
if addr.empty?
|
394
|
+
if client.peer_family == Socket::AF_INET6
|
395
|
+
addr = LOCALHOST_IPV6
|
396
|
+
else
|
397
|
+
addr = LOCALHOST_IPV4
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
env[REMOTE_ADDR] = addr
|
402
|
+
end
|
403
|
+
|
404
|
+
# The legacy HTTP_VERSION header can be sent as a client header.
|
405
|
+
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
406
|
+
env[HTTP_VERSION] = env[SERVER_PROTOCOL]
|
407
|
+
end
|
408
|
+
private :normalize_env
|
409
|
+
|
410
|
+
# @param header_key [#to_s]
|
411
|
+
# @return [Boolean]
|
412
|
+
#
|
413
|
+
def illegal_header_key?(header_key)
|
414
|
+
!!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
|
415
|
+
end
|
416
|
+
|
417
|
+
# @param header_value [#to_s]
|
418
|
+
# @return [Boolean]
|
419
|
+
#
|
420
|
+
def illegal_header_value?(header_value)
|
421
|
+
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
422
|
+
end
|
423
|
+
private :illegal_header_key?, :illegal_header_value?
|
424
|
+
|
425
|
+
# Fixup any headers with `,` in the name to have `_` now. We emit
|
426
|
+
# headers with `,` in them during the parse phase to avoid ambiguity
|
427
|
+
# with the `-` to `_` conversion for critical headers. But here for
|
428
|
+
# compatibility, we'll convert them back. This code is written to
|
429
|
+
# avoid allocation in the common case (ie there are no headers
|
430
|
+
# with `,` in their names), that's why it has the extra conditionals.
|
431
|
+
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
432
|
+
# @version 5.0.3
|
433
|
+
#
|
434
|
+
def req_env_post_parse(env)
|
435
|
+
to_delete = nil
|
436
|
+
to_add = nil
|
437
|
+
|
438
|
+
env.each do |k,v|
|
439
|
+
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
440
|
+
if to_delete
|
441
|
+
to_delete << k
|
442
|
+
else
|
443
|
+
to_delete = [k]
|
444
|
+
end
|
445
|
+
|
446
|
+
unless to_add
|
447
|
+
to_add = {}
|
448
|
+
end
|
449
|
+
|
450
|
+
to_add[k.tr(",", "_")] = v
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
if to_delete
|
455
|
+
to_delete.each { |k| env.delete(k) }
|
456
|
+
env.merge! to_add
|
457
|
+
end
|
458
|
+
end
|
459
|
+
private :req_env_post_parse
|
460
|
+
|
461
|
+
# Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
|
462
|
+
# @param headers [Hash] the headers returned by the Rack application
|
463
|
+
# @return [String]
|
464
|
+
# @version 5.0.3
|
465
|
+
#
|
466
|
+
def str_early_hints(headers)
|
467
|
+
eh_str = +"HTTP/1.1 103 Early Hints\r\n"
|
468
|
+
headers.each_pair do |k, vs|
|
469
|
+
next if illegal_header_key?(k)
|
470
|
+
|
471
|
+
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
472
|
+
vs.to_s.split(NEWLINE).each do |v|
|
473
|
+
next if illegal_header_value?(v)
|
474
|
+
eh_str << "#{k}: #{v}\r\n"
|
475
|
+
end
|
476
|
+
else
|
477
|
+
eh_str << "#{k}: #{vs}\r\n"
|
478
|
+
end
|
479
|
+
end
|
480
|
+
"#{eh_str}\r\n".freeze
|
481
|
+
end
|
482
|
+
private :str_early_hints
|
483
|
+
|
484
|
+
# @param status [Integer] status from the app
|
485
|
+
# @return [String] the text description from Puma::HTTP_STATUS_CODES
|
486
|
+
#
|
487
|
+
def fetch_status_code(status)
|
488
|
+
HTTP_STATUS_CODES.fetch(status) { CUSTOM_STAT }
|
489
|
+
end
|
490
|
+
private :fetch_status_code
|
491
|
+
|
492
|
+
# Processes and write headers to the IOBuffer.
|
493
|
+
# @param env [Hash] see Puma::Client#env, from request
|
494
|
+
# @param status [Integer] the status returned by the Rack application
|
495
|
+
# @param headers [Hash] the headers returned by the Rack application
|
496
|
+
# @param content_length [Integer,nil] content length if it can be determined from the
|
497
|
+
# response body
|
498
|
+
# @param io_buffer [Puma::IOBuffer] modified inn place
|
499
|
+
# @param requests [Integer] number of inline requests handled
|
500
|
+
# @param client [Puma::Client]
|
501
|
+
# @return [Hash] resp_info
|
502
|
+
# @version 5.0.3
|
503
|
+
#
|
504
|
+
def str_headers(env, status, headers, res_body, io_buffer, requests, client)
|
505
|
+
line_ending = LINE_END
|
506
|
+
colon = COLON
|
507
|
+
|
508
|
+
resp_info = {}
|
509
|
+
resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
|
510
|
+
|
511
|
+
http_11 = env[SERVER_PROTOCOL] == HTTP_11
|
512
|
+
if http_11
|
513
|
+
resp_info[:allow_chunked] = true
|
514
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
515
|
+
|
516
|
+
# An optimization. The most common response is 200, so we can
|
517
|
+
# reply with the proper 200 status without having to compute
|
518
|
+
# the response header.
|
519
|
+
#
|
520
|
+
if status == 200
|
521
|
+
io_buffer << HTTP_11_200
|
522
|
+
else
|
523
|
+
io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
|
524
|
+
|
525
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
526
|
+
end
|
527
|
+
else
|
528
|
+
resp_info[:allow_chunked] = false
|
529
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
530
|
+
|
531
|
+
# Same optimization as above for HTTP/1.1
|
532
|
+
#
|
533
|
+
if status == 200
|
534
|
+
io_buffer << HTTP_10_200
|
535
|
+
else
|
536
|
+
io_buffer.append "HTTP/1.0 #{status} ",
|
537
|
+
fetch_status_code(status), line_ending
|
538
|
+
|
539
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
# regardless of what the client wants, we always close the connection
|
544
|
+
# if running without request queueing
|
545
|
+
resp_info[:keep_alive] &&= @queue_requests
|
546
|
+
|
547
|
+
# Close the connection after a reasonable number of inline requests
|
548
|
+
# if the server is at capacity and the listener has a new connection ready.
|
549
|
+
# This allows Puma to service connections fairly when the number
|
550
|
+
# of concurrent connections exceeds the size of the threadpool.
|
551
|
+
resp_info[:keep_alive] &&= requests < @max_fast_inline ||
|
552
|
+
@thread_pool.busy_threads < @max_threads ||
|
553
|
+
!client.listener.to_io.wait_readable(0)
|
554
|
+
|
555
|
+
resp_info[:response_hijack] = nil
|
556
|
+
|
557
|
+
headers.each do |k, vs|
|
558
|
+
next if illegal_header_key?(k)
|
559
|
+
|
560
|
+
case k.downcase
|
561
|
+
when CONTENT_LENGTH2
|
562
|
+
next if illegal_header_value?(vs)
|
563
|
+
resp_info[:content_length] = vs
|
564
|
+
next
|
565
|
+
when TRANSFER_ENCODING
|
566
|
+
resp_info[:allow_chunked] = false
|
567
|
+
resp_info[:content_length] = nil
|
568
|
+
resp_info[:transfer_encoding] = vs
|
569
|
+
when HIJACK
|
570
|
+
resp_info[:response_hijack] = vs
|
571
|
+
next
|
572
|
+
when BANNED_HEADER_KEY
|
573
|
+
next
|
574
|
+
end
|
575
|
+
|
576
|
+
ary = if vs.is_a?(::Array) && !vs.empty?
|
577
|
+
vs
|
578
|
+
elsif vs.respond_to?(:to_s) && !vs.to_s.empty?
|
579
|
+
vs.to_s.split NEWLINE
|
580
|
+
else
|
581
|
+
nil
|
582
|
+
end
|
583
|
+
if ary
|
584
|
+
ary.each do |v|
|
585
|
+
next if illegal_header_value?(v)
|
586
|
+
io_buffer.append k, colon, v, line_ending
|
587
|
+
end
|
588
|
+
else
|
589
|
+
io_buffer.append k, colon, line_ending
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
# HTTP/1.1 & 1.0 assume different defaults:
|
594
|
+
# - HTTP 1.0 assumes the connection will be closed if not specified
|
595
|
+
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
596
|
+
# Only set the header if we're doing something which is not the default
|
597
|
+
# for this protocol version
|
598
|
+
if http_11
|
599
|
+
io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
|
600
|
+
else
|
601
|
+
io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
|
602
|
+
end
|
603
|
+
resp_info
|
604
|
+
end
|
605
|
+
private :str_headers
|
606
|
+
end
|
607
|
+
end
|