puma 3.8.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/History.md +157 -0
- data/README.md +155 -225
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
- 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/plugins.md +28 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +130 -37
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/http11_parser.c +84 -84
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/mini_ssl.c +51 -9
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +26 -6
- data/lib/puma.rb +8 -0
- data/lib/puma/app/status.rb +9 -0
- data/lib/puma/binder.rb +31 -18
- data/lib/puma/cli.rb +22 -7
- data/lib/puma/client.rb +67 -18
- data/lib/puma/cluster.rb +64 -19
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +22 -14
- data/lib/puma/const.rb +13 -2
- data/lib/puma/control_cli.rb +26 -14
- data/lib/puma/convenient.rb +2 -0
- data/lib/puma/daemon_ext.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +91 -12
- data/lib/puma/events.rb +3 -2
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +51 -30
- data/lib/puma/minissl.rb +79 -28
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -1
- data/lib/puma/rack/builder.rb +2 -1
- data/lib/puma/reactor.rb +218 -30
- data/lib/puma/runner.rb +17 -4
- data/lib/puma/server.rb +113 -49
- data/lib/puma/single.rb +16 -5
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +59 -6
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +13 -2
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +7 -7
- data/tools/jungle/init.d/run-puma +1 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/trickletest.rb +1 -1
- metadata +25 -87
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -12
- data/Manifest.txt +0 -78
- data/Rakefile +0 -158
- data/Release.md +0 -9
- data/gemfiles/2.1-Gemfile +0 -12
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/puma.gemspec +0 -52
data/lib/puma.rb
CHANGED
data/lib/puma/app/status.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
module App
|
3
5
|
class Status
|
@@ -55,6 +57,13 @@ module Puma
|
|
55
57
|
return rack_response(200, OK_STATUS)
|
56
58
|
end
|
57
59
|
|
60
|
+
when /\/gc$/
|
61
|
+
GC.start
|
62
|
+
return rack_response(200, OK_STATUS)
|
63
|
+
|
64
|
+
when /\/gc-stats$/
|
65
|
+
return rack_response(200, GC.stat.to_json)
|
66
|
+
|
58
67
|
when /\/stats$/
|
59
68
|
return rack_response(200, @cli.stats)
|
60
69
|
else
|
data/lib/puma/binder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'socket'
|
3
5
|
|
@@ -48,7 +50,13 @@ module Puma
|
|
48
50
|
|
49
51
|
def close
|
50
52
|
@ios.each { |i| i.close }
|
51
|
-
@unix_paths.each
|
53
|
+
@unix_paths.each do |i|
|
54
|
+
# Errno::ENOENT is intermittently raised
|
55
|
+
begin
|
56
|
+
File.unlink i
|
57
|
+
rescue Errno::ENOENT
|
58
|
+
end
|
59
|
+
end
|
52
60
|
end
|
53
61
|
|
54
62
|
def import_from_env
|
@@ -90,19 +98,19 @@ module Puma
|
|
90
98
|
case uri.scheme
|
91
99
|
when "tcp"
|
92
100
|
if fd = @inherited_fds.delete(str)
|
93
|
-
logger.log "* Inherited #{str}"
|
94
101
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
102
|
+
logger.log "* Inherited #{str}"
|
95
103
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
96
|
-
logger.log "* Activated #{str}"
|
97
104
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
105
|
+
logger.log "* Activated #{str}"
|
98
106
|
else
|
99
107
|
params = Util.parse_query uri.query
|
100
108
|
|
101
109
|
opt = params.key?('low_latency')
|
102
110
|
bak = params.fetch('backlog', 1024).to_i
|
103
111
|
|
104
|
-
logger.log "* Listening on #{str}"
|
105
112
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
113
|
+
logger.log "* Listening on #{str}"
|
106
114
|
end
|
107
115
|
|
108
116
|
@listeners << [str, io] if io
|
@@ -110,17 +118,15 @@ module Puma
|
|
110
118
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
111
119
|
|
112
120
|
if fd = @inherited_fds.delete(str)
|
113
|
-
logger.log "* Inherited #{str}"
|
114
121
|
io = inherit_unix_listener path, fd
|
122
|
+
logger.log "* Inherited #{str}"
|
115
123
|
elsif sock = @activated_sockets.delete([ :unix, path ])
|
116
|
-
logger.log "* Activated #{str}"
|
117
124
|
io = inherit_unix_listener path, sock
|
125
|
+
logger.log "* Activated #{str}"
|
118
126
|
else
|
119
|
-
logger.log "* Listening on #{str}"
|
120
|
-
|
121
127
|
umask = nil
|
122
128
|
mode = nil
|
123
|
-
backlog =
|
129
|
+
backlog = 1024
|
124
130
|
|
125
131
|
if uri.query
|
126
132
|
params = Util.parse_query uri.query
|
@@ -139,6 +145,7 @@ module Puma
|
|
139
145
|
end
|
140
146
|
|
141
147
|
io = add_unix_listener path, umask, mode, backlog
|
148
|
+
logger.log "* Listening on #{str}"
|
142
149
|
end
|
143
150
|
|
144
151
|
@listeners << [str, io]
|
@@ -162,6 +169,7 @@ module Puma
|
|
162
169
|
end
|
163
170
|
|
164
171
|
ctx.keystore_pass = params['keystore-pass']
|
172
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
165
173
|
else
|
166
174
|
unless params['key']
|
167
175
|
@events.error "Please specify the SSL key via 'key='"
|
@@ -182,8 +190,11 @@ module Puma
|
|
182
190
|
end
|
183
191
|
|
184
192
|
ctx.ca = params['ca'] if params['ca']
|
193
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
185
194
|
end
|
186
195
|
|
196
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
197
|
+
|
187
198
|
if params['verify_mode']
|
188
199
|
ctx.verify_mode = case params['verify_mode']
|
189
200
|
when "peer"
|
@@ -202,11 +213,11 @@ module Puma
|
|
202
213
|
logger.log "* Inherited #{str}"
|
203
214
|
io = inherit_ssl_listener fd, ctx
|
204
215
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
205
|
-
logger.log "* Activated #{str}"
|
206
216
|
io = inherit_ssl_listener sock, ctx
|
217
|
+
logger.log "* Activated #{str}"
|
207
218
|
else
|
208
|
-
logger.log "* Listening on #{str}"
|
209
219
|
io = add_ssl_listener uri.host, uri.port, ctx
|
220
|
+
logger.log "* Listening on #{str}"
|
210
221
|
end
|
211
222
|
|
212
223
|
@listeners << [str, io] if io
|
@@ -245,9 +256,10 @@ module Puma
|
|
245
256
|
end
|
246
257
|
end
|
247
258
|
|
248
|
-
def
|
249
|
-
|
250
|
-
|
259
|
+
def loopback_addresses
|
260
|
+
Socket.ip_address_list.select do |addrinfo|
|
261
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
262
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
251
263
|
end
|
252
264
|
|
253
265
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -259,7 +271,7 @@ module Puma
|
|
259
271
|
#
|
260
272
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
261
273
|
if host == "localhost"
|
262
|
-
|
274
|
+
loopback_addresses.each do |addr|
|
263
275
|
add_tcp_listener addr, port, optimize_for_latency, backlog
|
264
276
|
end
|
265
277
|
return
|
@@ -298,7 +310,7 @@ module Puma
|
|
298
310
|
MiniSSL.check
|
299
311
|
|
300
312
|
if host == "localhost"
|
301
|
-
|
313
|
+
loopback_addresses.each do |addr|
|
302
314
|
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
|
303
315
|
end
|
304
316
|
return
|
@@ -312,6 +324,7 @@ module Puma
|
|
312
324
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
313
325
|
s.listen backlog
|
314
326
|
|
327
|
+
|
315
328
|
ssl = MiniSSL::Server.new s, ctx
|
316
329
|
env = @proto_env.dup
|
317
330
|
env[HTTPS_KEY] = HTTPS
|
@@ -343,7 +356,7 @@ module Puma
|
|
343
356
|
|
344
357
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
345
358
|
#
|
346
|
-
def add_unix_listener(path, umask=nil, mode=nil, backlog=
|
359
|
+
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
347
360
|
@unix_paths << path
|
348
361
|
|
349
362
|
# Let anyone connect by default
|
@@ -364,7 +377,7 @@ module Puma
|
|
364
377
|
end
|
365
378
|
|
366
379
|
s = UNIXServer.new(path)
|
367
|
-
s.listen backlog
|
380
|
+
s.listen backlog
|
368
381
|
@ios << s
|
369
382
|
ensure
|
370
383
|
File.umask old_mask
|
data/lib/puma/cli.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
require 'uri'
|
3
5
|
|
6
|
+
require 'puma'
|
4
7
|
require 'puma/configuration'
|
5
8
|
require 'puma/launcher'
|
6
9
|
require 'puma/const'
|
@@ -83,6 +86,14 @@ module Puma
|
|
83
86
|
raise UnsupportedOption
|
84
87
|
end
|
85
88
|
|
89
|
+
def configure_control_url(command_line_arg)
|
90
|
+
if command_line_arg
|
91
|
+
@control_url = command_line_arg
|
92
|
+
elsif Puma.jruby?
|
93
|
+
unsupported "No default url available on JRuby"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
86
97
|
# Build the OptionParser object to handle the available options.
|
87
98
|
#
|
88
99
|
|
@@ -97,13 +108,13 @@ module Puma
|
|
97
108
|
file_config.load arg
|
98
109
|
end
|
99
110
|
|
100
|
-
o.on "--control URL", "The bind url to use for the control server"
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
111
|
+
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
112
|
+
configure_control_url(arg)
|
113
|
+
end
|
114
|
+
|
115
|
+
# alias --control-url for backwards-compatibility
|
116
|
+
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
117
|
+
configure_control_url(arg)
|
107
118
|
end
|
108
119
|
|
109
120
|
o.on "--control-token TOKEN",
|
@@ -181,6 +192,10 @@ module Puma
|
|
181
192
|
user_config.tcp_mode!
|
182
193
|
end
|
183
194
|
|
195
|
+
o.on "--early-hints", "Enable early hints support" do
|
196
|
+
user_config.early_hints
|
197
|
+
end
|
198
|
+
|
184
199
|
o.on "-V", "--version", "Print the version information" do
|
185
200
|
puts "puma version #{Puma::Const::VERSION}"
|
186
201
|
exit 0
|
data/lib/puma/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class IO
|
2
4
|
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
3
5
|
# So this either creates the constant (on 1.8), or harmlessly
|
@@ -21,6 +23,18 @@ module Puma
|
|
21
23
|
|
22
24
|
class ConnectionError < RuntimeError; end
|
23
25
|
|
26
|
+
# An instance of this class represents a unique request from a client.
|
27
|
+
# For example a web request from a browser or from CURL. This
|
28
|
+
#
|
29
|
+
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
+
# by the reactor, that's because the latter is expected to call `#to_io`
|
31
|
+
# on any non-IO objects it polls. For example nio4r internally calls
|
32
|
+
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
33
|
+
# registered.
|
34
|
+
#
|
35
|
+
# Instances of this class are responsible for knowing if
|
36
|
+
# the header and body are fully buffered via the `try_to_finish` method.
|
37
|
+
# They can be used to "time out" a response via the `timeout_at` reader.
|
24
38
|
class Client
|
25
39
|
include Puma::Const
|
26
40
|
extend Puma::Delegation
|
@@ -41,6 +55,7 @@ module Puma
|
|
41
55
|
@ready = false
|
42
56
|
|
43
57
|
@body = nil
|
58
|
+
@body_read_start = nil
|
44
59
|
@buffer = nil
|
45
60
|
@tempfile = nil
|
46
61
|
|
@@ -51,6 +66,8 @@ module Puma
|
|
51
66
|
|
52
67
|
@peerip = nil
|
53
68
|
@remote_addr_header = nil
|
69
|
+
|
70
|
+
@body_remain = 0
|
54
71
|
end
|
55
72
|
|
56
73
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -89,6 +106,8 @@ module Puma
|
|
89
106
|
@tempfile = nil
|
90
107
|
@parsed_bytes = 0
|
91
108
|
@ready = false
|
109
|
+
@body_remain = 0
|
110
|
+
@peerip = nil
|
92
111
|
|
93
112
|
if @buffer
|
94
113
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -101,9 +120,16 @@ module Puma
|
|
101
120
|
end
|
102
121
|
|
103
122
|
return false
|
104
|
-
|
105
|
-
|
106
|
-
|
123
|
+
else
|
124
|
+
begin
|
125
|
+
if fast_check &&
|
126
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
127
|
+
return try_to_finish
|
128
|
+
end
|
129
|
+
rescue IOError
|
130
|
+
# swallow it
|
131
|
+
end
|
132
|
+
|
107
133
|
end
|
108
134
|
end
|
109
135
|
|
@@ -111,6 +137,7 @@ module Puma
|
|
111
137
|
begin
|
112
138
|
@io.close
|
113
139
|
rescue IOError
|
140
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
114
141
|
end
|
115
142
|
end
|
116
143
|
|
@@ -133,8 +160,11 @@ module Puma
|
|
133
160
|
def decode_chunk(chunk)
|
134
161
|
if @partial_part_left > 0
|
135
162
|
if @partial_part_left <= chunk.size
|
136
|
-
|
163
|
+
if @partial_part_left > 2
|
164
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
165
|
+
end
|
137
166
|
chunk = chunk[@partial_part_left..-1]
|
167
|
+
@partial_part_left = 0
|
138
168
|
else
|
139
169
|
@body << chunk
|
140
170
|
@partial_part_left -= chunk.size
|
@@ -156,9 +186,9 @@ module Puma
|
|
156
186
|
if len == 0
|
157
187
|
@body.rewind
|
158
188
|
rest = io.read
|
189
|
+
rest = rest[2..-1] if rest.start_with?("\r\n")
|
159
190
|
@buffer = rest.empty? ? nil : rest
|
160
|
-
|
161
|
-
@ready = true
|
191
|
+
set_ready
|
162
192
|
return true
|
163
193
|
end
|
164
194
|
|
@@ -196,7 +226,7 @@ module Puma
|
|
196
226
|
while true
|
197
227
|
begin
|
198
228
|
chunk = @io.read_nonblock(4096)
|
199
|
-
rescue
|
229
|
+
rescue IO::WaitReadable
|
200
230
|
return false
|
201
231
|
rescue SystemCallError, IOError
|
202
232
|
raise ConnectionError, "Connection error detected during read"
|
@@ -206,8 +236,7 @@ module Puma
|
|
206
236
|
unless chunk
|
207
237
|
@body.close
|
208
238
|
@buffer = nil
|
209
|
-
|
210
|
-
@ready = true
|
239
|
+
set_ready
|
211
240
|
raise EOFError
|
212
241
|
end
|
213
242
|
|
@@ -216,6 +245,8 @@ module Puma
|
|
216
245
|
end
|
217
246
|
|
218
247
|
def setup_body
|
248
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
249
|
+
|
219
250
|
if @env[HTTP_EXPECT] == CONTINUE
|
220
251
|
# TODO allow a hook here to check the headers before
|
221
252
|
# going forward
|
@@ -240,8 +271,7 @@ module Puma
|
|
240
271
|
unless cl
|
241
272
|
@buffer = body.empty? ? nil : body
|
242
273
|
@body = EmptyBody
|
243
|
-
|
244
|
-
@ready = true
|
274
|
+
set_ready
|
245
275
|
return true
|
246
276
|
end
|
247
277
|
|
@@ -250,8 +280,7 @@ module Puma
|
|
250
280
|
if remain <= 0
|
251
281
|
@body = StringIO.new(body)
|
252
282
|
@buffer = nil
|
253
|
-
|
254
|
-
@ready = true
|
283
|
+
set_ready
|
255
284
|
return true
|
256
285
|
end
|
257
286
|
|
@@ -279,10 +308,17 @@ module Puma
|
|
279
308
|
data = @io.read_nonblock(CHUNK_SIZE)
|
280
309
|
rescue Errno::EAGAIN
|
281
310
|
return false
|
282
|
-
rescue SystemCallError, IOError
|
311
|
+
rescue SystemCallError, IOError, EOFError
|
283
312
|
raise ConnectionError, "Connection error detected during read"
|
284
313
|
end
|
285
314
|
|
315
|
+
# No data means a closed socket
|
316
|
+
unless data
|
317
|
+
@buffer = nil
|
318
|
+
set_ready
|
319
|
+
raise EOFError
|
320
|
+
end
|
321
|
+
|
286
322
|
if @buffer
|
287
323
|
@buffer << data
|
288
324
|
else
|
@@ -312,6 +348,13 @@ module Puma
|
|
312
348
|
raise e
|
313
349
|
end
|
314
350
|
|
351
|
+
# No data means a closed socket
|
352
|
+
unless data
|
353
|
+
@buffer = nil
|
354
|
+
set_ready
|
355
|
+
raise EOFError
|
356
|
+
end
|
357
|
+
|
315
358
|
if @buffer
|
316
359
|
@buffer << data
|
317
360
|
else
|
@@ -385,8 +428,7 @@ module Puma
|
|
385
428
|
unless chunk
|
386
429
|
@body.close
|
387
430
|
@buffer = nil
|
388
|
-
|
389
|
-
@ready = true
|
431
|
+
set_ready
|
390
432
|
raise EOFError
|
391
433
|
end
|
392
434
|
|
@@ -395,8 +437,7 @@ module Puma
|
|
395
437
|
if remain <= 0
|
396
438
|
@body.rewind
|
397
439
|
@buffer = nil
|
398
|
-
|
399
|
-
@ready = true
|
440
|
+
set_ready
|
400
441
|
return true
|
401
442
|
end
|
402
443
|
|
@@ -405,6 +446,14 @@ module Puma
|
|
405
446
|
false
|
406
447
|
end
|
407
448
|
|
449
|
+
def set_ready
|
450
|
+
if @body_read_start
|
451
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
452
|
+
end
|
453
|
+
@requests_served += 1
|
454
|
+
@ready = true
|
455
|
+
end
|
456
|
+
|
408
457
|
def write_400
|
409
458
|
begin
|
410
459
|
@io << ERROR_400_RESPONSE
|