puma 3.8.2 → 4.3.12
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 +305 -0
- data/LICENSE +0 -0
- data/README.md +162 -224
- data/bin/puma-wild +0 -0
- 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/nginx.md +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/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +21 -0
- data/ext/puma_http11/http11_parser.c +134 -144
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +12 -10
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/io_buffer.c +0 -0
- data/ext/puma_http11/mini_ssl.c +165 -34
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +85 -101
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +42 -26
- data/lib/puma/binder.rb +57 -74
- data/lib/puma/cli.rb +26 -7
- data/lib/puma/client.rb +307 -191
- 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 +41 -20
- 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 -2
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +125 -61
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/minissl.rb +85 -28
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +2 -1
- data/lib/puma/plugin.rb +7 -2
- 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 +27 -6
- data/lib/puma/server.rb +212 -68
- 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 +67 -36
- data/lib/puma/util.rb +2 -6
- data/lib/puma.rb +16 -0
- data/lib/rack/handler/puma.rb +16 -5
- 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/jungle/upstart/README.md +0 -0
- data/tools/jungle/upstart/puma-manager.conf +0 -0
- data/tools/jungle/upstart/puma.conf +0 -0
- data/tools/trickletest.rb +1 -2
- metadata +32 -93
- 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/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 -52
data/lib/puma/jruby_restart.rb
CHANGED
data/lib/puma/launcher.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'puma/events'
|
2
4
|
require 'puma/detect'
|
3
|
-
|
4
5
|
require 'puma/cluster'
|
5
6
|
require 'puma/single'
|
6
|
-
|
7
7
|
require 'puma/const'
|
8
|
-
|
9
8
|
require 'puma/binder'
|
10
9
|
|
11
10
|
module Puma
|
@@ -40,7 +39,7 @@ module Puma
|
|
40
39
|
# [200, {}, ["hello world"]]
|
41
40
|
# end
|
42
41
|
# end
|
43
|
-
# Puma::Launcher.new(conf,
|
42
|
+
# Puma::Launcher.new(conf, events: Puma::Events.stdio).run
|
44
43
|
def initialize(conf, launcher_args={})
|
45
44
|
@runner = nil
|
46
45
|
@events = launcher_args[:events] || Events::DEFAULT
|
@@ -61,10 +60,13 @@ module Puma
|
|
61
60
|
@options = @config.options
|
62
61
|
@config.clamp
|
63
62
|
|
63
|
+
@events.formatter = Events::PidFormatter.new if clustered?
|
64
|
+
@events.formatter = options[:log_formatter] if @options[:log_formatter]
|
65
|
+
|
64
66
|
generate_restart_data
|
65
67
|
|
66
|
-
if clustered? &&
|
67
|
-
unsupported
|
68
|
+
if clustered? && !Process.respond_to?(:fork)
|
69
|
+
unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
|
68
70
|
end
|
69
71
|
|
70
72
|
if @options[:daemon] && Puma.windows?
|
@@ -79,13 +81,13 @@ module Puma
|
|
79
81
|
set_rack_environment
|
80
82
|
|
81
83
|
if clustered?
|
82
|
-
@events.formatter = Events::PidFormatter.new
|
83
84
|
@options[:logger] = @events
|
84
85
|
|
85
86
|
@runner = Cluster.new(self, @events)
|
86
87
|
else
|
87
88
|
@runner = Single.new(self, @events)
|
88
89
|
end
|
90
|
+
Puma.stats_object = @runner
|
89
91
|
|
90
92
|
@status = :run
|
91
93
|
end
|
@@ -121,19 +123,6 @@ module Puma
|
|
121
123
|
File.unlink(path) if path && File.exist?(path)
|
122
124
|
end
|
123
125
|
|
124
|
-
# If configured, write the pid of the current process out
|
125
|
-
# to a file.
|
126
|
-
def write_pid
|
127
|
-
path = @options[:pidfile]
|
128
|
-
return unless path
|
129
|
-
|
130
|
-
File.open(path, 'w') { |f| f.puts Process.pid }
|
131
|
-
cur = Process.pid
|
132
|
-
at_exit do
|
133
|
-
delete_pidfile if cur == Process.pid
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
126
|
# Begin async shutdown of the server
|
138
127
|
def halt
|
139
128
|
@status = :halt
|
@@ -163,6 +152,17 @@ module Puma
|
|
163
152
|
|
164
153
|
# Run the server. This blocks until the server is stopped
|
165
154
|
def run
|
155
|
+
previous_env =
|
156
|
+
if defined?(Bundler)
|
157
|
+
env = Bundler::ORIGINAL_ENV.dup
|
158
|
+
# add -rbundler/setup so we load from Gemfile when restarting
|
159
|
+
bundle = "-rbundler/setup"
|
160
|
+
env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
|
161
|
+
env
|
162
|
+
else
|
163
|
+
ENV.to_h
|
164
|
+
end
|
165
|
+
|
166
166
|
@config.clamp
|
167
167
|
|
168
168
|
@config.plugins.fire_starts self
|
@@ -178,11 +178,13 @@ module Puma
|
|
178
178
|
graceful_stop
|
179
179
|
when :restart
|
180
180
|
log "* Restarting..."
|
181
|
+
ENV.replace(previous_env)
|
181
182
|
@runner.before_restart
|
182
183
|
restart!
|
183
184
|
when :exit
|
184
185
|
# nothing
|
185
186
|
end
|
187
|
+
@binder.close_unix_paths
|
186
188
|
end
|
187
189
|
|
188
190
|
# Return which tcp port the launcher is using, if it's using TCP
|
@@ -199,8 +201,25 @@ module Puma
|
|
199
201
|
end
|
200
202
|
end
|
201
203
|
|
204
|
+
def close_binder_listeners
|
205
|
+
@binder.close_listeners
|
206
|
+
end
|
207
|
+
|
202
208
|
private
|
203
209
|
|
210
|
+
# If configured, write the pid of the current process out
|
211
|
+
# to a file.
|
212
|
+
def write_pid
|
213
|
+
path = @options[:pidfile]
|
214
|
+
return unless path
|
215
|
+
|
216
|
+
File.open(path, 'w') { |f| f.puts Process.pid }
|
217
|
+
cur = Process.pid
|
218
|
+
at_exit do
|
219
|
+
delete_pidfile if cur == Process.pid
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
204
223
|
def reload_worker_directory
|
205
224
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
206
225
|
end
|
@@ -220,48 +239,71 @@ module Puma
|
|
220
239
|
Dir.chdir(@restart_dir)
|
221
240
|
Kernel.exec(*argv)
|
222
241
|
else
|
223
|
-
redirects = {:close_others => true}
|
224
|
-
@binder.listeners.each_with_index do |(l, io), i|
|
225
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
226
|
-
redirects[io.to_i] = io.to_i
|
227
|
-
end
|
228
|
-
|
229
242
|
argv = restart_args
|
230
243
|
Dir.chdir(@restart_dir)
|
231
|
-
argv += [
|
244
|
+
argv += [@binder.redirects_for_restart]
|
232
245
|
Kernel.exec(*argv)
|
233
246
|
end
|
234
247
|
end
|
235
248
|
|
236
|
-
def
|
237
|
-
|
238
|
-
|
239
|
-
|
249
|
+
def dependencies_and_files_to_require_after_prune
|
250
|
+
puma = spec_for_gem("puma")
|
251
|
+
|
252
|
+
deps = puma.runtime_dependencies.map do |d|
|
253
|
+
"#{d.name}:#{spec_for_gem(d.name).version}"
|
254
|
+
end
|
255
|
+
|
256
|
+
[deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
|
257
|
+
end
|
258
|
+
|
259
|
+
def extra_runtime_deps_directories
|
260
|
+
Array(@options[:extra_runtime_dependencies]).map do |d_name|
|
261
|
+
if (spec = spec_for_gem(d_name))
|
262
|
+
require_paths_for_gem(spec)
|
263
|
+
else
|
264
|
+
log "* Could not load extra dependency: #{d_name}"
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
end.flatten.compact
|
268
|
+
end
|
269
|
+
|
270
|
+
def puma_wild_location
|
271
|
+
puma = spec_for_gem("puma")
|
272
|
+
dirs = require_paths_for_gem(puma)
|
240
273
|
puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
274
|
+
File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
|
275
|
+
end
|
241
276
|
|
242
|
-
|
277
|
+
def prune_bundler
|
278
|
+
return unless defined?(Bundler)
|
279
|
+
require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
|
280
|
+
unless puma_wild_location
|
243
281
|
log "! Unable to prune Bundler environment, continuing"
|
244
282
|
return
|
245
283
|
end
|
246
284
|
|
247
|
-
deps =
|
248
|
-
spec = Bundler.rubygems.loaded_specs(d.name)
|
249
|
-
"#{d.name}:#{spec.version.to_s}"
|
250
|
-
end
|
285
|
+
deps, dirs = dependencies_and_files_to_require_after_prune
|
251
286
|
|
252
287
|
log '* Pruning Bundler environment'
|
253
288
|
home = ENV['GEM_HOME']
|
254
289
|
Bundler.with_clean_env do
|
255
290
|
ENV['GEM_HOME'] = home
|
256
291
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
257
|
-
|
258
|
-
args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
292
|
+
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
|
259
293
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
260
|
-
args += [{:close_others => false}]
|
294
|
+
args += [{:close_others => false}]
|
261
295
|
Kernel.exec(*args)
|
262
296
|
end
|
263
297
|
end
|
264
298
|
|
299
|
+
def spec_for_gem(gem_name)
|
300
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
301
|
+
end
|
302
|
+
|
303
|
+
def require_paths_for_gem(gem_spec)
|
304
|
+
gem_spec.full_require_paths
|
305
|
+
end
|
306
|
+
|
265
307
|
def log(str)
|
266
308
|
@events.log str
|
267
309
|
end
|
@@ -281,13 +323,28 @@ module Puma
|
|
281
323
|
log "- Goodbye!"
|
282
324
|
end
|
283
325
|
|
326
|
+
def log_thread_status
|
327
|
+
Thread.list.each do |thread|
|
328
|
+
log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
|
329
|
+
logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
|
330
|
+
logstr += " #{thread.name}" if thread.respond_to?(:name)
|
331
|
+
log logstr
|
332
|
+
|
333
|
+
if thread.backtrace
|
334
|
+
log thread.backtrace.join("\n")
|
335
|
+
else
|
336
|
+
log "<no backtrace available>"
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
284
341
|
def set_process_title
|
285
342
|
Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
|
286
343
|
end
|
287
344
|
|
288
345
|
def title
|
289
|
-
buffer
|
290
|
-
buffer
|
346
|
+
buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
|
347
|
+
buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
291
348
|
buffer
|
292
349
|
end
|
293
350
|
|
@@ -304,16 +361,6 @@ module Puma
|
|
304
361
|
@options[:prune_bundler] && clustered? && !@options[:preload_app]
|
305
362
|
end
|
306
363
|
|
307
|
-
def close_binder_listeners
|
308
|
-
@binder.listeners.each do |l, io|
|
309
|
-
io.close
|
310
|
-
uri = URI.parse(l)
|
311
|
-
next unless uri.scheme == 'unix'
|
312
|
-
File.unlink("#{uri.host}#{uri.path}")
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
|
317
364
|
def generate_restart_data
|
318
365
|
if dir = @options[:directory]
|
319
366
|
@restart_dir = dir
|
@@ -337,8 +384,6 @@ module Puma
|
|
337
384
|
|
338
385
|
@restart_dir ||= Dir.pwd
|
339
386
|
|
340
|
-
require 'rubygems'
|
341
|
-
|
342
387
|
# if $0 is a file in the current directory, then restart
|
343
388
|
# it the same, otherwise add -S on there because it was
|
344
389
|
# picked up in PATH.
|
@@ -349,9 +394,10 @@ module Puma
|
|
349
394
|
arg0 = [Gem.ruby, "-S", $0]
|
350
395
|
end
|
351
396
|
|
352
|
-
# Detect and reinject -Ilib from the command line
|
397
|
+
# Detect and reinject -Ilib from the command line, used for testing without bundler
|
398
|
+
# cruby has an expanded path, jruby has just "lib"
|
353
399
|
lib = File.expand_path "lib"
|
354
|
-
arg0[1,0] = ["-I", lib] if
|
400
|
+
arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
|
355
401
|
|
356
402
|
if defined? Puma::WILD_ARGS
|
357
403
|
@restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
|
@@ -381,12 +427,22 @@ module Puma
|
|
381
427
|
|
382
428
|
begin
|
383
429
|
Signal.trap "SIGTERM" do
|
384
|
-
|
430
|
+
graceful_stop
|
431
|
+
|
432
|
+
raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
|
385
433
|
end
|
386
434
|
rescue Exception
|
387
435
|
log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
|
388
436
|
end
|
389
437
|
|
438
|
+
begin
|
439
|
+
Signal.trap "SIGINT" do
|
440
|
+
stop
|
441
|
+
end
|
442
|
+
rescue Exception
|
443
|
+
log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
|
444
|
+
end
|
445
|
+
|
390
446
|
begin
|
391
447
|
Signal.trap "SIGHUP" do
|
392
448
|
if @runner.redirected_io?
|
@@ -399,13 +455,21 @@ module Puma
|
|
399
455
|
log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
|
400
456
|
end
|
401
457
|
|
402
|
-
|
403
|
-
Signal.trap
|
404
|
-
|
405
|
-
graceful_stop
|
406
|
-
exit
|
458
|
+
begin
|
459
|
+
Signal.trap "SIGINFO" do
|
460
|
+
log_thread_status
|
407
461
|
end
|
462
|
+
rescue Exception
|
463
|
+
# Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
|
464
|
+
# to see this constantly on Linux.
|
408
465
|
end
|
409
466
|
end
|
467
|
+
|
468
|
+
def require_rubygems_min_version!(min_version, feature)
|
469
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
470
|
+
|
471
|
+
raise "#{feature} is not supported on your version of RubyGems. " \
|
472
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
473
|
+
end
|
410
474
|
end
|
411
475
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Puma
|
2
|
+
module MiniSSL
|
3
|
+
class ContextBuilder
|
4
|
+
def initialize(params, events)
|
5
|
+
require 'puma/minissl'
|
6
|
+
MiniSSL.check
|
7
|
+
|
8
|
+
@params = params
|
9
|
+
@events = events
|
10
|
+
end
|
11
|
+
|
12
|
+
def context
|
13
|
+
ctx = MiniSSL::Context.new
|
14
|
+
|
15
|
+
if defined?(JRUBY_VERSION)
|
16
|
+
unless params['keystore']
|
17
|
+
events.error "Please specify the Java keystore via 'keystore='"
|
18
|
+
end
|
19
|
+
|
20
|
+
ctx.keystore = params['keystore']
|
21
|
+
|
22
|
+
unless params['keystore-pass']
|
23
|
+
events.error "Please specify the Java keystore password via 'keystore-pass='"
|
24
|
+
end
|
25
|
+
|
26
|
+
ctx.keystore_pass = params['keystore-pass']
|
27
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
28
|
+
else
|
29
|
+
unless params['key']
|
30
|
+
events.error "Please specify the SSL key via 'key='"
|
31
|
+
end
|
32
|
+
|
33
|
+
ctx.key = params['key']
|
34
|
+
|
35
|
+
unless params['cert']
|
36
|
+
events.error "Please specify the SSL cert via 'cert='"
|
37
|
+
end
|
38
|
+
|
39
|
+
ctx.cert = params['cert']
|
40
|
+
|
41
|
+
if ['peer', 'force_peer'].include?(params['verify_mode'])
|
42
|
+
unless params['ca']
|
43
|
+
events.error "Please specify the SSL ca via 'ca='"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ctx.ca = params['ca'] if params['ca']
|
48
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
49
|
+
end
|
50
|
+
|
51
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
52
|
+
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
53
|
+
|
54
|
+
if params['verify_mode']
|
55
|
+
ctx.verify_mode = case params['verify_mode']
|
56
|
+
when "peer"
|
57
|
+
MiniSSL::VERIFY_PEER
|
58
|
+
when "force_peer"
|
59
|
+
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
60
|
+
when "none"
|
61
|
+
MiniSSL::VERIFY_NONE
|
62
|
+
else
|
63
|
+
events.error "Please specify a valid verify_mode="
|
64
|
+
MiniSSL::VERIFY_NONE
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
ctx
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
attr_reader :params, :events
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/puma/minissl.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'io/wait'
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
1
8
|
module Puma
|
2
9
|
module MiniSSL
|
3
10
|
class Socket
|
@@ -11,6 +18,10 @@ module Puma
|
|
11
18
|
@socket
|
12
19
|
end
|
13
20
|
|
21
|
+
def closed?
|
22
|
+
@socket.closed?
|
23
|
+
end
|
24
|
+
|
14
25
|
def readpartial(size)
|
15
26
|
while true
|
16
27
|
output = @engine.read
|
@@ -36,12 +47,28 @@ module Puma
|
|
36
47
|
output
|
37
48
|
end
|
38
49
|
|
39
|
-
def read_nonblock(size)
|
50
|
+
def read_nonblock(size, *_)
|
51
|
+
# *_ is to deal with keyword args that were added
|
52
|
+
# at some point (and being used in the wild)
|
40
53
|
while true
|
41
54
|
output = engine_read_all
|
42
55
|
return output if output
|
43
56
|
|
44
|
-
data = @socket.read_nonblock(size)
|
57
|
+
data = @socket.read_nonblock(size, exception: false)
|
58
|
+
if data == :wait_readable || data == :wait_writable
|
59
|
+
# It would make more sense to let @socket.read_nonblock raise
|
60
|
+
# EAGAIN if necessary but it seems like it'll misbehave on Windows.
|
61
|
+
# I don't have a Windows machine to debug this so I can't explain
|
62
|
+
# exactly whats happening in that OS. Please let me know if you
|
63
|
+
# find out!
|
64
|
+
#
|
65
|
+
# In the meantime, we can emulate the correct behavior by
|
66
|
+
# capturing :wait_readable & :wait_writable and raising EAGAIN
|
67
|
+
# ourselves.
|
68
|
+
raise IO::EAGAINWaitReadable
|
69
|
+
elsif data.nil?
|
70
|
+
return nil
|
71
|
+
end
|
45
72
|
|
46
73
|
@engine.inject(data)
|
47
74
|
output = engine_read_all
|
@@ -55,6 +82,8 @@ module Puma
|
|
55
82
|
end
|
56
83
|
|
57
84
|
def write(data)
|
85
|
+
return 0 if data.empty?
|
86
|
+
|
58
87
|
need = data.bytesize
|
59
88
|
|
60
89
|
while true
|
@@ -77,39 +106,46 @@ module Puma
|
|
77
106
|
alias_method :syswrite, :write
|
78
107
|
alias_method :<<, :write
|
79
108
|
|
109
|
+
# This is a temporary fix to deal with websockets code using
|
110
|
+
# write_nonblock. The problem with implementing it properly
|
111
|
+
# is that it means we'd have to have the ability to rewind
|
112
|
+
# an engine because after we write+extract, the socket
|
113
|
+
# write_nonblock call might raise an exception and later
|
114
|
+
# code would pass the same data in, but the engine would think
|
115
|
+
# it had already written the data in. So for the time being
|
116
|
+
# (and since write blocking is quite rare), go ahead and actually
|
117
|
+
# block in write_nonblock.
|
118
|
+
def write_nonblock(data, *_)
|
119
|
+
write data
|
120
|
+
end
|
121
|
+
|
80
122
|
def flush
|
81
123
|
@socket.flush
|
82
124
|
end
|
83
125
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
return unless IO.select([@socket], nil, nil, 1)
|
93
|
-
begin
|
94
|
-
read_nonblock(1024)
|
95
|
-
rescue Errno::EAGAIN
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
done = @engine.shutdown
|
100
|
-
|
101
|
-
while true
|
102
|
-
enc = @engine.extract
|
103
|
-
@socket.write enc
|
104
|
-
|
105
|
-
notify = @socket.sysread(1024)
|
126
|
+
def read_and_drop(timeout = 1)
|
127
|
+
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
128
|
+
return :eof unless read_nonblock(1024)
|
129
|
+
:drop
|
130
|
+
rescue Errno::EAGAIN
|
131
|
+
# do nothing
|
132
|
+
:eagain
|
133
|
+
end
|
106
134
|
|
107
|
-
|
108
|
-
|
135
|
+
def should_drop_bytes?
|
136
|
+
@engine.init? || !@engine.shutdown
|
137
|
+
end
|
109
138
|
|
110
|
-
|
139
|
+
def close
|
140
|
+
begin
|
141
|
+
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
142
|
+
# Don't let this socket hold this loop forever.
|
143
|
+
# If it can't send more packets within 1s, then give up.
|
144
|
+
while should_drop_bytes?
|
145
|
+
return if [:timeout, :eof].include?(read_and_drop(1))
|
111
146
|
end
|
112
147
|
rescue IOError, SystemCallError
|
148
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
113
149
|
# nothing
|
114
150
|
ensure
|
115
151
|
@socket.close
|
@@ -140,11 +176,18 @@ module Puma
|
|
140
176
|
|
141
177
|
class Context
|
142
178
|
attr_accessor :verify_mode
|
179
|
+
attr_reader :no_tlsv1, :no_tlsv1_1
|
180
|
+
|
181
|
+
def initialize
|
182
|
+
@no_tlsv1 = false
|
183
|
+
@no_tlsv1_1 = false
|
184
|
+
end
|
143
185
|
|
144
186
|
if defined?(JRUBY_VERSION)
|
145
187
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
146
188
|
attr_reader :keystore
|
147
189
|
attr_accessor :keystore_pass
|
190
|
+
attr_accessor :ssl_cipher_list
|
148
191
|
|
149
192
|
def keystore=(keystore)
|
150
193
|
raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
|
@@ -160,6 +203,7 @@ module Puma
|
|
160
203
|
attr_reader :key
|
161
204
|
attr_reader :cert
|
162
205
|
attr_reader :ca
|
206
|
+
attr_accessor :ssl_cipher_filter
|
163
207
|
|
164
208
|
def key=(key)
|
165
209
|
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
@@ -181,6 +225,19 @@ module Puma
|
|
181
225
|
raise "Cert not configured" unless @cert
|
182
226
|
end
|
183
227
|
end
|
228
|
+
|
229
|
+
# disables TLSv1
|
230
|
+
def no_tlsv1=(tlsv1)
|
231
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
232
|
+
@no_tlsv1 = tlsv1
|
233
|
+
end
|
234
|
+
|
235
|
+
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
236
|
+
def no_tlsv1_1=(tlsv1_1)
|
237
|
+
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
|
+
@no_tlsv1_1 = tlsv1_1
|
239
|
+
end
|
240
|
+
|
184
241
|
end
|
185
242
|
|
186
243
|
VERIFY_NONE = 0
|
@@ -214,7 +271,7 @@ module Puma
|
|
214
271
|
end
|
215
272
|
|
216
273
|
def close
|
217
|
-
@socket.close
|
274
|
+
@socket.close unless @socket.closed? # closed? call is for Windows
|
218
275
|
end
|
219
276
|
end
|
220
277
|
end
|
data/lib/puma/null_io.rb
CHANGED
data/lib/puma/plugin.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
class UnknownPlugin < RuntimeError; end
|
3
5
|
|
@@ -60,8 +62,11 @@ module Puma
|
|
60
62
|
end
|
61
63
|
|
62
64
|
def fire_background
|
63
|
-
@background.
|
64
|
-
Thread.new
|
65
|
+
@background.each_with_index do |b, i|
|
66
|
+
Thread.new do
|
67
|
+
Puma.set_thread_name "plugin background #{i}"
|
68
|
+
b.call
|
69
|
+
end
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
data/lib/puma/rack/builder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Puma
|
2
4
|
end
|
3
5
|
|
@@ -110,7 +112,8 @@ module Puma::Rack
|
|
110
112
|
|
111
113
|
has_options = false
|
112
114
|
server.valid_options.each do |name, description|
|
113
|
-
next if name.to_s
|
115
|
+
next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
|
116
|
+
|
114
117
|
info << " -O %-21s %s" % [name, description]
|
115
118
|
has_options = true
|
116
119
|
end
|
data/lib/puma/rack/urlmap.rb
CHANGED