puma 2.2.0 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/History.md +3334 -0
- data/LICENSE +23 -20
- data/README.md +387 -100
- data/bin/puma-wild +25 -0
- data/bin/pumactl +1 -1
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/architecture.md +74 -0
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +137 -0
- data/docs/fork_worker.md +41 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -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/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +9 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -0
- data/docs/kubernetes.md +73 -0
- data/docs/nginx.md +5 -5
- data/docs/plugins.md +42 -0
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +65 -0
- data/docs/signals.md +98 -0
- data/docs/stats.md +148 -0
- data/docs/systemd.md +253 -0
- 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 -2
- data/ext/puma_http11/extconf.rb +59 -2
- data/ext/puma_http11/http11_parser.c +319 -487
- data/ext/puma_http11/http11_parser.h +15 -13
- data/ext/puma_http11/http11_parser.java.rl +64 -94
- data/ext/puma_http11/http11_parser.rl +27 -24
- data/ext/puma_http11/http11_parser_common.rl +8 -8
- data/ext/puma_http11/mini_ssl.c +696 -38
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +241 -145
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +174 -221
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
- data/ext/puma_http11/puma_http11.c +183 -175
- data/lib/puma/app/status.rb +77 -29
- data/lib/puma/binder.rb +349 -119
- data/lib/puma/cli.rb +163 -801
- data/lib/puma/client.rb +627 -144
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +127 -0
- data/lib/puma/cluster.rb +634 -0
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +115 -0
- data/lib/puma/configuration.rb +420 -198
- data/lib/puma/const.rb +266 -115
- data/lib/puma/control_cli.rb +219 -120
- data/lib/puma/detect.rb +56 -2
- data/lib/puma/dsl.rb +1562 -0
- data/lib/puma/error_logger.rb +115 -0
- data/lib/puma/events.rb +44 -60
- data/lib/puma/io_buffer.rb +48 -5
- data/lib/puma/jruby_restart.rb +2 -51
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +102 -0
- data/lib/puma/launcher.rb +501 -0
- data/lib/puma/log_writer.rb +153 -0
- data/lib/puma/minissl/context_builder.rb +96 -0
- data/lib/puma/minissl.rb +355 -37
- data/lib/puma/null_io.rb +79 -12
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +36 -0
- data/lib/puma/plugin.rb +111 -0
- data/lib/puma/rack/builder.rb +297 -0
- data/lib/puma/rack/urlmap.rb +93 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +102 -133
- data/lib/puma/response.rb +532 -0
- data/lib/puma/runner.rb +211 -0
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +582 -445
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +72 -0
- data/lib/puma/state_file.rb +69 -0
- data/lib/puma/thread_pool.rb +389 -57
- data/lib/puma/util.rb +125 -0
- data/lib/puma.rb +80 -6
- data/lib/rack/handler/puma.rb +125 -47
- data/tools/Dockerfile +26 -0
- data/tools/trickletest.rb +44 -0
- metadata +88 -148
- data/COPYING +0 -55
- data/Gemfile +0 -10
- data/History.txt +0 -302
- data/Manifest.txt +0 -60
- data/Rakefile +0 -165
- data/TODO +0 -5
- data/docs/config.md +0 -0
- data/ext/puma_http11/ext_help.h +0 -15
- data/ext/puma_http11/io_buffer.c +0 -154
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/capistrano.rb +0 -34
- data/lib/puma/compat.rb +0 -11
- data/lib/puma/daemon_ext.rb +0 -20
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack_patch.rb +0 -25
- data/puma.gemspec +0 -45
- data/test/test_app_status.rb +0 -88
- data/test/test_cli.rb +0 -171
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -126
- data/test/test_integration.rb +0 -154
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -25
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -224
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_thread_pool.rb +0 -146
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -332
- data/tools/jungle/init.d/run-puma +0 -3
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -52
data/lib/puma/binder.rb
CHANGED
|
@@ -1,39 +1,65 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'socket'
|
|
5
|
+
|
|
6
|
+
require_relative 'const'
|
|
7
|
+
require_relative 'util'
|
|
2
8
|
|
|
3
9
|
module Puma
|
|
10
|
+
|
|
11
|
+
if HAS_SSL
|
|
12
|
+
require_relative 'minissl'
|
|
13
|
+
require_relative 'minissl/context_builder'
|
|
14
|
+
end
|
|
15
|
+
|
|
4
16
|
class Binder
|
|
5
17
|
include Puma::Const
|
|
6
18
|
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
RACK_VERSION = [1,6].freeze
|
|
20
|
+
|
|
21
|
+
def initialize(log_writer, options, env: ENV)
|
|
22
|
+
@log_writer = log_writer
|
|
23
|
+
@options = options
|
|
9
24
|
@listeners = []
|
|
10
25
|
@inherited_fds = {}
|
|
26
|
+
@activated_sockets = {}
|
|
11
27
|
@unix_paths = []
|
|
28
|
+
@env = env
|
|
12
29
|
|
|
13
30
|
@proto_env = {
|
|
14
|
-
"rack.version".freeze =>
|
|
15
|
-
"rack.errors".freeze =>
|
|
16
|
-
"rack.multithread".freeze =>
|
|
17
|
-
"rack.multiprocess".freeze =>
|
|
31
|
+
"rack.version".freeze => RACK_VERSION,
|
|
32
|
+
"rack.errors".freeze => log_writer.stderr,
|
|
33
|
+
"rack.multithread".freeze => options[:max_threads] > 1,
|
|
34
|
+
"rack.multiprocess".freeze => options[:workers] >= 1,
|
|
18
35
|
"rack.run_once".freeze => false,
|
|
19
|
-
|
|
36
|
+
RACK_URL_SCHEME => options[:rack_url_scheme],
|
|
37
|
+
"SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
|
|
20
38
|
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
|
|
39
|
+
# I'd like to set a default CONTENT_TYPE here but some things
|
|
40
|
+
# depend on their not being a default set and inferring
|
|
41
|
+
# it from the content. And so if i set it here, it won't
|
|
42
|
+
# infer properly.
|
|
25
43
|
|
|
26
44
|
"QUERY_STRING".freeze => "",
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
SERVER_SOFTWARE => PUMA_SERVER_STRING,
|
|
46
|
+
GATEWAY_INTERFACE => CGI_VER,
|
|
47
|
+
|
|
48
|
+
RACK_AFTER_REPLY => nil,
|
|
49
|
+
RACK_RESPONSE_FINISHED => nil,
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
@envs = {}
|
|
33
53
|
@ios = []
|
|
34
54
|
end
|
|
35
55
|
|
|
36
|
-
attr_reader :
|
|
56
|
+
attr_reader :ios
|
|
57
|
+
|
|
58
|
+
# @version 5.0.0
|
|
59
|
+
attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
|
|
60
|
+
|
|
61
|
+
# @version 5.0.0
|
|
62
|
+
attr_writer :ios, :listeners
|
|
37
63
|
|
|
38
64
|
def env(sock)
|
|
39
65
|
@envs.fetch(sock, @proto_env)
|
|
@@ -41,130 +67,221 @@ module Puma
|
|
|
41
67
|
|
|
42
68
|
def close
|
|
43
69
|
@ios.each { |i| i.close }
|
|
44
|
-
@unix_paths.each { |i| File.unlink i }
|
|
45
70
|
end
|
|
46
71
|
|
|
47
|
-
|
|
48
|
-
|
|
72
|
+
# @!attribute [r] connected_ports
|
|
73
|
+
# @version 5.0.0
|
|
74
|
+
def connected_ports
|
|
75
|
+
t = ios.map { |io| io.addr[1] }; t.uniq!; t
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @version 5.0.0
|
|
79
|
+
def create_inherited_fds(env_hash)
|
|
80
|
+
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
|
|
81
|
+
fd, url = v.split(":", 2)
|
|
82
|
+
@inherited_fds[url] = fd.to_i
|
|
83
|
+
end.keys # pass keys back for removal
|
|
84
|
+
end
|
|
49
85
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
86
|
+
# systemd socket activation.
|
|
87
|
+
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
|
|
88
|
+
# LISTEN_PID = PID of the service process, aka us
|
|
89
|
+
# @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
|
90
|
+
# @version 5.0.0
|
|
91
|
+
#
|
|
92
|
+
def create_activated_fds(env_hash)
|
|
93
|
+
@log_writer.debug { "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}" }
|
|
94
|
+
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
|
95
|
+
env_hash['LISTEN_FDS'].to_i.times do |index|
|
|
96
|
+
sock = TCPServer.for_fd(socket_activation_fd(index))
|
|
97
|
+
key = begin # Try to parse as a path
|
|
98
|
+
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
|
99
|
+
rescue ArgumentError # Try to parse as a port/ip
|
|
100
|
+
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
|
101
|
+
addr = "[#{addr}]" if addr&.include? ':'
|
|
102
|
+
[:tcp, addr, port]
|
|
55
103
|
end
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
104
|
+
@activated_sockets[key] = sock
|
|
105
|
+
@log_writer.debug { "Registered #{key.join ':'} for activation from LISTEN_FDS" }
|
|
106
|
+
end
|
|
107
|
+
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Synthesize binds from systemd socket activation
|
|
111
|
+
#
|
|
112
|
+
# When systemd socket activation is enabled, it can be tedious to keep the
|
|
113
|
+
# binds in sync. This method can synthesize any binds based on the received
|
|
114
|
+
# activated sockets. Any existing matching binds will be respected.
|
|
115
|
+
#
|
|
116
|
+
# When only_matching is true in, all binds that do not match an activated
|
|
117
|
+
# socket is removed in place.
|
|
118
|
+
#
|
|
119
|
+
# It's a noop if no activated sockets were received.
|
|
120
|
+
def synthesize_binds_from_activated_fs(binds, only_matching)
|
|
121
|
+
return binds unless activated_sockets.any?
|
|
122
|
+
|
|
123
|
+
activated_binds = []
|
|
124
|
+
|
|
125
|
+
activated_sockets.keys.each do |proto, addr, port|
|
|
126
|
+
if port
|
|
127
|
+
tcp_url = "#{proto}://#{addr}:#{port}"
|
|
128
|
+
ssl_url = "ssl://#{addr}:#{port}"
|
|
129
|
+
ssl_url_prefix = "#{ssl_url}?"
|
|
130
|
+
|
|
131
|
+
existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
|
|
132
|
+
|
|
133
|
+
activated_binds << (existing || tcp_url)
|
|
134
|
+
else
|
|
135
|
+
# TODO: can there be a SSL bind without a port?
|
|
136
|
+
activated_binds << "#{proto}://#{addr}"
|
|
73
137
|
end
|
|
74
138
|
end
|
|
75
139
|
|
|
76
|
-
|
|
77
|
-
|
|
140
|
+
if only_matching
|
|
141
|
+
activated_binds
|
|
142
|
+
else
|
|
143
|
+
binds | activated_binds
|
|
78
144
|
end
|
|
79
145
|
end
|
|
80
146
|
|
|
81
|
-
def
|
|
147
|
+
def before_parse(&block)
|
|
148
|
+
@before_parse ||= []
|
|
149
|
+
@before_parse << block if block
|
|
150
|
+
@before_parse
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def parse(binds, log_writer = nil, log_msg = 'Listening')
|
|
154
|
+
before_parse.each(&:call)
|
|
155
|
+
log_writer ||= @log_writer
|
|
82
156
|
binds.each do |str|
|
|
83
157
|
uri = URI.parse str
|
|
84
158
|
case uri.scheme
|
|
85
159
|
when "tcp"
|
|
86
160
|
if fd = @inherited_fds.delete(str)
|
|
87
|
-
logger.log "* Inherited #{str}"
|
|
88
161
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
|
162
|
+
log_writer.log "* Inherited #{str}"
|
|
163
|
+
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
|
164
|
+
io = inherit_tcp_listener uri.host, uri.port, sock
|
|
165
|
+
log_writer.log "* Activated #{str}"
|
|
89
166
|
else
|
|
90
|
-
|
|
91
|
-
|
|
167
|
+
ios_len = @ios.length
|
|
168
|
+
params = Util.parse_query uri.query
|
|
169
|
+
|
|
170
|
+
low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
|
|
171
|
+
backlog = params.fetch('backlog', 1024).to_i
|
|
172
|
+
|
|
173
|
+
io = add_tcp_listener uri.host, uri.port, low_latency, backlog
|
|
174
|
+
|
|
175
|
+
@ios[ios_len..-1].each do |i|
|
|
176
|
+
addr = loc_addr_str i
|
|
177
|
+
log_writer.log "* #{log_msg} on http://#{addr}"
|
|
178
|
+
end
|
|
92
179
|
end
|
|
93
180
|
|
|
94
|
-
@listeners << [str, io]
|
|
181
|
+
@listeners << [str, io] if io
|
|
95
182
|
when "unix"
|
|
96
|
-
path = "#{uri.host}#{uri.path}"
|
|
183
|
+
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
|
184
|
+
abstract = false
|
|
185
|
+
if str.start_with? 'unix://@'
|
|
186
|
+
raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
|
|
187
|
+
abstract = true
|
|
188
|
+
path = "@#{path}"
|
|
189
|
+
end
|
|
97
190
|
|
|
98
191
|
if fd = @inherited_fds.delete(str)
|
|
99
|
-
|
|
192
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
|
100
193
|
io = inherit_unix_listener path, fd
|
|
194
|
+
log_writer.log "* Inherited #{str}"
|
|
195
|
+
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
|
196
|
+
!abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
|
197
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
|
198
|
+
io = inherit_unix_listener path, sock
|
|
199
|
+
log_writer.log "* Activated #{str}"
|
|
101
200
|
else
|
|
102
|
-
logger.log "* Listening on #{str}"
|
|
103
|
-
|
|
104
201
|
umask = nil
|
|
202
|
+
mode = nil
|
|
203
|
+
backlog = 1024
|
|
105
204
|
|
|
106
205
|
if uri.query
|
|
107
|
-
params =
|
|
206
|
+
params = Util.parse_query uri.query
|
|
108
207
|
if u = params['umask']
|
|
109
208
|
# Use Integer() to respect the 0 prefix as octal
|
|
110
209
|
umask = Integer(u)
|
|
111
210
|
end
|
|
211
|
+
|
|
212
|
+
if u = params['mode']
|
|
213
|
+
mode = Integer('0'+u)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
if u = params['backlog']
|
|
217
|
+
backlog = Integer(u)
|
|
218
|
+
end
|
|
112
219
|
end
|
|
113
220
|
|
|
114
|
-
|
|
221
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
|
222
|
+
io = add_unix_listener path, umask, mode, backlog
|
|
223
|
+
log_writer.log "* #{log_msg} on #{str}"
|
|
115
224
|
end
|
|
116
225
|
|
|
117
226
|
@listeners << [str, io]
|
|
118
227
|
when "ssl"
|
|
119
|
-
|
|
120
|
-
@events.error "SSL not supported on JRuby"
|
|
121
|
-
raise UnsupportedOption
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
params = Rack::Utils.parse_query uri.query
|
|
125
|
-
require 'puma/minissl'
|
|
228
|
+
cert_key = %w[cert key]
|
|
126
229
|
|
|
127
|
-
|
|
128
|
-
unless params['key']
|
|
129
|
-
@events.error "Please specify the SSL key via 'key='"
|
|
130
|
-
end
|
|
230
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
|
131
231
|
|
|
132
|
-
|
|
232
|
+
params = Util.parse_query uri.query
|
|
133
233
|
|
|
134
|
-
|
|
135
|
-
|
|
234
|
+
# If key and certs are not defined and localhost gem is required.
|
|
235
|
+
# localhost gem will be used for self signed
|
|
236
|
+
# Load localhost authority if not loaded.
|
|
237
|
+
# Ruby 3 `values_at` accepts an array, earlier do not
|
|
238
|
+
if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
|
|
239
|
+
ctx = localhost_authority && localhost_authority_context
|
|
136
240
|
end
|
|
137
241
|
|
|
138
|
-
ctx
|
|
139
|
-
|
|
140
|
-
|
|
242
|
+
ctx ||=
|
|
243
|
+
begin
|
|
244
|
+
# Extract cert_pem and key_pem from options[:store] if present
|
|
245
|
+
cert_key.each do |v|
|
|
246
|
+
if params[v]&.start_with?('store:')
|
|
247
|
+
index = Integer(params.delete(v).split('store:').last)
|
|
248
|
+
params["#{v}_pem"] = @options[:store][index]
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
MiniSSL::ContextBuilder.new(params, @log_writer).context
|
|
252
|
+
end
|
|
141
253
|
|
|
142
254
|
if fd = @inherited_fds.delete(str)
|
|
143
|
-
|
|
144
|
-
io =
|
|
255
|
+
log_writer.log "* Inherited #{str}"
|
|
256
|
+
io = inherit_ssl_listener fd, ctx
|
|
257
|
+
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
|
258
|
+
io = inherit_ssl_listener sock, ctx
|
|
259
|
+
log_writer.log "* Activated #{str}"
|
|
145
260
|
else
|
|
146
|
-
|
|
147
|
-
|
|
261
|
+
ios_len = @ios.length
|
|
262
|
+
backlog = params.fetch('backlog', 1024).to_i
|
|
263
|
+
low_latency = params['low_latency'] != 'false'
|
|
264
|
+
io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
|
|
265
|
+
|
|
266
|
+
@ios[ios_len..-1].each do |i|
|
|
267
|
+
addr = loc_addr_str i
|
|
268
|
+
log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
|
|
269
|
+
end
|
|
148
270
|
end
|
|
149
271
|
|
|
150
|
-
@listeners << [str, io]
|
|
272
|
+
@listeners << [str, io] if io
|
|
151
273
|
else
|
|
152
|
-
|
|
274
|
+
log_writer.error "Invalid URI: #{str}"
|
|
153
275
|
end
|
|
154
276
|
end
|
|
155
277
|
|
|
156
278
|
# If we inherited fds but didn't use them (because of a
|
|
157
279
|
# configuration change), then be sure to close them.
|
|
158
280
|
@inherited_fds.each do |str, fd|
|
|
159
|
-
|
|
281
|
+
log_writer.log "* Closing unused inherited connection: #{str}"
|
|
160
282
|
|
|
161
283
|
begin
|
|
162
|
-
|
|
163
|
-
fd.close
|
|
164
|
-
else
|
|
165
|
-
IO.for_fd(fd).close
|
|
166
|
-
end
|
|
167
|
-
|
|
284
|
+
IO.for_fd(fd).close
|
|
168
285
|
rescue SystemCallError
|
|
169
286
|
end
|
|
170
287
|
|
|
@@ -176,6 +293,36 @@ module Puma
|
|
|
176
293
|
end
|
|
177
294
|
end
|
|
178
295
|
|
|
296
|
+
# Also close any unused activated sockets
|
|
297
|
+
unless @activated_sockets.empty?
|
|
298
|
+
fds = @ios.map(&:to_i)
|
|
299
|
+
@activated_sockets.each do |key, sock|
|
|
300
|
+
next if fds.include? sock.to_i
|
|
301
|
+
log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
|
|
302
|
+
begin
|
|
303
|
+
sock.close
|
|
304
|
+
rescue SystemCallError
|
|
305
|
+
end
|
|
306
|
+
# We have to unlink a unix socket path that's not being used
|
|
307
|
+
File.unlink key[1] if key.first == :unix
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def localhost_authority
|
|
313
|
+
@localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def localhost_authority_context
|
|
317
|
+
return unless localhost_authority
|
|
318
|
+
|
|
319
|
+
key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
|
|
320
|
+
[localhost_authority.key_path, localhost_authority.certificate_path]
|
|
321
|
+
else
|
|
322
|
+
local_certificates_path = File.expand_path("~/.localhost")
|
|
323
|
+
[File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
|
|
324
|
+
end
|
|
325
|
+
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
|
|
179
326
|
end
|
|
180
327
|
|
|
181
328
|
# Tell the server to listen on host +host+, port +port+.
|
|
@@ -186,23 +333,28 @@ module Puma
|
|
|
186
333
|
# allow to accumulate before returning connection refused.
|
|
187
334
|
#
|
|
188
335
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
|
189
|
-
|
|
190
|
-
|
|
336
|
+
if host == "localhost"
|
|
337
|
+
loopback_addresses.each do |addr|
|
|
338
|
+
add_tcp_listener addr, port, optimize_for_latency, backlog
|
|
339
|
+
end
|
|
340
|
+
return
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
host = host[1..-2] if host&.start_with? '['
|
|
344
|
+
tcp_server = TCPServer.new(host, port)
|
|
345
|
+
|
|
191
346
|
if optimize_for_latency
|
|
192
|
-
|
|
347
|
+
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
193
348
|
end
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
349
|
+
tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
|
350
|
+
tcp_server.listen backlog
|
|
351
|
+
|
|
352
|
+
@ios << tcp_server
|
|
353
|
+
tcp_server
|
|
198
354
|
end
|
|
199
355
|
|
|
200
356
|
def inherit_tcp_listener(host, port, fd)
|
|
201
|
-
|
|
202
|
-
s = fd
|
|
203
|
-
else
|
|
204
|
-
s = TCPServer.for_fd(fd)
|
|
205
|
-
end
|
|
357
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
|
206
358
|
|
|
207
359
|
@ios << s
|
|
208
360
|
s
|
|
@@ -210,13 +362,19 @@ module Puma
|
|
|
210
362
|
|
|
211
363
|
def add_ssl_listener(host, port, ctx,
|
|
212
364
|
optimize_for_latency=true, backlog=1024)
|
|
213
|
-
if IS_JRUBY
|
|
214
|
-
@events.error "SSL not supported on JRuby"
|
|
215
|
-
raise UnsupportedOption
|
|
216
|
-
end
|
|
217
365
|
|
|
218
|
-
|
|
366
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
|
367
|
+
# Puma will try to use local authority context if context is supplied nil
|
|
368
|
+
ctx ||= localhost_authority_context
|
|
369
|
+
|
|
370
|
+
if host == "localhost"
|
|
371
|
+
loopback_addresses.each do |addr|
|
|
372
|
+
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
|
|
373
|
+
end
|
|
374
|
+
return
|
|
375
|
+
end
|
|
219
376
|
|
|
377
|
+
host = host[1..-2] if host&.start_with? '['
|
|
220
378
|
s = TCPServer.new(host, port)
|
|
221
379
|
if optimize_for_latency
|
|
222
380
|
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
@@ -233,49 +391,121 @@ module Puma
|
|
|
233
391
|
s
|
|
234
392
|
end
|
|
235
393
|
|
|
236
|
-
def
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
394
|
+
def inherit_ssl_listener(fd, ctx)
|
|
395
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
|
396
|
+
# Puma will try to use local authority context if context is supplied nil
|
|
397
|
+
ctx ||= localhost_authority_context
|
|
398
|
+
|
|
399
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
|
400
|
+
|
|
401
|
+
ssl = MiniSSL::Server.new(s, ctx)
|
|
402
|
+
|
|
403
|
+
env = @proto_env.dup
|
|
404
|
+
env[HTTPS_KEY] = HTTPS
|
|
405
|
+
@envs[ssl] = env
|
|
406
|
+
|
|
407
|
+
@ios << ssl
|
|
241
408
|
|
|
242
|
-
require 'puma/minissl'
|
|
243
|
-
s = TCPServer.for_fd(fd)
|
|
244
|
-
@ios << MiniSSL::Server.new(s, ctx)
|
|
245
409
|
s
|
|
246
410
|
end
|
|
247
411
|
|
|
248
412
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
|
249
413
|
#
|
|
250
|
-
def add_unix_listener(path, umask=nil)
|
|
251
|
-
@unix_paths << path
|
|
252
|
-
|
|
414
|
+
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
|
253
415
|
# Let anyone connect by default
|
|
254
416
|
umask ||= 0
|
|
255
417
|
|
|
256
418
|
begin
|
|
257
419
|
old_mask = File.umask(umask)
|
|
258
|
-
|
|
420
|
+
|
|
421
|
+
if File.exist? path
|
|
422
|
+
begin
|
|
423
|
+
old = UNIXSocket.new path
|
|
424
|
+
rescue SystemCallError, IOError
|
|
425
|
+
File.unlink path
|
|
426
|
+
else
|
|
427
|
+
old.close
|
|
428
|
+
raise "There is already a server bound to: #{path}"
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
|
|
432
|
+
s.listen backlog
|
|
259
433
|
@ios << s
|
|
260
434
|
ensure
|
|
261
435
|
File.umask old_mask
|
|
262
436
|
end
|
|
263
437
|
|
|
438
|
+
if mode
|
|
439
|
+
File.chmod mode, path
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
env = @proto_env.dup
|
|
443
|
+
env[REMOTE_ADDR] = "127.0.0.1"
|
|
444
|
+
@envs[s] = env
|
|
445
|
+
|
|
264
446
|
s
|
|
265
447
|
end
|
|
266
448
|
|
|
267
449
|
def inherit_unix_listener(path, fd)
|
|
268
|
-
|
|
450
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
|
|
269
451
|
|
|
270
|
-
if fd.kind_of? TCPServer
|
|
271
|
-
s = fd
|
|
272
|
-
else
|
|
273
|
-
s = UNIXServer.for_fd fd
|
|
274
|
-
end
|
|
275
452
|
@ios << s
|
|
276
453
|
|
|
454
|
+
env = @proto_env.dup
|
|
455
|
+
env[REMOTE_ADDR] = "127.0.0.1"
|
|
456
|
+
@envs[s] = env
|
|
457
|
+
|
|
277
458
|
s
|
|
278
459
|
end
|
|
279
460
|
|
|
461
|
+
def close_listeners
|
|
462
|
+
@listeners.each do |l, io|
|
|
463
|
+
begin
|
|
464
|
+
io.close unless io.closed?
|
|
465
|
+
uri = URI.parse l
|
|
466
|
+
next unless uri.scheme == 'unix'
|
|
467
|
+
unix_path = "#{uri.host}#{uri.path}"
|
|
468
|
+
File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
|
|
469
|
+
rescue Errno::EBADF
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def redirects_for_restart
|
|
475
|
+
redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
|
476
|
+
redirects[:close_others] = true
|
|
477
|
+
redirects
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# @version 5.0.0
|
|
481
|
+
def redirects_for_restart_env
|
|
482
|
+
@listeners.each_with_object({}).with_index do |(listen, memo), i|
|
|
483
|
+
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
private
|
|
488
|
+
|
|
489
|
+
# @!attribute [r] loopback_addresses
|
|
490
|
+
def loopback_addresses
|
|
491
|
+
t = Socket.ip_address_list.select do |addrinfo|
|
|
492
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
|
493
|
+
end
|
|
494
|
+
t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def loc_addr_str(io)
|
|
498
|
+
loc_addr = io.to_io.local_address
|
|
499
|
+
if loc_addr.ipv6?
|
|
500
|
+
"[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
|
|
501
|
+
else
|
|
502
|
+
loc_addr.ip_unpack.join(':')
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
# @version 5.0.0
|
|
507
|
+
def socket_activation_fd(int)
|
|
508
|
+
int + 3 # 3 is the magic number you add to follow the SA protocol
|
|
509
|
+
end
|
|
280
510
|
end
|
|
281
511
|
end
|