puma 5.6.9-java → 6.0.0-java
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 +96 -28
- data/LICENSE +0 -0
- data/README.md +21 -17
- data/bin/puma-wild +1 -1
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +34 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +1 -3
- 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 +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +0 -0
- data/docs/nginx.md +0 -0
- data/docs/plugins.md +0 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +0 -0
- data/docs/signals.md +0 -0
- data/docs/stats.md +0 -0
- data/docs/systemd.md +0 -0
- 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 +0 -0
- data/ext/puma_http11/ext_help.h +0 -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/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -5
- 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 +36 -42
- data/lib/puma/cli.rb +11 -17
- data/lib/puma/client.rb +29 -53
- data/lib/puma/cluster/worker.rb +13 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +28 -25
- data/lib/puma/commonlogger.rb +0 -0
- data/lib/puma/configuration.rb +74 -58
- data/lib/puma/const.rb +14 -26
- data/lib/puma/control_cli.rb +3 -6
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +93 -52
- data/lib/puma/error_logger.rb +17 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +29 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +96 -156
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +82 -11
- data/lib/puma/null_io.rb +0 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +4 -4
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +3 -3
- data/lib/puma/request.rb +295 -177
- data/lib/puma/runner.rb +41 -20
- data/lib/puma/server.rb +53 -66
- data/lib/puma/single.rb +10 -10
- data/lib/puma/state_file.rb +1 -4
- data/lib/puma/systemd.rb +3 -2
- data/lib/puma/thread_pool.rb +16 -13
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +10 -9
- data/lib/rack/handler/puma.rb +9 -9
- data/tools/Dockerfile +0 -0
- data/tools/trickletest.rb +0 -0
- metadata +10 -7
- data/lib/puma/queue_close.rb +0 -26
- 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,21 @@ 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
|
+
# 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'
|
14
30
|
|
15
31
|
include Puma::Const
|
16
32
|
|
@@ -25,40 +41,36 @@ module Puma
|
|
25
41
|
#
|
26
42
|
# Finally, it'll return +true+ on keep-alive connections.
|
27
43
|
# @param client [Puma::Client]
|
28
|
-
# @param
|
44
|
+
# @param io_buffer [Puma::IOBuffer]
|
29
45
|
# @param requests [Integer]
|
30
46
|
# @return [Boolean,:async]
|
31
47
|
#
|
32
|
-
def handle_request(client,
|
48
|
+
def handle_request(client, io_buffer, requests)
|
33
49
|
env = client.env
|
34
|
-
|
50
|
+
socket = client.io # io may be a MiniSSL::Socket
|
35
51
|
|
36
|
-
return false if closed_socket?(
|
52
|
+
return false if closed_socket?(socket)
|
37
53
|
|
38
54
|
normalize_env env, client
|
39
55
|
|
40
|
-
env[PUMA_SOCKET] =
|
56
|
+
env[PUMA_SOCKET] = socket
|
41
57
|
|
42
|
-
if env[HTTPS_KEY] &&
|
43
|
-
env[PUMA_PEERCERT] =
|
58
|
+
if env[HTTPS_KEY] && socket.peercert
|
59
|
+
env[PUMA_PEERCERT] = socket.peercert
|
44
60
|
end
|
45
61
|
|
46
62
|
env[HIJACK_P] = true
|
47
63
|
env[HIJACK] = client
|
48
64
|
|
49
|
-
|
50
|
-
|
51
|
-
head = env[REQUEST_METHOD] == HEAD
|
52
|
-
|
53
|
-
env[RACK_INPUT] = body
|
65
|
+
env[RACK_INPUT] = client.body
|
54
66
|
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
55
67
|
|
56
68
|
if @early_hints
|
57
69
|
env[EARLY_HINTS] = lambda { |headers|
|
58
70
|
begin
|
59
|
-
|
71
|
+
fast_write_str socket, str_early_hints(headers)
|
60
72
|
rescue ConnectionError => e
|
61
|
-
@
|
73
|
+
@log_writer.debug_error e
|
62
74
|
# noop, if we lost the socket we just won't send the early hints
|
63
75
|
end
|
64
76
|
}
|
@@ -69,123 +81,147 @@ module Puma
|
|
69
81
|
# A rack extension. If the app writes #call'ables to this
|
70
82
|
# array, we will invoke them when the request is done.
|
71
83
|
#
|
72
|
-
|
84
|
+
env[RACK_AFTER_REPLY] ||= []
|
73
85
|
|
74
86
|
begin
|
75
|
-
|
87
|
+
if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
|
76
88
|
status, headers, res_body = @thread_pool.with_force_shutdown do
|
77
89
|
@app.call(env)
|
78
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
|
79
95
|
|
80
|
-
|
81
|
-
|
82
|
-
status = status.to_i
|
96
|
+
return :async if client.hijacked
|
83
97
|
|
84
|
-
|
85
|
-
unless headers.empty? and res_body == []
|
86
|
-
raise "async response must have empty headers and body"
|
87
|
-
end
|
98
|
+
status = status.to_i
|
88
99
|
|
89
|
-
|
100
|
+
if status == -1
|
101
|
+
unless headers.empty? and res_body == []
|
102
|
+
raise "async response must have empty headers and body"
|
90
103
|
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
104
|
|
99
|
-
|
100
|
-
end
|
101
|
-
|
102
|
-
res_info = {}
|
103
|
-
res_info[:content_length] = nil
|
104
|
-
res_info[:no_body] = head
|
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
|
105
|
+
return :async
|
110
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"
|
111
110
|
|
112
|
-
|
113
|
-
|
114
|
-
|
111
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
112
|
+
rescue Exception => e
|
113
|
+
@log_writer.unknown_error e, client, "Rack app"
|
115
114
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
120
119
|
|
121
|
-
|
122
|
-
|
123
|
-
|
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 }
|
124
153
|
end
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
lines << TRANSFER_ENCODING_CHUNKED
|
136
|
-
chunked = true
|
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
|
137
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
|
138
172
|
|
139
|
-
|
173
|
+
line_ending = LINE_END
|
140
174
|
|
141
|
-
|
175
|
+
content_length = resp_info[:content_length]
|
176
|
+
keep_alive = resp_info[:keep_alive]
|
142
177
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
147
183
|
|
148
|
-
|
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
|
184
|
+
cork_socket socket
|
160
185
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
165
|
-
rescue SystemCallError, IOError
|
166
|
-
raise ConnectionError, "Connection error detected during write"
|
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
|
167
189
|
end
|
168
190
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
180
202
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
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
|
186
209
|
end
|
187
210
|
|
188
|
-
|
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?
|
189
225
|
end
|
190
226
|
|
191
227
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -199,45 +235,107 @@ module Puma
|
|
199
235
|
end
|
200
236
|
end
|
201
237
|
|
202
|
-
#
|
203
|
-
#
|
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
|
204
244
|
# @param str [String] the string written to the io
|
205
245
|
# @raise [ConnectionError]
|
206
246
|
#
|
207
|
-
def
|
247
|
+
def fast_write_str(socket, str)
|
208
248
|
n = 0
|
209
|
-
|
249
|
+
byte_size = str.bytesize
|
250
|
+
while n < byte_size
|
210
251
|
begin
|
211
|
-
n
|
252
|
+
n += socket.syswrite(n.zero? ? str : str.byteslice(n..-1))
|
212
253
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
213
|
-
unless
|
214
|
-
raise ConnectionError,
|
254
|
+
unless socket.wait_writable WRITE_TIMEOUT
|
255
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
215
256
|
end
|
216
|
-
|
217
257
|
retry
|
218
258
|
rescue Errno::EPIPE, SystemCallError, IOError
|
219
|
-
raise ConnectionError,
|
259
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
220
260
|
end
|
221
|
-
|
222
|
-
return if n == str.bytesize
|
223
|
-
str = str.byteslice(n..-1)
|
224
261
|
end
|
225
262
|
end
|
226
|
-
private :fast_write
|
227
263
|
|
228
|
-
#
|
229
|
-
#
|
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]
|
230
272
|
#
|
231
|
-
def
|
232
|
-
|
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
|
233
331
|
end
|
234
|
-
|
332
|
+
|
333
|
+
private :fast_write_str, :fast_write_response
|
235
334
|
|
236
335
|
# Given a Hash +env+ for the request read from +client+, add
|
237
336
|
# and fixup keys to comply with Rack's env guidelines.
|
238
337
|
# @param env [Hash] see Puma::Client#env, from request
|
239
338
|
# @param client [Puma::Client] only needed for Client#peerip
|
240
|
-
# @todo make private in 6.0.0
|
241
339
|
#
|
242
340
|
def normalize_env(env, client)
|
243
341
|
if host = env[HTTP_HOST]
|
@@ -262,14 +360,12 @@ module Puma
|
|
262
360
|
uri = URI.parse(env[REQUEST_URI])
|
263
361
|
env[REQUEST_PATH] = uri.path
|
264
362
|
|
265
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
266
|
-
|
267
363
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
268
364
|
# so only set the env value if there actually is a value.
|
269
365
|
env[QUERY_STRING] = uri.query if uri.query
|
270
366
|
end
|
271
367
|
|
272
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
368
|
+
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
273
369
|
|
274
370
|
# From https://www.ietf.org/rfc/rfc3875 :
|
275
371
|
# "Script authors should be aware that the REMOTE_ADDR and
|
@@ -285,17 +381,31 @@ module Puma
|
|
285
381
|
addr = client.peerip
|
286
382
|
rescue Errno::ENOTCONN
|
287
383
|
# Client disconnects can result in an inability to get the
|
288
|
-
# peeraddr from the socket; default to
|
289
|
-
|
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
|
290
390
|
end
|
291
391
|
|
292
392
|
# Set unix socket addrs to localhost
|
293
|
-
|
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
|
294
400
|
|
295
401
|
env[REMOTE_ADDR] = addr
|
296
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]
|
297
407
|
end
|
298
|
-
|
408
|
+
private :normalize_env
|
299
409
|
|
300
410
|
# @param header_key [#to_s]
|
301
411
|
# @return [Boolean]
|
@@ -318,11 +428,6 @@ module Puma
|
|
318
428
|
# compatibility, we'll convert them back. This code is written to
|
319
429
|
# avoid allocation in the common case (ie there are no headers
|
320
430
|
# with `,` in their names), that's why it has the extra conditionals.
|
321
|
-
#
|
322
|
-
# @note If a normalized version of a `,` header already exists, we ignore
|
323
|
-
# the `,` version. This prevents clobbering headers managed by proxies
|
324
|
-
# but not by clients (Like X-Forwarded-For).
|
325
|
-
#
|
326
431
|
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
327
432
|
# @version 5.0.3
|
328
433
|
#
|
@@ -331,31 +436,23 @@ module Puma
|
|
331
436
|
to_add = nil
|
332
437
|
|
333
438
|
env.each do |k,v|
|
334
|
-
if k.start_with?("HTTP_")
|
439
|
+
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
335
440
|
if to_delete
|
336
441
|
to_delete << k
|
337
442
|
else
|
338
443
|
to_delete = [k]
|
339
444
|
end
|
340
445
|
|
341
|
-
new_k = k.tr(",", "_")
|
342
|
-
if env.key?(new_k)
|
343
|
-
next
|
344
|
-
end
|
345
|
-
|
346
446
|
unless to_add
|
347
447
|
to_add = {}
|
348
448
|
end
|
349
449
|
|
350
|
-
to_add[
|
450
|
+
to_add[k.tr(",", "_")] = v
|
351
451
|
end
|
352
452
|
end
|
353
453
|
|
354
|
-
if to_delete
|
454
|
+
if to_delete
|
355
455
|
to_delete.each { |k| env.delete(k) }
|
356
|
-
end
|
357
|
-
|
358
|
-
if to_add
|
359
456
|
env.merge! to_add
|
360
457
|
end
|
361
458
|
end
|
@@ -367,7 +464,7 @@ module Puma
|
|
367
464
|
# @version 5.0.3
|
368
465
|
#
|
369
466
|
def str_early_hints(headers)
|
370
|
-
eh_str = "HTTP/1.1 103 Early Hints\r\n"
|
467
|
+
eh_str = +"HTTP/1.1 103 Early Hints\r\n"
|
371
468
|
headers.each_pair do |k, vs|
|
372
469
|
next if illegal_header_key?(k)
|
373
470
|
|
@@ -384,66 +481,78 @@ module Puma
|
|
384
481
|
end
|
385
482
|
private :str_early_hints
|
386
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
|
+
|
387
492
|
# Processes and write headers to the IOBuffer.
|
388
493
|
# @param env [Hash] see Puma::Client#env, from request
|
389
494
|
# @param status [Integer] the status returned by the Rack application
|
390
495
|
# @param headers [Hash] the headers returned by the Rack application
|
391
|
-
# @param
|
392
|
-
#
|
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
|
393
499
|
# @param requests [Integer] number of inline requests handled
|
394
500
|
# @param client [Puma::Client]
|
501
|
+
# @return [Hash] resp_info
|
395
502
|
# @version 5.0.3
|
396
503
|
#
|
397
|
-
def str_headers(env, status, headers,
|
504
|
+
def str_headers(env, status, headers, res_body, io_buffer, requests, client)
|
398
505
|
line_ending = LINE_END
|
399
506
|
colon = COLON
|
400
507
|
|
401
|
-
|
508
|
+
resp_info = {}
|
509
|
+
resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
|
510
|
+
|
511
|
+
http_11 = env[SERVER_PROTOCOL] == HTTP_11
|
402
512
|
if http_11
|
403
|
-
|
404
|
-
|
513
|
+
resp_info[:allow_chunked] = true
|
514
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
405
515
|
|
406
516
|
# An optimization. The most common response is 200, so we can
|
407
517
|
# reply with the proper 200 status without having to compute
|
408
518
|
# the response header.
|
409
519
|
#
|
410
520
|
if status == 200
|
411
|
-
|
521
|
+
io_buffer << HTTP_11_200
|
412
522
|
else
|
413
|
-
|
414
|
-
fetch_status_code(status), line_ending
|
523
|
+
io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
|
415
524
|
|
416
|
-
|
525
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
417
526
|
end
|
418
527
|
else
|
419
|
-
|
420
|
-
|
528
|
+
resp_info[:allow_chunked] = false
|
529
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
421
530
|
|
422
531
|
# Same optimization as above for HTTP/1.1
|
423
532
|
#
|
424
533
|
if status == 200
|
425
|
-
|
534
|
+
io_buffer << HTTP_10_200
|
426
535
|
else
|
427
|
-
|
536
|
+
io_buffer.append "HTTP/1.0 #{status} ",
|
428
537
|
fetch_status_code(status), line_ending
|
429
538
|
|
430
|
-
|
539
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
431
540
|
end
|
432
541
|
end
|
433
542
|
|
434
543
|
# regardless of what the client wants, we always close the connection
|
435
544
|
# if running without request queueing
|
436
|
-
|
545
|
+
resp_info[:keep_alive] &&= @queue_requests
|
437
546
|
|
438
547
|
# Close the connection after a reasonable number of inline requests
|
439
548
|
# if the server is at capacity and the listener has a new connection ready.
|
440
549
|
# This allows Puma to service connections fairly when the number
|
441
550
|
# of concurrent connections exceeds the size of the threadpool.
|
442
|
-
|
551
|
+
resp_info[:keep_alive] &&= requests < @max_fast_inline ||
|
443
552
|
@thread_pool.busy_threads < @max_threads ||
|
444
553
|
!client.listener.to_io.wait_readable(0)
|
445
554
|
|
446
|
-
|
555
|
+
resp_info[:response_hijack] = nil
|
447
556
|
|
448
557
|
headers.each do |k, vs|
|
449
558
|
next if illegal_header_key?(k)
|
@@ -451,25 +560,33 @@ module Puma
|
|
451
560
|
case k.downcase
|
452
561
|
when CONTENT_LENGTH2
|
453
562
|
next if illegal_header_value?(vs)
|
454
|
-
|
563
|
+
resp_info[:content_length] = vs
|
455
564
|
next
|
456
565
|
when TRANSFER_ENCODING
|
457
|
-
|
458
|
-
|
566
|
+
resp_info[:allow_chunked] = false
|
567
|
+
resp_info[:content_length] = nil
|
568
|
+
resp_info[:transfer_encoding] = vs
|
459
569
|
when HIJACK
|
460
|
-
|
570
|
+
resp_info[:response_hijack] = vs
|
461
571
|
next
|
462
572
|
when BANNED_HEADER_KEY
|
463
573
|
next
|
464
574
|
end
|
465
575
|
|
466
|
-
if vs.
|
467
|
-
vs
|
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|
|
468
585
|
next if illegal_header_value?(v)
|
469
|
-
|
586
|
+
io_buffer.append k, colon, v, line_ending
|
470
587
|
end
|
471
588
|
else
|
472
|
-
|
589
|
+
io_buffer.append k, colon, line_ending
|
473
590
|
end
|
474
591
|
end
|
475
592
|
|
@@ -479,10 +596,11 @@ module Puma
|
|
479
596
|
# Only set the header if we're doing something which is not the default
|
480
597
|
# for this protocol version
|
481
598
|
if http_11
|
482
|
-
|
599
|
+
io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
|
483
600
|
else
|
484
|
-
|
601
|
+
io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
|
485
602
|
end
|
603
|
+
resp_info
|
486
604
|
end
|
487
605
|
private :str_headers
|
488
606
|
end
|