puma 4.3.12 → 6.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 +4 -4
- data/History.md +1729 -521
- data/LICENSE +23 -20
- data/README.md +169 -45
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +31 -0
- 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/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +84 -128
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +49 -12
- data/ext/puma_http11/http11_parser.c +46 -48
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +3 -3
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +278 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
- data/ext/puma_http11/puma_http11.c +46 -57
- data/lib/puma/app/status.rb +53 -39
- data/lib/puma/binder.rb +237 -121
- data/lib/puma/cli.rb +34 -34
- data/lib/puma/client.rb +172 -98
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +226 -231
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +114 -87
- data/lib/puma/const.rb +139 -95
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +516 -110
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +44 -2
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +164 -155
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +36 -19
- data/lib/puma/minissl.rb +230 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +7 -11
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +93 -368
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +92 -75
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +321 -794
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +140 -68
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -7
- data/lib/rack/handler/puma.rb +113 -87
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +33 -24
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/accept_nonblock.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'const'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
# The implementation of a detailed error logging.
|
7
|
+
# @version 5.0.0
|
8
|
+
#
|
9
|
+
class ErrorLogger
|
10
|
+
include Const
|
11
|
+
|
12
|
+
attr_reader :ioerr
|
13
|
+
|
14
|
+
REQUEST_FORMAT = %{"%s %s%s" - (%s)}
|
15
|
+
|
16
|
+
LOG_QUEUE = Queue.new
|
17
|
+
|
18
|
+
def initialize(ioerr)
|
19
|
+
@ioerr = ioerr
|
20
|
+
|
21
|
+
@debug = ENV.key? 'PUMA_DEBUG'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.stdio
|
25
|
+
new $stderr
|
26
|
+
end
|
27
|
+
|
28
|
+
# Print occurred error details.
|
29
|
+
# +options+ hash with additional options:
|
30
|
+
# - +error+ is an exception object
|
31
|
+
# - +req+ the http request
|
32
|
+
# - +text+ (default nil) custom string to print in title
|
33
|
+
# and before all remaining info.
|
34
|
+
#
|
35
|
+
def info(options={})
|
36
|
+
internal_write title(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Print occurred error details only if
|
40
|
+
# environment variable PUMA_DEBUG is defined.
|
41
|
+
# +options+ hash with additional options:
|
42
|
+
# - +error+ is an exception object
|
43
|
+
# - +req+ the http request
|
44
|
+
# - +text+ (default nil) custom string to print in title
|
45
|
+
# and before all remaining info.
|
46
|
+
#
|
47
|
+
def debug(options={})
|
48
|
+
return unless @debug
|
49
|
+
|
50
|
+
error = options[:error]
|
51
|
+
req = options[:req]
|
52
|
+
|
53
|
+
string_block = []
|
54
|
+
string_block << title(options)
|
55
|
+
string_block << request_dump(req) if request_parsed?(req)
|
56
|
+
string_block << error.backtrace if error
|
57
|
+
|
58
|
+
internal_write string_block.join("\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
def title(options={})
|
62
|
+
text = options[:text]
|
63
|
+
req = options[:req]
|
64
|
+
error = options[:error]
|
65
|
+
|
66
|
+
string_block = ["#{Time.now}"]
|
67
|
+
string_block << " #{text}" if text
|
68
|
+
string_block << " (#{request_title(req)})" if request_parsed?(req)
|
69
|
+
string_block << ": #{error.inspect}" if error
|
70
|
+
string_block.join('')
|
71
|
+
end
|
72
|
+
|
73
|
+
def request_dump(req)
|
74
|
+
"Headers: #{request_headers(req)}\n" \
|
75
|
+
"Body: #{req.body}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def request_title(req)
|
79
|
+
env = req.env
|
80
|
+
|
81
|
+
REQUEST_FORMAT % [
|
82
|
+
env[REQUEST_METHOD],
|
83
|
+
env[REQUEST_PATH] || env[PATH_INFO],
|
84
|
+
env[QUERY_STRING] || "",
|
85
|
+
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
|
86
|
+
]
|
87
|
+
end
|
88
|
+
|
89
|
+
def request_headers(req)
|
90
|
+
headers = req.env.select { |key, _| key.start_with?('HTTP_') }
|
91
|
+
headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
|
92
|
+
end
|
93
|
+
|
94
|
+
def request_parsed?(req)
|
95
|
+
req && req.env[REQUEST_METHOD]
|
96
|
+
end
|
97
|
+
|
98
|
+
def internal_write(str)
|
99
|
+
LOG_QUEUE << str
|
100
|
+
while (w_str = LOG_QUEUE.pop(true)) do
|
101
|
+
begin
|
102
|
+
@ioerr.is_a?(IO) and @ioerr.wait_writable(1)
|
103
|
+
@ioerr.write "#{w_str}\n"
|
104
|
+
@ioerr.flush unless @ioerr.sync
|
105
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
|
106
|
+
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
|
107
|
+
end
|
108
|
+
end
|
109
|
+
rescue ThreadError
|
110
|
+
end
|
111
|
+
private :internal_write
|
112
|
+
end
|
113
|
+
end
|
data/lib/puma/events.rb
CHANGED
@@ -1,56 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'puma/const'
|
4
|
-
require "puma/null_io"
|
5
|
-
require 'stringio'
|
6
|
-
|
7
3
|
module Puma
|
8
|
-
# The default implement of an event sink object used by Server
|
9
|
-
# for when certain kinds of events occur in the life of the server.
|
10
|
-
#
|
11
|
-
# The methods available are the events that the Server fires.
|
12
|
-
#
|
13
|
-
class Events
|
14
|
-
class DefaultFormatter
|
15
|
-
def call(str)
|
16
|
-
str
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class PidFormatter
|
21
|
-
def call(str)
|
22
|
-
"[#{$$}] #{str}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
include Const
|
27
4
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@stderr = stderr
|
34
|
-
|
35
|
-
@stdout.sync = true
|
36
|
-
@stderr.sync = true
|
37
|
-
|
38
|
-
@debug = ENV.key? 'PUMA_DEBUG'
|
5
|
+
# This is an event sink used by `Puma::Server` to handle
|
6
|
+
# lifecycle events such as :on_booted, :on_restart, and :on_stopped.
|
7
|
+
# Using `Puma::DSL` it is possible to register callback hooks
|
8
|
+
# for each event type.
|
9
|
+
class Events
|
39
10
|
|
11
|
+
def initialize
|
40
12
|
@hooks = Hash.new { |h,k| h[k] = [] }
|
41
13
|
end
|
42
14
|
|
43
|
-
attr_reader :stdout, :stderr
|
44
|
-
attr_accessor :formatter
|
45
|
-
|
46
15
|
# Fire callbacks for the named hook
|
47
|
-
#
|
48
16
|
def fire(hook, *args)
|
49
17
|
@hooks[hook].each { |t| t.call(*args) }
|
50
18
|
end
|
51
19
|
|
52
20
|
# Register a callback for a given hook
|
53
|
-
#
|
54
21
|
def register(hook, obj=nil, &blk)
|
55
22
|
if obj and blk
|
56
23
|
raise "Specify either an object or a block, not both"
|
@@ -63,94 +30,28 @@ module Puma
|
|
63
30
|
h
|
64
31
|
end
|
65
32
|
|
66
|
-
|
67
|
-
|
68
|
-
def log(str)
|
69
|
-
@stdout.puts format(str)
|
70
|
-
end
|
71
|
-
|
72
|
-
def write(str)
|
73
|
-
@stdout.write format(str)
|
74
|
-
end
|
75
|
-
|
76
|
-
def debug(str)
|
77
|
-
log("% #{str}") if @debug
|
78
|
-
end
|
79
|
-
|
80
|
-
# Write +str+ to +@stderr+
|
81
|
-
#
|
82
|
-
def error(str)
|
83
|
-
@stderr.puts format("ERROR: #{str}")
|
84
|
-
exit 1
|
85
|
-
end
|
86
|
-
|
87
|
-
def format(str)
|
88
|
-
formatter.call(str)
|
89
|
-
end
|
90
|
-
|
91
|
-
# An HTTP parse error has occurred.
|
92
|
-
# +server+ is the Server object, +env+ the request, and +error+ a
|
93
|
-
# parsing exception.
|
94
|
-
#
|
95
|
-
def parse_error(server, env, error)
|
96
|
-
@stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
|
97
|
-
"(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
|
98
|
-
"#{error.inspect}" \
|
99
|
-
"\n---\n"
|
100
|
-
end
|
101
|
-
|
102
|
-
# An SSL error has occurred.
|
103
|
-
# +server+ is the Server object, +peeraddr+ peer address, +peercert+
|
104
|
-
# any peer certificate (if present), and +error+ an exception object.
|
105
|
-
#
|
106
|
-
def ssl_error(server, peeraddr, peercert, error)
|
107
|
-
subject = peercert ? peercert.subject : nil
|
108
|
-
@stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
|
33
|
+
def on_booted(&block)
|
34
|
+
register(:on_booted, &block)
|
109
35
|
end
|
110
36
|
|
111
|
-
|
112
|
-
|
113
|
-
# +kind+ some additional info, and +env+ the request.
|
114
|
-
#
|
115
|
-
def unknown_error(server, error, kind="Unknown", env=nil)
|
116
|
-
if error.respond_to? :render
|
117
|
-
error.render "#{Time.now}: #{kind} error", @stderr
|
118
|
-
else
|
119
|
-
if env
|
120
|
-
string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
|
121
|
-
string_block << error.inspect
|
122
|
-
else
|
123
|
-
string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
|
124
|
-
end
|
125
|
-
string_block << error.backtrace
|
126
|
-
@stderr.puts string_block.join("\n")
|
127
|
-
end
|
37
|
+
def on_restart(&block)
|
38
|
+
register(:on_restart, &block)
|
128
39
|
end
|
129
40
|
|
130
|
-
def
|
131
|
-
register(:
|
41
|
+
def on_stopped(&block)
|
42
|
+
register(:on_stopped, &block)
|
132
43
|
end
|
133
44
|
|
134
45
|
def fire_on_booted!
|
135
46
|
fire(:on_booted)
|
136
47
|
end
|
137
48
|
|
138
|
-
|
139
|
-
|
140
|
-
# Returns an Events object which writes its status to 2 StringIO
|
141
|
-
# objects.
|
142
|
-
#
|
143
|
-
def self.strings
|
144
|
-
Events.new StringIO.new, StringIO.new
|
145
|
-
end
|
146
|
-
|
147
|
-
def self.stdio
|
148
|
-
Events.new $stdout, $stderr
|
49
|
+
def fire_on_restart!
|
50
|
+
fire(:on_restart)
|
149
51
|
end
|
150
52
|
|
151
|
-
def
|
152
|
-
|
153
|
-
Events.new n, n
|
53
|
+
def fire_on_stopped!
|
54
|
+
fire(:on_stopped)
|
154
55
|
end
|
155
56
|
end
|
156
57
|
end
|
data/lib/puma/io_buffer.rb
CHANGED
@@ -1,4 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
class IOBuffer < StringIO
|
7
|
+
def initialize
|
8
|
+
super.binmode
|
9
|
+
end
|
10
|
+
|
11
|
+
def empty?
|
12
|
+
length.zero?
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
truncate 0
|
17
|
+
rewind
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
rewind
|
22
|
+
read
|
23
|
+
end
|
24
|
+
|
25
|
+
# Read & Reset - returns contents and resets
|
26
|
+
# @return [String] StringIO contents
|
27
|
+
def read_and_reset
|
28
|
+
rewind
|
29
|
+
str = read
|
30
|
+
truncate 0
|
31
|
+
rewind
|
32
|
+
str
|
33
|
+
end
|
34
|
+
|
35
|
+
alias_method :clear, :reset
|
36
|
+
|
37
|
+
# before Ruby 2.5, `write` would only take one argument
|
38
|
+
if RUBY_VERSION >= '2.5' && RUBY_ENGINE != 'truffleruby'
|
39
|
+
alias_method :append, :write
|
40
|
+
else
|
41
|
+
def append(*strs)
|
42
|
+
strs.each { |str| write str }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/puma/jruby_restart.rb
CHANGED
@@ -16,69 +16,12 @@ module Puma
|
|
16
16
|
def self.chdir_exec(dir, argv)
|
17
17
|
chdir(dir)
|
18
18
|
cmd = argv.first
|
19
|
-
argv = ([:string] * argv.size).zip(argv)
|
19
|
+
argv = ([:string] * argv.size).zip(argv)
|
20
|
+
argv.flatten!
|
20
21
|
argv << :string
|
21
22
|
argv << nil
|
22
23
|
execlp(cmd, *argv)
|
23
24
|
raise SystemCallError.new(FFI.errno)
|
24
25
|
end
|
25
|
-
|
26
|
-
PermKey = 'PUMA_DAEMON_PERM'
|
27
|
-
RestartKey = 'PUMA_DAEMON_RESTART'
|
28
|
-
|
29
|
-
# Called to tell things "Your now always in daemon mode,
|
30
|
-
# don't try to reenter it."
|
31
|
-
#
|
32
|
-
def self.perm_daemonize
|
33
|
-
ENV[PermKey] = "1"
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.daemon?
|
37
|
-
ENV.key?(PermKey) || ENV.key?(RestartKey)
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.daemon_init
|
41
|
-
return true if ENV.key?(PermKey)
|
42
|
-
|
43
|
-
return false unless ENV.key? RestartKey
|
44
|
-
|
45
|
-
master = ENV[RestartKey]
|
46
|
-
|
47
|
-
# In case the master disappears early
|
48
|
-
begin
|
49
|
-
Process.kill "SIGUSR2", master.to_i
|
50
|
-
rescue SystemCallError => e
|
51
|
-
end
|
52
|
-
|
53
|
-
ENV[RestartKey] = ""
|
54
|
-
|
55
|
-
setsid
|
56
|
-
|
57
|
-
null = File.open "/dev/null", "w+"
|
58
|
-
STDIN.reopen null
|
59
|
-
STDOUT.reopen null
|
60
|
-
STDERR.reopen null
|
61
|
-
|
62
|
-
true
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.daemon_start(dir, argv)
|
66
|
-
ENV[RestartKey] = Process.pid.to_s
|
67
|
-
|
68
|
-
if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
|
69
|
-
ENV['JRUBY_OPTS'] = k
|
70
|
-
end
|
71
|
-
|
72
|
-
cmd = argv.first
|
73
|
-
argv = ([:string] * argv.size).zip(argv).flatten
|
74
|
-
argv << :string
|
75
|
-
argv << nil
|
76
|
-
|
77
|
-
chdir(dir)
|
78
|
-
ret = fork
|
79
|
-
return ret if ret != 0
|
80
|
-
execlp(cmd, *argv)
|
81
|
-
raise SystemCallError.new(FFI.errno)
|
82
|
-
end
|
83
26
|
end
|
84
27
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Puma
|
5
|
+
|
6
|
+
# Puma deliberately avoids the use of the json gem and instead performs JSON
|
7
|
+
# serialization without any external dependencies. In a puma cluster, loading
|
8
|
+
# any gem into the puma master process means that operators cannot use a
|
9
|
+
# phased restart to upgrade their application if the new version of that
|
10
|
+
# application uses a different version of that gem. The json gem in
|
11
|
+
# particular is additionally problematic because it leverages native
|
12
|
+
# extensions. If the puma master process relies on a gem with native
|
13
|
+
# extensions and operators remove gems from disk related to old releases,
|
14
|
+
# subsequent phased restarts can fail.
|
15
|
+
#
|
16
|
+
# The implementation of JSON serialization in this module is not designed to
|
17
|
+
# be particularly full-featured or fast. It just has to handle the few places
|
18
|
+
# where Puma relies on JSON serialization internally.
|
19
|
+
|
20
|
+
module JSONSerialization
|
21
|
+
QUOTE = /"/
|
22
|
+
BACKSLASH = /\\/
|
23
|
+
CONTROL_CHAR_TO_ESCAPE = /[\x00-\x1F]/ # As required by ECMA-404
|
24
|
+
CHAR_TO_ESCAPE = Regexp.union QUOTE, BACKSLASH, CONTROL_CHAR_TO_ESCAPE
|
25
|
+
|
26
|
+
class SerializationError < StandardError; end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def generate(value)
|
30
|
+
StringIO.open do |io|
|
31
|
+
serialize_value io, value
|
32
|
+
io.string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def serialize_value(output, value)
|
39
|
+
case value
|
40
|
+
when Hash
|
41
|
+
output << '{'
|
42
|
+
value.each_with_index do |(k, v), index|
|
43
|
+
output << ',' if index != 0
|
44
|
+
serialize_object_key output, k
|
45
|
+
output << ':'
|
46
|
+
serialize_value output, v
|
47
|
+
end
|
48
|
+
output << '}'
|
49
|
+
when Array
|
50
|
+
output << '['
|
51
|
+
value.each_with_index do |member, index|
|
52
|
+
output << ',' if index != 0
|
53
|
+
serialize_value output, member
|
54
|
+
end
|
55
|
+
output << ']'
|
56
|
+
when Integer, Float
|
57
|
+
output << value.to_s
|
58
|
+
when String
|
59
|
+
serialize_string output, value
|
60
|
+
when true
|
61
|
+
output << 'true'
|
62
|
+
when false
|
63
|
+
output << 'false'
|
64
|
+
when nil
|
65
|
+
output << 'null'
|
66
|
+
else
|
67
|
+
raise SerializationError, "Unexpected value of type #{value.class}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def serialize_string(output, value)
|
72
|
+
output << '"'
|
73
|
+
output << value.gsub(CHAR_TO_ESCAPE) do |character|
|
74
|
+
case character
|
75
|
+
when BACKSLASH
|
76
|
+
'\\\\'
|
77
|
+
when QUOTE
|
78
|
+
'\\"'
|
79
|
+
when CONTROL_CHAR_TO_ESCAPE
|
80
|
+
'\u%.4X' % character.ord
|
81
|
+
end
|
82
|
+
end
|
83
|
+
output << '"'
|
84
|
+
end
|
85
|
+
|
86
|
+
def serialize_object_key(output, value)
|
87
|
+
case value
|
88
|
+
when Symbol, String
|
89
|
+
serialize_string output, value.to_s
|
90
|
+
else
|
91
|
+
raise SerializationError, "Could not serialize object of type #{value.class} as object key"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
class Launcher
|
5
|
+
|
6
|
+
# This class is used to pickup Gemfile changes during
|
7
|
+
# application restarts.
|
8
|
+
class BundlePruner
|
9
|
+
|
10
|
+
def initialize(original_argv, extra_runtime_dependencies, log_writer)
|
11
|
+
@original_argv = Array(original_argv)
|
12
|
+
@extra_runtime_dependencies = Array(extra_runtime_dependencies)
|
13
|
+
@log_writer = log_writer
|
14
|
+
end
|
15
|
+
|
16
|
+
def prune
|
17
|
+
return if ENV['PUMA_BUNDLER_PRUNED']
|
18
|
+
return unless defined?(Bundler)
|
19
|
+
|
20
|
+
require_rubygems_min_version!
|
21
|
+
|
22
|
+
unless puma_wild_path
|
23
|
+
log "! Unable to prune Bundler environment, continuing"
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
dirs = paths_to_require_after_prune
|
28
|
+
|
29
|
+
log '* Pruning Bundler environment'
|
30
|
+
home = ENV['GEM_HOME']
|
31
|
+
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
32
|
+
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
33
|
+
|
34
|
+
with_unbundled_env do
|
35
|
+
ENV['GEM_HOME'] = home
|
36
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
37
|
+
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
38
|
+
ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
|
39
|
+
args = [Gem.ruby, puma_wild_path, '-I', dirs.join(':')] + @original_argv
|
40
|
+
# Ruby 2.0+ defaults to true which breaks socket activation
|
41
|
+
args += [{:close_others => false}]
|
42
|
+
Kernel.exec(*args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def require_rubygems_min_version!
|
49
|
+
min_version = Gem::Version.new('2.2')
|
50
|
+
|
51
|
+
return if min_version <= Gem::Version.new(Gem::VERSION)
|
52
|
+
|
53
|
+
raise "prune_bundler is not supported on your version of RubyGems. " \
|
54
|
+
"You must have RubyGems #{min_version}+ to use this feature."
|
55
|
+
end
|
56
|
+
|
57
|
+
def puma_wild_path
|
58
|
+
puma_lib_dir = puma_require_paths.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
|
59
|
+
File.expand_path(File.join(puma_lib_dir, '../bin/puma-wild'))
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_unbundled_env
|
63
|
+
bundler_ver = Gem::Version.new(Bundler::VERSION)
|
64
|
+
if bundler_ver < Gem::Version.new('2.1.0')
|
65
|
+
Bundler.with_clean_env { yield }
|
66
|
+
else
|
67
|
+
Bundler.with_unbundled_env { yield }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def paths_to_require_after_prune
|
72
|
+
puma_require_paths + extra_runtime_deps_paths
|
73
|
+
end
|
74
|
+
|
75
|
+
def extra_runtime_deps_paths
|
76
|
+
t = @extra_runtime_dependencies.map do |dep_name|
|
77
|
+
if (spec = spec_for_gem(dep_name))
|
78
|
+
require_paths_for_gem(spec)
|
79
|
+
else
|
80
|
+
log "* Could not load extra dependency: #{dep_name}"
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
t.flatten!; t.compact!; t
|
85
|
+
end
|
86
|
+
|
87
|
+
def puma_require_paths
|
88
|
+
require_paths_for_gem(spec_for_gem('puma'))
|
89
|
+
end
|
90
|
+
|
91
|
+
def spec_for_gem(gem_name)
|
92
|
+
Bundler.rubygems.loaded_specs(gem_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
def require_paths_for_gem(gem_spec)
|
96
|
+
gem_spec.full_require_paths
|
97
|
+
end
|
98
|
+
|
99
|
+
def log(str)
|
100
|
+
@log_writer.log(str)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|