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