puma 3.12.1 → 5.6.4
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 +1553 -447
- data/LICENSE +23 -20
- data/README.md +175 -63
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +69 -58
- data/docs/fork_worker.md +33 -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 +1 -1
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +95 -120
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +51 -1
- data/ext/puma_http11/http11_parser.c +105 -117
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +4 -2
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +319 -96
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +120 -65
- data/ext/puma_http11/puma_http11.c +35 -51
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +234 -137
- data/lib/puma/cli.rb +28 -18
- data/lib/puma/client.rb +343 -230
- data/lib/puma/cluster/worker.rb +173 -0
- data/lib/puma/cluster/worker_handle.rb +94 -0
- data/lib/puma/cluster.rb +247 -232
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +61 -51
- data/lib/puma/const.rb +42 -21
- data/lib/puma/control_cli.rb +109 -67
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +615 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -31
- data/lib/puma/io_buffer.rb +7 -5
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +182 -69
- data/lib/puma/minissl/context_builder.rb +81 -0
- data/lib/puma/minissl.rb +161 -61
- data/lib/puma/null_io.rb +13 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +3 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +472 -0
- data/lib/puma/runner.rb +48 -55
- data/lib/puma/server.rb +303 -695
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +47 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +132 -82
- data/lib/puma/util.rb +21 -7
- data/lib/puma.rb +54 -0
- data/lib/rack/handler/puma.rb +5 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -29
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- 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
data/lib/puma/app/status.rb
CHANGED
@@ -1,73 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'puma/json_serialization'
|
3
|
+
|
1
4
|
module Puma
|
2
5
|
module App
|
6
|
+
# Check out {#call}'s source code to see what actions this web application
|
7
|
+
# can respond to.
|
3
8
|
class Status
|
4
|
-
def initialize(cli)
|
5
|
-
@cli = cli
|
6
|
-
@auth_token = nil
|
7
|
-
end
|
8
9
|
OK_STATUS = '{ "status": "ok" }'.freeze
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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]]
|
11
|
+
# @param launcher [::Puma::Launcher]
|
12
|
+
# @param token [String, nil] the token used for authentication
|
13
|
+
#
|
14
|
+
def initialize(launcher, token = nil)
|
15
|
+
@launcher = launcher
|
16
|
+
@auth_token = token
|
24
17
|
end
|
25
18
|
|
19
|
+
# most commands call methods in `::Puma::Launcher` based on command in
|
20
|
+
# `env['PATH_INFO']`
|
26
21
|
def call(env)
|
27
22
|
unless authenticate(env)
|
28
23
|
return rack_response(403, 'Invalid auth token', 'text/plain')
|
29
24
|
end
|
30
25
|
|
31
|
-
case
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
# resp_type is processed by following case statement, return
|
27
|
+
# is a number (status) or a string used as the body of a 200 response
|
28
|
+
resp_type =
|
29
|
+
case env['PATH_INFO'][/\/([^\/]+)$/, 1]
|
30
|
+
when 'stop'
|
31
|
+
@launcher.stop ; 200
|
35
32
|
|
36
|
-
|
37
|
-
|
38
|
-
return rack_response(200, OK_STATUS)
|
33
|
+
when 'halt'
|
34
|
+
@launcher.halt ; 200
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
return rack_response(200, OK_STATUS)
|
36
|
+
when 'restart'
|
37
|
+
@launcher.restart ; 200
|
43
38
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
39
|
+
when 'phased-restart'
|
40
|
+
@launcher.phased_restart ? 200 : 404
|
41
|
+
|
42
|
+
when 'reload-worker-directory'
|
43
|
+
@launcher.send(:reload_worker_directory) ? 200 : 404
|
44
|
+
|
45
|
+
when 'gc'
|
46
|
+
GC.start ; 200
|
47
|
+
|
48
|
+
when 'gc-stats'
|
49
|
+
Puma::JSONSerialization.generate GC.stat
|
50
|
+
|
51
|
+
when 'stats'
|
52
|
+
Puma::JSONSerialization.generate @launcher.stats
|
53
|
+
|
54
|
+
when 'thread-backtraces'
|
55
|
+
backtraces = []
|
56
|
+
@launcher.thread_status do |name, backtrace|
|
57
|
+
backtraces << { name: name, backtrace: backtrace }
|
58
|
+
end
|
59
|
+
Puma::JSONSerialization.generate backtraces
|
50
60
|
|
51
|
-
when /\/reload-worker-directory$/
|
52
|
-
if !@cli.send(:reload_worker_directory)
|
53
|
-
return rack_response(404, '{ "error": "reload_worker_directory not available" }')
|
54
61
|
else
|
55
|
-
return rack_response(
|
62
|
+
return rack_response(404, "Unsupported action", 'text/plain')
|
56
63
|
end
|
57
64
|
|
58
|
-
|
59
|
-
|
60
|
-
|
65
|
+
case resp_type
|
66
|
+
when String
|
67
|
+
rack_response 200, resp_type
|
68
|
+
when 200
|
69
|
+
rack_response 200, OK_STATUS
|
70
|
+
when 404
|
71
|
+
str = env['PATH_INFO'][/\/(\S+)/, 1].tr '-', '_'
|
72
|
+
rack_response 404, "{ \"error\": \"#{str} not available\" }"
|
73
|
+
end
|
74
|
+
end
|
61
75
|
|
62
|
-
|
63
|
-
json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}"
|
64
|
-
return rack_response(200, json)
|
76
|
+
private
|
65
77
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
78
|
+
def authenticate(env)
|
79
|
+
return true unless @auth_token
|
80
|
+
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def rack_response(status, body, content_type='application/json')
|
84
|
+
headers = {
|
85
|
+
'Content-Type' => content_type,
|
86
|
+
'Content-Length' => body.bytesize.to_s
|
87
|
+
}
|
88
|
+
|
89
|
+
[status, headers, [body]]
|
71
90
|
end
|
72
91
|
end
|
73
92
|
end
|
data/lib/puma/binder.rb
CHANGED
@@ -5,15 +5,32 @@ require 'socket'
|
|
5
5
|
|
6
6
|
require 'puma/const'
|
7
7
|
require 'puma/util'
|
8
|
+
require 'puma/configuration'
|
8
9
|
|
9
10
|
module Puma
|
11
|
+
|
12
|
+
if HAS_SSL
|
13
|
+
require 'puma/minissl'
|
14
|
+
require 'puma/minissl/context_builder'
|
15
|
+
|
16
|
+
# Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
|
17
|
+
# NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
|
18
|
+
# The bug was that it did not require openssl.
|
19
|
+
# @todo remove when Ruby 2.3 support is dropped
|
20
|
+
#
|
21
|
+
if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
|
22
|
+
require 'openssl'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
10
26
|
class Binder
|
11
27
|
include Puma::Const
|
12
28
|
|
13
|
-
RACK_VERSION = [1,
|
29
|
+
RACK_VERSION = [1,6].freeze
|
14
30
|
|
15
|
-
def initialize(events)
|
31
|
+
def initialize(events, conf = Configuration.new)
|
16
32
|
@events = events
|
33
|
+
@conf = conf
|
17
34
|
@listeners = []
|
18
35
|
@inherited_fds = {}
|
19
36
|
@activated_sockets = {}
|
@@ -22,9 +39,10 @@ module Puma
|
|
22
39
|
@proto_env = {
|
23
40
|
"rack.version".freeze => RACK_VERSION,
|
24
41
|
"rack.errors".freeze => events.stderr,
|
25
|
-
"rack.multithread".freeze =>
|
26
|
-
"rack.multiprocess".freeze =>
|
42
|
+
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
43
|
+
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
27
44
|
"rack.run_once".freeze => false,
|
45
|
+
RACK_URL_SCHEME => conf.options[:rack_url_scheme],
|
28
46
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
29
47
|
|
30
48
|
# I'd like to set a default CONTENT_TYPE here but some things
|
@@ -40,9 +58,16 @@ module Puma
|
|
40
58
|
|
41
59
|
@envs = {}
|
42
60
|
@ios = []
|
61
|
+
localhost_authority
|
43
62
|
end
|
44
63
|
|
45
|
-
attr_reader :
|
64
|
+
attr_reader :ios
|
65
|
+
|
66
|
+
# @version 5.0.0
|
67
|
+
attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
|
68
|
+
|
69
|
+
# @version 5.0.0
|
70
|
+
attr_writer :ios, :listeners
|
46
71
|
|
47
72
|
def env(sock)
|
48
73
|
@envs.fetch(sock, @proto_env)
|
@@ -50,43 +75,84 @@ module Puma
|
|
50
75
|
|
51
76
|
def close
|
52
77
|
@ios.each { |i| i.close }
|
53
|
-
@unix_paths.each { |i| File.unlink i }
|
54
78
|
end
|
55
79
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
80
|
+
# @!attribute [r] connected_ports
|
81
|
+
# @version 5.0.0
|
82
|
+
def connected_ports
|
83
|
+
ios.map { |io| io.addr[1] }.uniq
|
84
|
+
end
|
85
|
+
|
86
|
+
# @version 5.0.0
|
87
|
+
def create_inherited_fds(env_hash)
|
88
|
+
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
|
89
|
+
fd, url = v.split(":", 2)
|
90
|
+
@inherited_fds[url] = fd.to_i
|
91
|
+
end.keys # pass keys back for removal
|
92
|
+
end
|
93
|
+
|
94
|
+
# systemd socket activation.
|
95
|
+
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
|
96
|
+
# LISTEN_PID = PID of the service process, aka us
|
97
|
+
# @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
98
|
+
# @version 5.0.0
|
99
|
+
#
|
100
|
+
def create_activated_fds(env_hash)
|
101
|
+
@events.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
102
|
+
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
103
|
+
env_hash['LISTEN_FDS'].to_i.times do |index|
|
104
|
+
sock = TCPServer.for_fd(socket_activation_fd(index))
|
105
|
+
key = begin # Try to parse as a path
|
106
|
+
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
107
|
+
rescue ArgumentError # Try to parse as a port/ip
|
108
|
+
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
109
|
+
addr = "[#{addr}]" if addr =~ /\:/
|
110
|
+
[:tcp, addr, port]
|
81
111
|
end
|
112
|
+
@activated_sockets[key] = sock
|
113
|
+
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
82
114
|
end
|
115
|
+
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
116
|
+
end
|
83
117
|
|
84
|
-
|
85
|
-
|
118
|
+
# Synthesize binds from systemd socket activation
|
119
|
+
#
|
120
|
+
# When systemd socket activation is enabled, it can be tedious to keep the
|
121
|
+
# binds in sync. This method can synthesize any binds based on the received
|
122
|
+
# activated sockets. Any existing matching binds will be respected.
|
123
|
+
#
|
124
|
+
# When only_matching is true in, all binds that do not match an activated
|
125
|
+
# socket is removed in place.
|
126
|
+
#
|
127
|
+
# It's a noop if no activated sockets were received.
|
128
|
+
def synthesize_binds_from_activated_fs(binds, only_matching)
|
129
|
+
return binds unless activated_sockets.any?
|
130
|
+
|
131
|
+
activated_binds = []
|
132
|
+
|
133
|
+
activated_sockets.keys.each do |proto, addr, port|
|
134
|
+
if port
|
135
|
+
tcp_url = "#{proto}://#{addr}:#{port}"
|
136
|
+
ssl_url = "ssl://#{addr}:#{port}"
|
137
|
+
ssl_url_prefix = "#{ssl_url}?"
|
138
|
+
|
139
|
+
existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
|
140
|
+
|
141
|
+
activated_binds << (existing || tcp_url)
|
142
|
+
else
|
143
|
+
# TODO: can there be a SSL bind without a port?
|
144
|
+
activated_binds << "#{proto}://#{addr}"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if only_matching
|
149
|
+
activated_binds
|
150
|
+
else
|
151
|
+
binds | activated_binds
|
86
152
|
end
|
87
153
|
end
|
88
154
|
|
89
|
-
def parse(binds, logger)
|
155
|
+
def parse(binds, logger, log_msg = 'Listening')
|
90
156
|
binds.each do |str|
|
91
157
|
uri = URI.parse str
|
92
158
|
case uri.scheme
|
@@ -98,23 +164,37 @@ module Puma
|
|
98
164
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
99
165
|
logger.log "* Activated #{str}"
|
100
166
|
else
|
167
|
+
ios_len = @ios.length
|
101
168
|
params = Util.parse_query uri.query
|
102
169
|
|
103
|
-
opt = params.key?('low_latency')
|
104
|
-
|
170
|
+
opt = 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, opt, backlog
|
105
174
|
|
106
|
-
|
107
|
-
|
175
|
+
@ios[ios_len..-1].each do |i|
|
176
|
+
addr = loc_addr_str i
|
177
|
+
logger.log "* #{log_msg} on http://#{addr}"
|
178
|
+
end
|
108
179
|
end
|
109
180
|
|
110
181
|
@listeners << [str, io] if io
|
111
182
|
when "unix"
|
112
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
|
113
190
|
|
114
191
|
if fd = @inherited_fds.delete(str)
|
192
|
+
@unix_paths << path unless abstract
|
115
193
|
io = inherit_unix_listener path, fd
|
116
194
|
logger.log "* Inherited #{str}"
|
117
|
-
elsif sock = @activated_sockets.delete([ :unix, path ])
|
195
|
+
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
196
|
+
@activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
197
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
118
198
|
io = inherit_unix_listener path, sock
|
119
199
|
logger.log "* Activated #{str}"
|
120
200
|
else
|
@@ -138,69 +218,37 @@ module Puma
|
|
138
218
|
end
|
139
219
|
end
|
140
220
|
|
221
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
141
222
|
io = add_unix_listener path, umask, mode, backlog
|
142
|
-
logger.log "*
|
223
|
+
logger.log "* #{log_msg} on #{str}"
|
143
224
|
end
|
144
225
|
|
145
226
|
@listeners << [str, io]
|
146
227
|
when "ssl"
|
147
|
-
params = Util.parse_query uri.query
|
148
|
-
require 'puma/minissl'
|
149
|
-
|
150
|
-
MiniSSL.check
|
151
|
-
|
152
|
-
ctx = MiniSSL::Context.new
|
153
|
-
|
154
|
-
if defined?(JRUBY_VERSION)
|
155
|
-
unless params['keystore']
|
156
|
-
@events.error "Please specify the Java keystore via 'keystore='"
|
157
|
-
end
|
158
|
-
|
159
|
-
ctx.keystore = params['keystore']
|
160
|
-
|
161
|
-
unless params['keystore-pass']
|
162
|
-
@events.error "Please specify the Java keystore password via 'keystore-pass='"
|
163
|
-
end
|
164
|
-
|
165
|
-
ctx.keystore_pass = params['keystore-pass']
|
166
|
-
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
167
|
-
else
|
168
|
-
unless params['key']
|
169
|
-
@events.error "Please specify the SSL key via 'key='"
|
170
|
-
end
|
171
228
|
|
172
|
-
|
229
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
173
230
|
|
174
|
-
|
175
|
-
@events.error "Please specify the SSL cert via 'cert='"
|
176
|
-
end
|
231
|
+
params = Util.parse_query uri.query
|
177
232
|
|
178
|
-
|
233
|
+
# If key and certs are not defined and localhost gem is required.
|
234
|
+
# localhost gem will be used for self signed
|
235
|
+
# Load localhost authority if not loaded.
|
236
|
+
if params.values_at('cert', 'key').all? { |v| v.to_s.empty? }
|
237
|
+
ctx = localhost_authority && localhost_authority_context
|
238
|
+
end
|
179
239
|
|
180
|
-
|
181
|
-
|
182
|
-
|
240
|
+
ctx ||=
|
241
|
+
begin
|
242
|
+
# Extract cert_pem and key_pem from options[:store] if present
|
243
|
+
['cert', 'key'].each do |v|
|
244
|
+
if params[v] && params[v].start_with?('store:')
|
245
|
+
index = Integer(params.delete(v).split('store:').last)
|
246
|
+
params["#{v}_pem"] = @conf.options[:store][index]
|
247
|
+
end
|
183
248
|
end
|
249
|
+
MiniSSL::ContextBuilder.new(params, @events).context
|
184
250
|
end
|
185
251
|
|
186
|
-
ctx.ca = params['ca'] if params['ca']
|
187
|
-
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
188
|
-
end
|
189
|
-
|
190
|
-
if params['verify_mode']
|
191
|
-
ctx.verify_mode = case params['verify_mode']
|
192
|
-
when "peer"
|
193
|
-
MiniSSL::VERIFY_PEER
|
194
|
-
when "force_peer"
|
195
|
-
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
196
|
-
when "none"
|
197
|
-
MiniSSL::VERIFY_NONE
|
198
|
-
else
|
199
|
-
@events.error "Please specify a valid verify_mode="
|
200
|
-
MiniSSL::VERIFY_NONE
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
252
|
if fd = @inherited_fds.delete(str)
|
205
253
|
logger.log "* Inherited #{str}"
|
206
254
|
io = inherit_ssl_listener fd, ctx
|
@@ -208,8 +256,14 @@ module Puma
|
|
208
256
|
io = inherit_ssl_listener sock, ctx
|
209
257
|
logger.log "* Activated #{str}"
|
210
258
|
else
|
211
|
-
|
212
|
-
|
259
|
+
ios_len = @ios.length
|
260
|
+
backlog = params.fetch('backlog', 1024).to_i
|
261
|
+
io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
|
262
|
+
|
263
|
+
@ios[ios_len..-1].each do |i|
|
264
|
+
addr = loc_addr_str i
|
265
|
+
logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
|
266
|
+
end
|
213
267
|
end
|
214
268
|
|
215
269
|
@listeners << [str, io] if io
|
@@ -237,21 +291,35 @@ module Puma
|
|
237
291
|
end
|
238
292
|
|
239
293
|
# Also close any unused activated sockets
|
240
|
-
@activated_sockets.
|
241
|
-
|
242
|
-
|
243
|
-
sock.
|
244
|
-
|
294
|
+
unless @activated_sockets.empty?
|
295
|
+
fds = @ios.map(&:to_i)
|
296
|
+
@activated_sockets.each do |key, sock|
|
297
|
+
next if fds.include? sock.to_i
|
298
|
+
logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
|
299
|
+
begin
|
300
|
+
sock.close
|
301
|
+
rescue SystemCallError
|
302
|
+
end
|
303
|
+
# We have to unlink a unix socket path that's not being used
|
304
|
+
File.unlink key[1] if key.first == :unix
|
245
305
|
end
|
246
|
-
# We have to unlink a unix socket path that's not being used
|
247
|
-
File.unlink key[1] if key[0] == :unix
|
248
306
|
end
|
249
307
|
end
|
250
308
|
|
251
|
-
def
|
252
|
-
|
253
|
-
|
254
|
-
|
309
|
+
def localhost_authority
|
310
|
+
@localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
|
311
|
+
end
|
312
|
+
|
313
|
+
def localhost_authority_context
|
314
|
+
return unless localhost_authority
|
315
|
+
|
316
|
+
key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
|
317
|
+
[localhost_authority.key_path, localhost_authority.certificate_path]
|
318
|
+
else
|
319
|
+
local_certificates_path = File.expand_path("~/.localhost")
|
320
|
+
[File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
|
321
|
+
end
|
322
|
+
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @events).context
|
255
323
|
end
|
256
324
|
|
257
325
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -270,26 +338,20 @@ module Puma
|
|
270
338
|
end
|
271
339
|
|
272
340
|
host = host[1..-2] if host and host[0..0] == '['
|
273
|
-
|
341
|
+
tcp_server = TCPServer.new(host, port)
|
342
|
+
|
274
343
|
if optimize_for_latency
|
275
|
-
|
344
|
+
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
276
345
|
end
|
277
|
-
|
278
|
-
|
279
|
-
@connected_port = s.addr[1]
|
346
|
+
tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
347
|
+
tcp_server.listen backlog
|
280
348
|
|
281
|
-
@ios <<
|
282
|
-
|
349
|
+
@ios << tcp_server
|
350
|
+
tcp_server
|
283
351
|
end
|
284
352
|
|
285
|
-
attr_reader :connected_port
|
286
|
-
|
287
353
|
def inherit_tcp_listener(host, port, fd)
|
288
|
-
|
289
|
-
s = fd
|
290
|
-
else
|
291
|
-
s = TCPServer.for_fd(fd)
|
292
|
-
end
|
354
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
293
355
|
|
294
356
|
@ios << s
|
295
357
|
s
|
@@ -297,9 +359,10 @@ module Puma
|
|
297
359
|
|
298
360
|
def add_ssl_listener(host, port, ctx,
|
299
361
|
optimize_for_latency=true, backlog=1024)
|
300
|
-
require 'puma/minissl'
|
301
362
|
|
302
|
-
|
363
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
364
|
+
# Puma will try to use local authority context if context is supplied nil
|
365
|
+
ctx ||= localhost_authority_context
|
303
366
|
|
304
367
|
if host == "localhost"
|
305
368
|
loopback_addresses.each do |addr|
|
@@ -316,7 +379,6 @@ module Puma
|
|
316
379
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
317
380
|
s.listen backlog
|
318
381
|
|
319
|
-
|
320
382
|
ssl = MiniSSL::Server.new s, ctx
|
321
383
|
env = @proto_env.dup
|
322
384
|
env[HTTPS_KEY] = HTTPS
|
@@ -327,14 +389,12 @@ module Puma
|
|
327
389
|
end
|
328
390
|
|
329
391
|
def inherit_ssl_listener(fd, ctx)
|
330
|
-
|
331
|
-
|
392
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
393
|
+
# Puma will try to use local authority context if context is supplied nil
|
394
|
+
ctx ||= localhost_authority_context
|
395
|
+
|
396
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
332
397
|
|
333
|
-
if fd.kind_of? TCPServer
|
334
|
-
s = fd
|
335
|
-
else
|
336
|
-
s = TCPServer.for_fd(fd)
|
337
|
-
end
|
338
398
|
ssl = MiniSSL::Server.new(s, ctx)
|
339
399
|
|
340
400
|
env = @proto_env.dup
|
@@ -349,8 +409,6 @@ module Puma
|
|
349
409
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
350
410
|
#
|
351
411
|
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
352
|
-
@unix_paths << path
|
353
|
-
|
354
412
|
# Let anyone connect by default
|
355
413
|
umask ||= 0
|
356
414
|
|
@@ -367,8 +425,7 @@ module Puma
|
|
367
425
|
raise "There is already a server bound to: #{path}"
|
368
426
|
end
|
369
427
|
end
|
370
|
-
|
371
|
-
s = UNIXServer.new(path)
|
428
|
+
s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
|
372
429
|
s.listen backlog
|
373
430
|
@ios << s
|
374
431
|
ensure
|
@@ -387,13 +444,8 @@ module Puma
|
|
387
444
|
end
|
388
445
|
|
389
446
|
def inherit_unix_listener(path, fd)
|
390
|
-
|
447
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
|
391
448
|
|
392
|
-
if fd.kind_of? TCPServer
|
393
|
-
s = fd
|
394
|
-
else
|
395
|
-
s = UNIXServer.for_fd fd
|
396
|
-
end
|
397
449
|
@ios << s
|
398
450
|
|
399
451
|
env = @proto_env.dup
|
@@ -403,5 +455,50 @@ module Puma
|
|
403
455
|
s
|
404
456
|
end
|
405
457
|
|
458
|
+
def close_listeners
|
459
|
+
@listeners.each do |l, io|
|
460
|
+
io.close unless io.closed?
|
461
|
+
uri = URI.parse l
|
462
|
+
next unless uri.scheme == 'unix'
|
463
|
+
unix_path = "#{uri.host}#{uri.path}"
|
464
|
+
File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def redirects_for_restart
|
469
|
+
redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
470
|
+
redirects[:close_others] = true
|
471
|
+
redirects
|
472
|
+
end
|
473
|
+
|
474
|
+
# @version 5.0.0
|
475
|
+
def redirects_for_restart_env
|
476
|
+
@listeners.each_with_object({}).with_index do |(listen, memo), i|
|
477
|
+
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
private
|
482
|
+
|
483
|
+
# @!attribute [r] loopback_addresses
|
484
|
+
def loopback_addresses
|
485
|
+
Socket.ip_address_list.select do |addrinfo|
|
486
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
487
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
488
|
+
end
|
489
|
+
|
490
|
+
def loc_addr_str(io)
|
491
|
+
loc_addr = io.to_io.local_address
|
492
|
+
if loc_addr.ipv6?
|
493
|
+
"[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
|
494
|
+
else
|
495
|
+
loc_addr.ip_unpack.join(':')
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
# @version 5.0.0
|
500
|
+
def socket_activation_fd(int)
|
501
|
+
int + 3 # 3 is the magic number you add to follow the SA protocol
|
502
|
+
end
|
406
503
|
end
|
407
504
|
end
|