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