puma 3.9.1 → 4.3.1
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 +232 -0
- data/README.md +162 -224
- 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 +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +130 -37
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +13 -0
- data/ext/puma_http11/http11_parser.c +115 -140
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +104 -8
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +90 -108
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +21 -4
- data/ext/puma_http11/puma_http11.c +2 -0
- data/lib/puma.rb +16 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +40 -26
- data/lib/puma/binder.rb +57 -74
- data/lib/puma/cli.rb +26 -7
- data/lib/puma/client.rb +243 -190
- data/lib/puma/cluster.rb +78 -34
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +24 -16
- data/lib/puma/const.rb +36 -18
- data/lib/puma/control_cli.rb +46 -19
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +329 -68
- data/lib/puma/events.rb +6 -1
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +120 -58
- data/lib/puma/minissl.rb +69 -27
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +7 -2
- data/lib/puma/plugin/tmp_restart.rb +2 -1
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +224 -34
- data/lib/puma/runner.rb +25 -4
- data/lib/puma/server.rb +148 -62
- 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 +61 -38
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +10 -4
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +8 -8
- 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 -2
- metadata +29 -56
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -14
- data/Manifest.txt +0 -78
- data/Rakefile +0 -165
- data/Release.md +0 -9
- data/gemfiles/2.1-Gemfile +0 -12
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/puma.gemspec +0 -20
@@ -200,6 +200,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
|
|
200
200
|
f = rb_str_new(hp->buf, new_size);
|
201
201
|
}
|
202
202
|
|
203
|
+
while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
|
204
|
+
|
203
205
|
/* check for duplicate header */
|
204
206
|
v = rb_hash_aref(hp->request, f);
|
205
207
|
|
data/lib/puma.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Standard libraries
|
2
4
|
require 'socket'
|
3
5
|
require 'tempfile'
|
@@ -12,4 +14,18 @@ module Puma
|
|
12
14
|
autoload :Const, 'puma/const'
|
13
15
|
autoload :Server, 'puma/server'
|
14
16
|
autoload :Launcher, 'puma/launcher'
|
17
|
+
|
18
|
+
def self.stats_object=(val)
|
19
|
+
@get_stats = val
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stats
|
23
|
+
@get_stats.stats
|
24
|
+
end
|
25
|
+
|
26
|
+
# Thread name is new in Ruby 2.3
|
27
|
+
def self.set_thread_name(name)
|
28
|
+
return unless Thread.current.respond_to?(:name=)
|
29
|
+
Thread.current.name = "puma #{name}"
|
30
|
+
end
|
15
31
|
end
|
data/lib/puma/accept_nonblock.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'openssl'
|
2
4
|
|
3
5
|
module OpenSSL
|
@@ -13,7 +15,11 @@ module OpenSSL
|
|
13
15
|
ssl.accept if @start_immediately
|
14
16
|
ssl
|
15
17
|
rescue SSLError => ex
|
16
|
-
|
18
|
+
if ssl
|
19
|
+
ssl.close
|
20
|
+
else
|
21
|
+
sock.close
|
22
|
+
end
|
17
23
|
raise ex
|
18
24
|
end
|
19
25
|
end
|
data/lib/puma/app/status.rb
CHANGED
@@ -1,26 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
1
5
|
module Puma
|
2
6
|
module App
|
7
|
+
# Check out {#call}'s source code to see what actions this web application
|
8
|
+
# can respond to.
|
3
9
|
class Status
|
4
|
-
def initialize(cli)
|
5
|
-
@cli = cli
|
6
|
-
@auth_token = nil
|
7
|
-
end
|
8
10
|
OK_STATUS = '{ "status": "ok" }'.freeze
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
return true unless @auth_token
|
14
|
-
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
15
|
-
end
|
16
|
-
|
17
|
-
def rack_response(status, body, content_type='application/json')
|
18
|
-
headers = {
|
19
|
-
'Content-Type' => content_type,
|
20
|
-
'Content-Length' => body.bytesize.to_s
|
21
|
-
}
|
22
|
-
|
23
|
-
[status, headers, [body]]
|
12
|
+
def initialize(cli, token = nil)
|
13
|
+
@cli = cli
|
14
|
+
@auth_token = token
|
24
15
|
end
|
25
16
|
|
26
17
|
def call(env)
|
@@ -31,36 +22,59 @@ module Puma
|
|
31
22
|
case env['PATH_INFO']
|
32
23
|
when /\/stop$/
|
33
24
|
@cli.stop
|
34
|
-
|
25
|
+
rack_response(200, OK_STATUS)
|
35
26
|
|
36
27
|
when /\/halt$/
|
37
28
|
@cli.halt
|
38
|
-
|
29
|
+
rack_response(200, OK_STATUS)
|
39
30
|
|
40
31
|
when /\/restart$/
|
41
32
|
@cli.restart
|
42
|
-
|
33
|
+
rack_response(200, OK_STATUS)
|
43
34
|
|
44
35
|
when /\/phased-restart$/
|
45
36
|
if !@cli.phased_restart
|
46
|
-
|
37
|
+
rack_response(404, '{ "error": "phased restart not available" }')
|
47
38
|
else
|
48
|
-
|
39
|
+
rack_response(200, OK_STATUS)
|
49
40
|
end
|
50
41
|
|
51
42
|
when /\/reload-worker-directory$/
|
52
43
|
if !@cli.send(:reload_worker_directory)
|
53
|
-
|
44
|
+
rack_response(404, '{ "error": "reload_worker_directory not available" }')
|
54
45
|
else
|
55
|
-
|
46
|
+
rack_response(200, OK_STATUS)
|
56
47
|
end
|
57
48
|
|
49
|
+
when /\/gc$/
|
50
|
+
GC.start
|
51
|
+
rack_response(200, OK_STATUS)
|
52
|
+
|
53
|
+
when /\/gc-stats$/
|
54
|
+
rack_response(200, GC.stat.to_json)
|
55
|
+
|
58
56
|
when /\/stats$/
|
59
|
-
|
57
|
+
rack_response(200, @cli.stats)
|
60
58
|
else
|
61
59
|
rack_response 404, "Unsupported action", 'text/plain'
|
62
60
|
end
|
63
61
|
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def authenticate(env)
|
66
|
+
return true unless @auth_token
|
67
|
+
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def rack_response(status, body, content_type='application/json')
|
71
|
+
headers = {
|
72
|
+
'Content-Type' => content_type,
|
73
|
+
'Content-Length' => body.bytesize.to_s
|
74
|
+
}
|
75
|
+
|
76
|
+
[status, headers, [body]]
|
77
|
+
end
|
64
78
|
end
|
65
79
|
end
|
66
80
|
end
|
data/lib/puma/binder.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'socket'
|
3
5
|
|
4
6
|
require 'puma/const'
|
5
7
|
require 'puma/util'
|
8
|
+
require 'puma/minissl/context_builder'
|
6
9
|
|
7
10
|
module Puma
|
8
11
|
class Binder
|
@@ -40,7 +43,7 @@ module Puma
|
|
40
43
|
@ios = []
|
41
44
|
end
|
42
45
|
|
43
|
-
attr_reader :
|
46
|
+
attr_reader :ios
|
44
47
|
|
45
48
|
def env(sock)
|
46
49
|
@envs.fetch(sock, @proto_env)
|
@@ -48,7 +51,6 @@ module Puma
|
|
48
51
|
|
49
52
|
def close
|
50
53
|
@ios.each { |i| i.close }
|
51
|
-
@unix_paths.each { |i| File.unlink i }
|
52
54
|
end
|
53
55
|
|
54
56
|
def import_from_env
|
@@ -90,19 +92,29 @@ module Puma
|
|
90
92
|
case uri.scheme
|
91
93
|
when "tcp"
|
92
94
|
if fd = @inherited_fds.delete(str)
|
93
|
-
logger.log "* Inherited #{str}"
|
94
95
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
96
|
+
logger.log "* Inherited #{str}"
|
95
97
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
96
|
-
logger.log "* Activated #{str}"
|
97
98
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
99
|
+
logger.log "* Activated #{str}"
|
98
100
|
else
|
99
101
|
params = Util.parse_query uri.query
|
100
102
|
|
101
103
|
opt = params.key?('low_latency')
|
102
104
|
bak = params.fetch('backlog', 1024).to_i
|
103
105
|
|
104
|
-
logger.log "* Listening on #{str}"
|
105
106
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
107
|
+
|
108
|
+
@ios.each do |i|
|
109
|
+
next unless TCPServer === i
|
110
|
+
addr = if i.local_address.ipv6?
|
111
|
+
"[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
|
112
|
+
else
|
113
|
+
i.local_address.ip_unpack.join(':')
|
114
|
+
end
|
115
|
+
|
116
|
+
logger.log "* Listening on tcp://#{addr}"
|
117
|
+
end
|
106
118
|
end
|
107
119
|
|
108
120
|
@listeners << [str, io] if io
|
@@ -110,17 +122,15 @@ module Puma
|
|
110
122
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
111
123
|
|
112
124
|
if fd = @inherited_fds.delete(str)
|
113
|
-
logger.log "* Inherited #{str}"
|
114
125
|
io = inherit_unix_listener path, fd
|
126
|
+
logger.log "* Inherited #{str}"
|
115
127
|
elsif sock = @activated_sockets.delete([ :unix, path ])
|
116
|
-
logger.log "* Activated #{str}"
|
117
128
|
io = inherit_unix_listener path, sock
|
129
|
+
logger.log "* Activated #{str}"
|
118
130
|
else
|
119
|
-
logger.log "* Listening on #{str}"
|
120
|
-
|
121
131
|
umask = nil
|
122
132
|
mode = nil
|
123
|
-
backlog =
|
133
|
+
backlog = 1024
|
124
134
|
|
125
135
|
if uri.query
|
126
136
|
params = Util.parse_query uri.query
|
@@ -139,74 +149,23 @@ module Puma
|
|
139
149
|
end
|
140
150
|
|
141
151
|
io = add_unix_listener path, umask, mode, backlog
|
152
|
+
logger.log "* Listening on #{str}"
|
142
153
|
end
|
143
154
|
|
144
155
|
@listeners << [str, io]
|
145
156
|
when "ssl"
|
146
157
|
params = Util.parse_query uri.query
|
147
|
-
|
148
|
-
|
149
|
-
MiniSSL.check
|
150
|
-
|
151
|
-
ctx = MiniSSL::Context.new
|
152
|
-
|
153
|
-
if defined?(JRUBY_VERSION)
|
154
|
-
unless params['keystore']
|
155
|
-
@events.error "Please specify the Java keystore via 'keystore='"
|
156
|
-
end
|
157
|
-
|
158
|
-
ctx.keystore = params['keystore']
|
159
|
-
|
160
|
-
unless params['keystore-pass']
|
161
|
-
@events.error "Please specify the Java keystore password via 'keystore-pass='"
|
162
|
-
end
|
163
|
-
|
164
|
-
ctx.keystore_pass = params['keystore-pass']
|
165
|
-
else
|
166
|
-
unless params['key']
|
167
|
-
@events.error "Please specify the SSL key via 'key='"
|
168
|
-
end
|
169
|
-
|
170
|
-
ctx.key = params['key']
|
171
|
-
|
172
|
-
unless params['cert']
|
173
|
-
@events.error "Please specify the SSL cert via 'cert='"
|
174
|
-
end
|
175
|
-
|
176
|
-
ctx.cert = params['cert']
|
177
|
-
|
178
|
-
if ['peer', 'force_peer'].include?(params['verify_mode'])
|
179
|
-
unless params['ca']
|
180
|
-
@events.error "Please specify the SSL ca via 'ca='"
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
ctx.ca = params['ca'] if params['ca']
|
185
|
-
end
|
186
|
-
|
187
|
-
if params['verify_mode']
|
188
|
-
ctx.verify_mode = case params['verify_mode']
|
189
|
-
when "peer"
|
190
|
-
MiniSSL::VERIFY_PEER
|
191
|
-
when "force_peer"
|
192
|
-
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
193
|
-
when "none"
|
194
|
-
MiniSSL::VERIFY_NONE
|
195
|
-
else
|
196
|
-
@events.error "Please specify a valid verify_mode="
|
197
|
-
MiniSSL::VERIFY_NONE
|
198
|
-
end
|
199
|
-
end
|
158
|
+
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
200
159
|
|
201
160
|
if fd = @inherited_fds.delete(str)
|
202
161
|
logger.log "* Inherited #{str}"
|
203
162
|
io = inherit_ssl_listener fd, ctx
|
204
163
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
205
|
-
logger.log "* Activated #{str}"
|
206
164
|
io = inherit_ssl_listener sock, ctx
|
165
|
+
logger.log "* Activated #{str}"
|
207
166
|
else
|
208
|
-
logger.log "* Listening on #{str}"
|
209
167
|
io = add_ssl_listener uri.host, uri.port, ctx
|
168
|
+
logger.log "* Listening on #{str}"
|
210
169
|
end
|
211
170
|
|
212
171
|
@listeners << [str, io] if io
|
@@ -245,9 +204,10 @@ module Puma
|
|
245
204
|
end
|
246
205
|
end
|
247
206
|
|
248
|
-
def
|
249
|
-
|
250
|
-
|
207
|
+
def loopback_addresses
|
208
|
+
Socket.ip_address_list.select do |addrinfo|
|
209
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
210
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
251
211
|
end
|
252
212
|
|
253
213
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -259,7 +219,7 @@ module Puma
|
|
259
219
|
#
|
260
220
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
261
221
|
if host == "localhost"
|
262
|
-
|
222
|
+
loopback_addresses.each do |addr|
|
263
223
|
add_tcp_listener addr, port, optimize_for_latency, backlog
|
264
224
|
end
|
265
225
|
return
|
@@ -298,7 +258,7 @@ module Puma
|
|
298
258
|
MiniSSL.check
|
299
259
|
|
300
260
|
if host == "localhost"
|
301
|
-
|
261
|
+
loopback_addresses.each do |addr|
|
302
262
|
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
|
303
263
|
end
|
304
264
|
return
|
@@ -312,6 +272,7 @@ module Puma
|
|
312
272
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
313
273
|
s.listen backlog
|
314
274
|
|
275
|
+
|
315
276
|
ssl = MiniSSL::Server.new s, ctx
|
316
277
|
env = @proto_env.dup
|
317
278
|
env[HTTPS_KEY] = HTTPS
|
@@ -343,8 +304,8 @@ module Puma
|
|
343
304
|
|
344
305
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
345
306
|
#
|
346
|
-
def add_unix_listener(path, umask=nil, mode=nil, backlog=
|
347
|
-
@unix_paths << path
|
307
|
+
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
308
|
+
@unix_paths << path unless File.exist? path
|
348
309
|
|
349
310
|
# Let anyone connect by default
|
350
311
|
umask ||= 0
|
@@ -364,7 +325,7 @@ module Puma
|
|
364
325
|
end
|
365
326
|
|
366
327
|
s = UNIXServer.new(path)
|
367
|
-
s.listen backlog
|
328
|
+
s.listen backlog
|
368
329
|
@ios << s
|
369
330
|
ensure
|
370
331
|
File.umask old_mask
|
@@ -382,7 +343,7 @@ module Puma
|
|
382
343
|
end
|
383
344
|
|
384
345
|
def inherit_unix_listener(path, fd)
|
385
|
-
@unix_paths << path
|
346
|
+
@unix_paths << path unless File.exist? path
|
386
347
|
|
387
348
|
if fd.kind_of? TCPServer
|
388
349
|
s = fd
|
@@ -398,5 +359,27 @@ module Puma
|
|
398
359
|
s
|
399
360
|
end
|
400
361
|
|
362
|
+
def close_listeners
|
363
|
+
@listeners.each do |l, io|
|
364
|
+
io.close
|
365
|
+
uri = URI.parse(l)
|
366
|
+
next unless uri.scheme == 'unix'
|
367
|
+
unix_path = "#{uri.host}#{uri.path}"
|
368
|
+
File.unlink unix_path if @unix_paths.include? unix_path
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def close_unix_paths
|
373
|
+
@unix_paths.each { |up| File.unlink(up) if File.exist? up }
|
374
|
+
end
|
375
|
+
|
376
|
+
def redirects_for_restart
|
377
|
+
redirects = {:close_others => true}
|
378
|
+
@listeners.each_with_index do |(l, io), i|
|
379
|
+
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
380
|
+
redirects[io.to_i] = io.to_i
|
381
|
+
end
|
382
|
+
redirects
|
383
|
+
end
|
401
384
|
end
|
402
385
|
end
|
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",
|
@@ -150,6 +161,10 @@ module Puma
|
|
150
161
|
user_config.prune_bundler
|
151
162
|
end
|
152
163
|
|
164
|
+
o.on "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
|
165
|
+
user_config.extra_runtime_dependencies arg.split(',')
|
166
|
+
end
|
167
|
+
|
153
168
|
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
154
169
|
user_config.quiet
|
155
170
|
end
|
@@ -181,6 +196,10 @@ module Puma
|
|
181
196
|
user_config.tcp_mode!
|
182
197
|
end
|
183
198
|
|
199
|
+
o.on "--early-hints", "Enable early hints support" do
|
200
|
+
user_config.early_hints
|
201
|
+
end
|
202
|
+
|
184
203
|
o.on "-V", "--version", "Print the version information" do
|
185
204
|
puts "puma version #{Puma::Const::VERSION}"
|
186
205
|
exit 0
|