puma 6.0.0 → 6.1.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 +60 -5
- data/README.md +1 -0
- data/docs/nginx.md +1 -1
- data/docs/systemd.md +1 -2
- data/lib/puma/binder.rb +4 -3
- data/lib/puma/client.rb +32 -4
- data/lib/puma/cluster/worker.rb +5 -0
- data/lib/puma/cluster.rb +5 -5
- data/lib/puma/configuration.rb +1 -0
- data/lib/puma/const.rb +76 -84
- data/lib/puma/dsl.rb +18 -1
- data/lib/puma/io_buffer.rb +10 -0
- data/lib/puma/launcher.rb +4 -22
- data/lib/puma/log_writer.rb +4 -0
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/rack_default.rb +18 -3
- data/lib/puma/reactor.rb +1 -1
- data/lib/puma/request.rb +154 -106
- data/lib/puma/runner.rb +11 -0
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +6 -7
- data/lib/puma/single.rb +3 -1
- data/lib/puma/thread_pool.rb +1 -4
- data/lib/puma.rb +1 -3
- data/lib/rack/handler/puma.rb +116 -94
- metadata +4 -3
- data/lib/puma/systemd.rb +0 -47
data/lib/puma/launcher.rb
CHANGED
@@ -59,6 +59,10 @@ module Puma
|
|
59
59
|
|
60
60
|
@environment = conf.environment
|
61
61
|
|
62
|
+
if ENV["NOTIFY_SOCKET"]
|
63
|
+
@config.plugins.create('systemd')
|
64
|
+
end
|
65
|
+
|
62
66
|
if @config.options[:bind_to_activated_sockets]
|
63
67
|
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
64
68
|
@config.options[:binds],
|
@@ -180,7 +184,6 @@ module Puma
|
|
180
184
|
|
181
185
|
setup_signals
|
182
186
|
set_process_title
|
183
|
-
integrate_with_systemd
|
184
187
|
|
185
188
|
# This blocks until the server is stopped
|
186
189
|
@runner.run
|
@@ -311,27 +314,6 @@ module Puma
|
|
311
314
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
312
315
|
end
|
313
316
|
|
314
|
-
# Puma's systemd integration allows Puma to inform systemd:
|
315
|
-
# 1. when it has successfully started
|
316
|
-
# 2. when it is starting shutdown
|
317
|
-
# 3. periodically for a liveness check with a watchdog thread
|
318
|
-
def integrate_with_systemd
|
319
|
-
return unless ENV["NOTIFY_SOCKET"]
|
320
|
-
|
321
|
-
begin
|
322
|
-
require_relative 'systemd'
|
323
|
-
rescue LoadError
|
324
|
-
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
325
|
-
return
|
326
|
-
end
|
327
|
-
|
328
|
-
log "* Enabling systemd notification integration"
|
329
|
-
|
330
|
-
systemd = Systemd.new(@log_writer, @events)
|
331
|
-
systemd.hook_events
|
332
|
-
systemd.start_watchdog
|
333
|
-
end
|
334
|
-
|
335
317
|
def log(str)
|
336
318
|
@log_writer.log(str)
|
337
319
|
end
|
data/lib/puma/log_writer.rb
CHANGED
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../plugin'
|
4
|
+
|
5
|
+
# Puma's systemd integration allows Puma to inform systemd:
|
6
|
+
# 1. when it has successfully started
|
7
|
+
# 2. when it is starting shutdown
|
8
|
+
# 3. periodically for a liveness check with a watchdog thread
|
9
|
+
# 4. periodically set the status
|
10
|
+
Puma::Plugin.create do
|
11
|
+
def start(launcher)
|
12
|
+
require_relative '../sd_notify'
|
13
|
+
|
14
|
+
launcher.log_writer.log "* Enabling systemd notification integration"
|
15
|
+
|
16
|
+
# hook_events
|
17
|
+
launcher.events.on_booted { Puma::SdNotify.ready }
|
18
|
+
launcher.events.on_stopped { Puma::SdNotify.stopping }
|
19
|
+
launcher.events.on_restart { Puma::SdNotify.reloading }
|
20
|
+
|
21
|
+
# start watchdog
|
22
|
+
if Puma::SdNotify.watchdog?
|
23
|
+
ping_f = watchdog_sleep_time
|
24
|
+
|
25
|
+
in_background do
|
26
|
+
launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
|
27
|
+
loop do
|
28
|
+
sleep ping_f
|
29
|
+
Puma::SdNotify.watchdog
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# start status loop
|
35
|
+
instance = self
|
36
|
+
sleep_time = 1.0
|
37
|
+
in_background do
|
38
|
+
launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
|
39
|
+
|
40
|
+
loop do
|
41
|
+
sleep sleep_time
|
42
|
+
# TODO: error handling?
|
43
|
+
Puma::SdNotify.status(instance.status)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def status
|
49
|
+
if clustered?
|
50
|
+
messages = stats[:worker_status].map do |worker|
|
51
|
+
common_message(worker[:last_status])
|
52
|
+
end.join(',')
|
53
|
+
|
54
|
+
"Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
|
55
|
+
else
|
56
|
+
"Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def watchdog_sleep_time
|
63
|
+
usec = Integer(ENV["WATCHDOG_USEC"])
|
64
|
+
|
65
|
+
sec_f = usec / 1_000_000.0
|
66
|
+
# "It is recommended that a daemon sends a keep-alive notification message
|
67
|
+
# to the service manager every half of the time returned here."
|
68
|
+
sec_f / 2
|
69
|
+
end
|
70
|
+
|
71
|
+
def stats
|
72
|
+
Puma.stats_hash
|
73
|
+
end
|
74
|
+
|
75
|
+
def clustered?
|
76
|
+
stats.has_key?(:workers)
|
77
|
+
end
|
78
|
+
|
79
|
+
def workers
|
80
|
+
stats.fetch(:workers, 1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def booted_workers
|
84
|
+
stats.fetch(:booted_workers, 1)
|
85
|
+
end
|
86
|
+
|
87
|
+
def common_message(stats)
|
88
|
+
"{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
|
89
|
+
end
|
90
|
+
end
|
data/lib/puma/rack_default.rb
CHANGED
@@ -2,8 +2,23 @@
|
|
2
2
|
|
3
3
|
require_relative '../rack/handler/puma'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# rackup was removed in Rack 3, it is now a separate gem
|
6
|
+
if Object.const_defined? :Rackup
|
7
|
+
module Rackup
|
8
|
+
module Handler
|
9
|
+
def self.default(options = {})
|
10
|
+
::Rackup::Handler::Puma
|
11
|
+
end
|
12
|
+
end
|
8
13
|
end
|
14
|
+
elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
|
15
|
+
module Rack
|
16
|
+
module Handler
|
17
|
+
def self.default(options = {})
|
18
|
+
::Rack::Handler::Puma
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise "Rack 3 must be used with the Rackup gem"
|
9
24
|
end
|
data/lib/puma/reactor.rb
CHANGED
data/lib/puma/request.rb
CHANGED
@@ -14,15 +14,18 @@ module Puma
|
|
14
14
|
#
|
15
15
|
module Request # :nodoc:
|
16
16
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
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
|
20
21
|
|
21
|
-
#
|
22
|
+
# File body: smaller bodies are combined with io_buffer, then written to
|
23
|
+
# socket. Larger bodies are written separately using `copy_stream`
|
22
24
|
IO_BODY_MAX = 1_024 * 64
|
23
25
|
|
24
|
-
#
|
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
|
26
29
|
|
27
30
|
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
28
31
|
|
@@ -41,16 +44,22 @@ module Puma
|
|
41
44
|
#
|
42
45
|
# Finally, it'll return +true+ on keep-alive connections.
|
43
46
|
# @param client [Puma::Client]
|
44
|
-
# @param io_buffer [Puma::IOBuffer]
|
45
47
|
# @param requests [Integer]
|
46
48
|
# @return [Boolean,:async]
|
47
49
|
#
|
48
|
-
def handle_request(client,
|
50
|
+
def handle_request(client, requests)
|
49
51
|
env = client.env
|
52
|
+
io_buffer = client.io_buffer
|
50
53
|
socket = client.io # io may be a MiniSSL::Socket
|
54
|
+
app_body = nil
|
55
|
+
|
51
56
|
|
52
57
|
return false if closed_socket?(socket)
|
53
58
|
|
59
|
+
if client.http_content_length_limit_exceeded
|
60
|
+
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
61
|
+
end
|
62
|
+
|
54
63
|
normalize_env env, client
|
55
64
|
|
56
65
|
env[PUMA_SOCKET] = socket
|
@@ -85,14 +94,18 @@ module Puma
|
|
85
94
|
|
86
95
|
begin
|
87
96
|
if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
|
88
|
-
status, headers,
|
97
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
89
98
|
@app.call(env)
|
90
99
|
end
|
91
100
|
else
|
92
101
|
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
93
|
-
status, headers,
|
102
|
+
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
94
103
|
end
|
95
104
|
|
105
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
106
|
+
# is called
|
107
|
+
res_body = app_body
|
108
|
+
|
96
109
|
return :async if client.hijacked
|
97
110
|
|
98
111
|
status = status.to_i
|
@@ -114,60 +127,83 @@ module Puma
|
|
114
127
|
|
115
128
|
status, headers, res_body = lowlevel_error(e, env, 500)
|
116
129
|
end
|
117
|
-
prepare_response(status, headers, res_body,
|
130
|
+
prepare_response(status, headers, res_body, requests, client)
|
131
|
+
ensure
|
132
|
+
io_buffer.reset
|
133
|
+
uncork_socket client.io
|
134
|
+
app_body.close if app_body.respond_to? :close
|
135
|
+
client.tempfile&.unlink
|
136
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
137
|
+
begin
|
138
|
+
after_reply.each { |o| o.call }
|
139
|
+
rescue StandardError => e
|
140
|
+
@log_writer.debug_error e
|
141
|
+
end unless after_reply.empty?
|
118
142
|
end
|
119
143
|
|
120
144
|
# Assembles the headers and prepares the body for actually sending the
|
121
|
-
# response via
|
145
|
+
# response via `#fast_write_response`.
|
122
146
|
#
|
123
147
|
# @param status [Integer] the status returned by the Rack application
|
124
148
|
# @param headers [Hash] the headers returned by the Rack application
|
125
|
-
# @param
|
126
|
-
# a call to `lowlevel_error`
|
127
|
-
# @param io_buffer [Puma::IOBuffer] modified in place
|
149
|
+
# @param res_body [Array] the body returned by the Rack application or
|
150
|
+
# a call to `Server#lowlevel_error`
|
128
151
|
# @param requests [Integer] number of inline requests handled
|
129
152
|
# @param client [Puma::Client]
|
130
|
-
# @return [Boolean,:async]
|
131
|
-
def prepare_response(status, headers,
|
153
|
+
# @return [Boolean,:async] keep-alive status or `:async`
|
154
|
+
def prepare_response(status, headers, res_body, requests, client)
|
132
155
|
env = client.env
|
133
|
-
socket
|
134
|
-
|
135
|
-
after_reply = env[RACK_AFTER_REPLY] || []
|
156
|
+
socket = client.io
|
157
|
+
io_buffer = client.io_buffer
|
136
158
|
|
137
159
|
return false if closed_socket?(socket)
|
138
160
|
|
139
|
-
|
161
|
+
# Close the connection after a reasonable number of inline requests
|
162
|
+
# if the server is at capacity and the listener has a new connection ready.
|
163
|
+
# This allows Puma to service connections fairly when the number
|
164
|
+
# of concurrent connections exceeds the size of the threadpool.
|
165
|
+
force_keep_alive = requests < @max_fast_inline ||
|
166
|
+
@thread_pool.busy_threads < @max_threads ||
|
167
|
+
!client.listener.to_io.wait_readable(0)
|
168
|
+
|
169
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
170
|
+
|
171
|
+
close_body = false
|
140
172
|
|
141
173
|
# below converts app_body into body, dependent on app_body's characteristics, and
|
142
174
|
# resp_info[:content_length] will be set if it can be determined
|
143
175
|
if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
|
144
|
-
if
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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)
|
176
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) && array_body.is_a?(Array)
|
177
|
+
body = array_body
|
178
|
+
resp_info[:content_length] = body.sum(&:bytesize)
|
179
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
180
|
+
body = res_body
|
181
|
+
resp_info[:content_length] = body.size
|
182
|
+
elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
183
|
+
File.readable?(fn = res_body.to_path)
|
160
184
|
body = File.open fn, 'rb'
|
161
185
|
resp_info[:content_length] = body.size
|
186
|
+
close_body = true
|
162
187
|
else
|
163
|
-
body =
|
188
|
+
body = res_body
|
164
189
|
end
|
165
|
-
elsif !
|
166
|
-
File.readable?(fn =
|
190
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
191
|
+
File.readable?(fn = res_body.to_path)
|
167
192
|
body = File.open fn, 'rb'
|
168
193
|
resp_info[:content_length] = body.size
|
194
|
+
close_body = true
|
195
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
|
196
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
197
|
+
# Sprockets::Asset
|
198
|
+
resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
|
199
|
+
if res_body.to_hash[:source] # use each to return @source
|
200
|
+
body = res_body
|
201
|
+
else # avoid each and use a File object
|
202
|
+
body = File.open fn, 'rb'
|
203
|
+
close_body = true
|
204
|
+
end
|
169
205
|
else
|
170
|
-
body =
|
206
|
+
body = res_body
|
171
207
|
end
|
172
208
|
|
173
209
|
line_ending = LINE_END
|
@@ -175,8 +211,8 @@ module Puma
|
|
175
211
|
content_length = resp_info[:content_length]
|
176
212
|
keep_alive = resp_info[:keep_alive]
|
177
213
|
|
178
|
-
if
|
179
|
-
response_hijack =
|
214
|
+
if res_body && !res_body.respond_to?(:each)
|
215
|
+
response_hijack = res_body
|
180
216
|
else
|
181
217
|
response_hijack = resp_info[:response_hijack]
|
182
218
|
end
|
@@ -184,44 +220,40 @@ module Puma
|
|
184
220
|
cork_socket socket
|
185
221
|
|
186
222
|
if resp_info[:no_body]
|
187
|
-
|
223
|
+
# 101 (Switching Protocols) doesn't return here or have content_length,
|
224
|
+
# it should be using `response_hijack`
|
225
|
+
unless status == 101
|
226
|
+
if content_length && status != 204
|
227
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
228
|
+
end
|
229
|
+
|
230
|
+
io_buffer << LINE_END
|
231
|
+
fast_write_str socket, io_buffer.read_and_reset
|
232
|
+
socket.flush
|
233
|
+
return keep_alive
|
234
|
+
end
|
235
|
+
else
|
236
|
+
if content_length
|
188
237
|
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
238
|
+
chunked = false
|
239
|
+
elsif !response_hijack && resp_info[:allow_chunked]
|
240
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
241
|
+
chunked = true
|
189
242
|
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
243
|
end
|
202
244
|
|
203
245
|
io_buffer << line_ending
|
204
246
|
|
205
247
|
if response_hijack
|
206
|
-
fast_write_str socket, io_buffer.
|
248
|
+
fast_write_str socket, io_buffer.read_and_reset
|
249
|
+
uncork_socket socket
|
207
250
|
response_hijack.call socket
|
208
251
|
return :async
|
209
252
|
end
|
210
253
|
|
211
254
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
255
|
+
body.close if close_body
|
212
256
|
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
257
|
end
|
226
258
|
|
227
259
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -237,10 +269,10 @@ module Puma
|
|
237
269
|
|
238
270
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
239
271
|
# and body segments (called by `fast_write_response`).
|
240
|
-
# Writes a string to
|
272
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
241
273
|
# Large strings may not be written in one pass, especially if `io` is a
|
242
274
|
# `MiniSSL::Socket`.
|
243
|
-
# @param
|
275
|
+
# @param socket [#write_nonblock] the request/response socket
|
244
276
|
# @param str [String] the string written to the io
|
245
277
|
# @raise [ConnectionError]
|
246
278
|
#
|
@@ -249,7 +281,7 @@ module Puma
|
|
249
281
|
byte_size = str.bytesize
|
250
282
|
while n < byte_size
|
251
283
|
begin
|
252
|
-
n += socket.
|
284
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
253
285
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
254
286
|
unless socket.wait_writable WRITE_TIMEOUT
|
255
287
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
@@ -267,39 +299,41 @@ module Puma
|
|
267
299
|
# @param socket [#write] the response socket
|
268
300
|
# @param body [Enumerable, File] the body object
|
269
301
|
# @param io_buffer [Puma::IOBuffer] contains headers
|
270
|
-
# @param
|
302
|
+
# @param chunked [Boolean]
|
303
|
+
# @paramn content_length [Integer
|
271
304
|
# @raise [ConnectionError]
|
272
305
|
#
|
273
306
|
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
274
|
-
if body.is_a?(::File)
|
307
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
275
308
|
if chunked # would this ever happen?
|
276
|
-
while
|
277
|
-
io_buffer.append
|
309
|
+
while chunk = body.read(BODY_LEN_MAX)
|
310
|
+
io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
|
278
311
|
end
|
279
|
-
|
280
|
-
fast_write_str socket, io_buffer.to_s
|
312
|
+
fast_write_str socket, CLOSE_CHUNKED
|
281
313
|
else
|
282
314
|
if content_length <= IO_BODY_MAX
|
283
|
-
io_buffer.write body.
|
284
|
-
fast_write_str socket, io_buffer.
|
315
|
+
io_buffer.write body.read(content_length)
|
316
|
+
fast_write_str socket, io_buffer.read_and_reset
|
285
317
|
else
|
286
|
-
fast_write_str socket, io_buffer.
|
318
|
+
fast_write_str socket, io_buffer.read_and_reset
|
287
319
|
IO.copy_stream body, socket
|
288
320
|
end
|
289
321
|
end
|
290
|
-
body.close
|
291
322
|
elsif body.is_a?(::Array) && body.length == 1
|
292
|
-
body_first =
|
293
|
-
|
323
|
+
body_first = nil
|
324
|
+
# using body_first = body.first causes issues?
|
325
|
+
body.each { |str| body_first ||= str }
|
326
|
+
|
327
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
328
|
+
# smaller body, write to io_buffer first
|
329
|
+
io_buffer.write body_first
|
330
|
+
fast_write_str socket, io_buffer.read_and_reset
|
331
|
+
else
|
294
332
|
# large body, write both header & body to socket
|
295
|
-
fast_write_str socket, io_buffer.
|
333
|
+
fast_write_str socket, io_buffer.read_and_reset
|
296
334
|
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
335
|
end
|
302
|
-
|
336
|
+
elsif body.is_a?(::Array)
|
303
337
|
# for array bodies, flush io_buffer to socket when size is greater than
|
304
338
|
# IO_BUFFER_LEN_MAX
|
305
339
|
if chunked
|
@@ -307,8 +341,7 @@ module Puma
|
|
307
341
|
next if (byte_size = part.bytesize).zero?
|
308
342
|
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
309
343
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
310
|
-
fast_write_str socket, io_buffer.
|
311
|
-
io_buffer.reset
|
344
|
+
fast_write_str socket, io_buffer.read_and_reset
|
312
345
|
end
|
313
346
|
end
|
314
347
|
io_buffer.write CLOSE_CHUNKED
|
@@ -317,13 +350,31 @@ module Puma
|
|
317
350
|
next if part.bytesize.zero?
|
318
351
|
io_buffer.write part
|
319
352
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
320
|
-
fast_write_str socket, io_buffer.
|
321
|
-
io_buffer.reset
|
353
|
+
fast_write_str socket, io_buffer.read_and_reset
|
322
354
|
end
|
323
355
|
end
|
324
356
|
end
|
325
|
-
|
357
|
+
# may write last body part for non-chunked, also headers if array is empty
|
358
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
359
|
+
else
|
360
|
+
# for enum bodies
|
361
|
+
fast_write_str socket, io_buffer.read_and_reset
|
362
|
+
if chunked
|
363
|
+
body.each do |part|
|
364
|
+
next if (byte_size = part.bytesize).zero?
|
365
|
+
fast_write_str socket, (byte_size.to_s(16) << LINE_END)
|
366
|
+
fast_write_str socket, part
|
367
|
+
fast_write_str socket, LINE_END
|
368
|
+
end
|
369
|
+
fast_write_str socket, CLOSE_CHUNKED
|
370
|
+
else
|
371
|
+
body.each do |part|
|
372
|
+
next if part.bytesize.zero?
|
373
|
+
fast_write_str socket, part
|
374
|
+
end
|
375
|
+
end
|
326
376
|
end
|
377
|
+
socket.flush
|
327
378
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
328
379
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
329
380
|
rescue Errno::EPIPE, SystemCallError, IOError
|
@@ -436,7 +487,7 @@ module Puma
|
|
436
487
|
to_add = nil
|
437
488
|
|
438
489
|
env.each do |k,v|
|
439
|
-
if k.start_with?("HTTP_")
|
490
|
+
if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
|
440
491
|
if to_delete
|
441
492
|
to_delete << k
|
442
493
|
else
|
@@ -496,12 +547,13 @@ module Puma
|
|
496
547
|
# @param content_length [Integer,nil] content length if it can be determined from the
|
497
548
|
# response body
|
498
549
|
# @param io_buffer [Puma::IOBuffer] modified inn place
|
499
|
-
# @param
|
500
|
-
#
|
550
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
551
|
+
# status and `@max_fast_inline`
|
501
552
|
# @return [Hash] resp_info
|
502
553
|
# @version 5.0.3
|
503
554
|
#
|
504
|
-
def str_headers(env, status, headers, res_body, io_buffer,
|
555
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
556
|
+
|
505
557
|
line_ending = LINE_END
|
506
558
|
colon = COLON
|
507
559
|
|
@@ -544,13 +596,8 @@ module Puma
|
|
544
596
|
# if running without request queueing
|
545
597
|
resp_info[:keep_alive] &&= @queue_requests
|
546
598
|
|
547
|
-
#
|
548
|
-
|
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)
|
599
|
+
# see prepare_response
|
600
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
554
601
|
|
555
602
|
resp_info[:response_hijack] = nil
|
556
603
|
|
@@ -560,7 +607,8 @@ module Puma
|
|
560
607
|
case k.downcase
|
561
608
|
when CONTENT_LENGTH2
|
562
609
|
next if illegal_header_value?(vs)
|
563
|
-
|
610
|
+
# nil.to_i is 0, nil&.to_i is nil
|
611
|
+
resp_info[:content_length] = vs&.to_i
|
564
612
|
next
|
565
613
|
when TRANSFER_ENCODING
|
566
614
|
resp_info[:allow_chunked] = false
|
data/lib/puma/runner.rb
CHANGED
@@ -182,6 +182,10 @@ module Puma
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
+
def utc_iso8601(val)
|
186
|
+
"#{val.utc.strftime '%FT%T'}Z"
|
187
|
+
end
|
188
|
+
|
185
189
|
def stats
|
186
190
|
{
|
187
191
|
versions: {
|
@@ -194,5 +198,12 @@ module Puma
|
|
194
198
|
}
|
195
199
|
}
|
196
200
|
end
|
201
|
+
|
202
|
+
# this method call should always be guarded by `@log_writer.debug?`
|
203
|
+
def debug_loaded_extensions(str)
|
204
|
+
@log_writer.debug "────────────────────────────────── #{str}"
|
205
|
+
re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
|
206
|
+
$LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
|
207
|
+
end
|
197
208
|
end
|
198
209
|
end
|