puma 4.3.12 → 6.0.2

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.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1618 -521
  3. data/LICENSE +23 -20
  4. data/README.md +130 -42
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +2 -2
  16. data/docs/plugins.md +15 -15
  17. data/docs/rails_dev_mode.md +28 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +13 -11
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +85 -128
  22. data/docs/testing_benchmarks_local_files.md +150 -0
  23. data/docs/testing_test_rackup_ci_files.md +36 -0
  24. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  25. data/ext/puma_http11/ext_help.h +1 -1
  26. data/ext/puma_http11/extconf.rb +49 -12
  27. data/ext/puma_http11/http11_parser.c +46 -48
  28. data/ext/puma_http11/http11_parser.h +2 -2
  29. data/ext/puma_http11/http11_parser.java.rl +3 -3
  30. data/ext/puma_http11/http11_parser.rl +3 -3
  31. data/ext/puma_http11/http11_parser_common.rl +2 -2
  32. data/ext/puma_http11/mini_ssl.c +250 -93
  33. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  34. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  35. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  37. data/ext/puma_http11/puma_http11.c +46 -57
  38. data/lib/puma/app/status.rb +52 -38
  39. data/lib/puma/binder.rb +232 -119
  40. data/lib/puma/cli.rb +33 -33
  41. data/lib/puma/client.rb +129 -88
  42. data/lib/puma/cluster/worker.rb +175 -0
  43. data/lib/puma/cluster/worker_handle.rb +97 -0
  44. data/lib/puma/cluster.rb +224 -231
  45. data/lib/puma/commonlogger.rb +2 -2
  46. data/lib/puma/configuration.rb +112 -87
  47. data/lib/puma/const.rb +86 -91
  48. data/lib/puma/control_cli.rb +99 -79
  49. data/lib/puma/detect.rb +31 -2
  50. data/lib/puma/dsl.rb +426 -110
  51. data/lib/puma/error_logger.rb +112 -0
  52. data/lib/puma/events.rb +16 -115
  53. data/lib/puma/io_buffer.rb +44 -2
  54. data/lib/puma/jruby_restart.rb +2 -59
  55. data/lib/puma/json_serialization.rb +96 -0
  56. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  57. data/lib/puma/launcher.rb +170 -148
  58. data/lib/puma/log_writer.rb +137 -0
  59. data/lib/puma/minissl/context_builder.rb +35 -19
  60. data/lib/puma/minissl.rb +213 -55
  61. data/lib/puma/null_io.rb +18 -1
  62. data/lib/puma/plugin/tmp_restart.rb +1 -1
  63. data/lib/puma/plugin.rb +3 -12
  64. data/lib/puma/rack/builder.rb +5 -9
  65. data/lib/puma/rack_default.rb +1 -1
  66. data/lib/puma/reactor.rb +85 -369
  67. data/lib/puma/request.rb +644 -0
  68. data/lib/puma/runner.rb +86 -76
  69. data/lib/puma/server.rb +306 -793
  70. data/lib/puma/single.rb +18 -74
  71. data/lib/puma/state_file.rb +45 -8
  72. data/lib/puma/systemd.rb +47 -0
  73. data/lib/puma/thread_pool.rb +136 -68
  74. data/lib/puma/util.rb +21 -4
  75. data/lib/puma.rb +54 -7
  76. data/lib/rack/handler/puma.rb +11 -12
  77. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  78. metadata +31 -23
  79. data/docs/tcp_mode.md +0 -96
  80. data/ext/puma_http11/io_buffer.c +0 -155
  81. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  82. data/lib/puma/accept_nonblock.rb +0 -29
  83. data/lib/puma/tcp_logger.rb +0 -41
  84. data/tools/jungle/README.md +0 -19
  85. data/tools/jungle/init.d/README.md +0 -61
  86. data/tools/jungle/init.d/puma +0 -421
  87. data/tools/jungle/init.d/run-puma +0 -18
  88. data/tools/jungle/upstart/README.md +0 -61
  89. data/tools/jungle/upstart/puma-manager.conf +0 -31
  90. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/binder.rb CHANGED
@@ -3,18 +3,25 @@
3
3
  require 'uri'
4
4
  require 'socket'
5
5
 
6
- require 'puma/const'
7
- require 'puma/util'
8
- require 'puma/minissl/context_builder'
6
+ require_relative 'const'
7
+ require_relative 'util'
8
+ require_relative 'configuration'
9
9
 
10
10
  module Puma
11
+
12
+ if HAS_SSL
13
+ require_relative 'minissl'
14
+ require_relative 'minissl/context_builder'
15
+ end
16
+
11
17
  class Binder
12
18
  include Puma::Const
13
19
 
14
- RACK_VERSION = [1,3].freeze
20
+ RACK_VERSION = [1,6].freeze
15
21
 
16
- def initialize(events)
17
- @events = events
22
+ def initialize(log_writer, conf = Configuration.new)
23
+ @log_writer = log_writer
24
+ @conf = conf
18
25
  @listeners = []
19
26
  @inherited_fds = {}
20
27
  @activated_sockets = {}
@@ -22,10 +29,11 @@ module Puma
22
29
 
23
30
  @proto_env = {
24
31
  "rack.version".freeze => RACK_VERSION,
25
- "rack.errors".freeze => events.stderr,
26
- "rack.multithread".freeze => true,
27
- "rack.multiprocess".freeze => false,
32
+ "rack.errors".freeze => log_writer.stderr,
33
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
34
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
28
35
  "rack.run_once".freeze => false,
36
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
29
37
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
38
 
31
39
  # I'd like to set a default CONTENT_TYPE here but some things
@@ -34,17 +42,23 @@ module Puma
34
42
  # infer properly.
35
43
 
36
44
  "QUERY_STRING".freeze => "",
37
- SERVER_PROTOCOL => HTTP_11,
38
45
  SERVER_SOFTWARE => PUMA_SERVER_STRING,
39
46
  GATEWAY_INTERFACE => CGI_VER
40
47
  }
41
48
 
42
49
  @envs = {}
43
50
  @ios = []
51
+ localhost_authority
44
52
  end
45
53
 
46
54
  attr_reader :ios
47
55
 
56
+ # @version 5.0.0
57
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
58
+
59
+ # @version 5.0.0
60
+ attr_writer :ios, :listeners
61
+
48
62
  def env(sock)
49
63
  @envs.fetch(sock, @proto_env)
50
64
  end
@@ -53,80 +67,127 @@ module Puma
53
67
  @ios.each { |i| i.close }
54
68
  end
55
69
 
56
- def import_from_env
57
- remove = []
58
-
59
- ENV.each do |k,v|
60
- if k =~ /PUMA_INHERIT_\d+/
61
- fd, url = v.split(":", 2)
62
- @inherited_fds[url] = fd.to_i
63
- remove << k
64
- elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
65
- v.to_i.times do |num|
66
- fd = num + 3
67
- sock = TCPServer.for_fd(fd)
68
- begin
69
- key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
70
- rescue ArgumentError
71
- port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
72
- if addr =~ /\:/
73
- addr = "[#{addr}]"
74
- end
75
- key = [ :tcp, addr, port ]
76
- end
77
- @activated_sockets[key] = sock
78
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
79
- end
80
- remove << k << 'LISTEN_PID'
70
+ # @!attribute [r] connected_ports
71
+ # @version 5.0.0
72
+ def connected_ports
73
+ t = ios.map { |io| io.addr[1] }; t.uniq!; t
74
+ end
75
+
76
+ # @version 5.0.0
77
+ def create_inherited_fds(env_hash)
78
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
79
+ fd, url = v.split(":", 2)
80
+ @inherited_fds[url] = fd.to_i
81
+ end.keys # pass keys back for removal
82
+ end
83
+
84
+ # systemd socket activation.
85
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
86
+ # LISTEN_PID = PID of the service process, aka us
87
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
88
+ # @version 5.0.0
89
+ #
90
+ def create_activated_fds(env_hash)
91
+ @log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
92
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
93
+ env_hash['LISTEN_FDS'].to_i.times do |index|
94
+ sock = TCPServer.for_fd(socket_activation_fd(index))
95
+ key = begin # Try to parse as a path
96
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
97
+ rescue ArgumentError # Try to parse as a port/ip
98
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
99
+ addr = "[#{addr}]" if addr&.include? ':'
100
+ [:tcp, addr, port]
81
101
  end
102
+ @activated_sockets[key] = sock
103
+ @log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
82
104
  end
105
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
106
+ end
107
+
108
+ # Synthesize binds from systemd socket activation
109
+ #
110
+ # When systemd socket activation is enabled, it can be tedious to keep the
111
+ # binds in sync. This method can synthesize any binds based on the received
112
+ # activated sockets. Any existing matching binds will be respected.
113
+ #
114
+ # When only_matching is true in, all binds that do not match an activated
115
+ # socket is removed in place.
116
+ #
117
+ # It's a noop if no activated sockets were received.
118
+ def synthesize_binds_from_activated_fs(binds, only_matching)
119
+ return binds unless activated_sockets.any?
120
+
121
+ activated_binds = []
122
+
123
+ activated_sockets.keys.each do |proto, addr, port|
124
+ if port
125
+ tcp_url = "#{proto}://#{addr}:#{port}"
126
+ ssl_url = "ssl://#{addr}:#{port}"
127
+ ssl_url_prefix = "#{ssl_url}?"
128
+
129
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
83
130
 
84
- remove.each do |k|
85
- ENV.delete k
131
+ activated_binds << (existing || tcp_url)
132
+ else
133
+ # TODO: can there be a SSL bind without a port?
134
+ activated_binds << "#{proto}://#{addr}"
135
+ end
136
+ end
137
+
138
+ if only_matching
139
+ activated_binds
140
+ else
141
+ binds | activated_binds
86
142
  end
87
143
  end
88
144
 
89
- def parse(binds, logger)
145
+ def parse(binds, log_writer = nil, log_msg = 'Listening')
146
+ log_writer ||= @log_writer
90
147
  binds.each do |str|
91
148
  uri = URI.parse str
92
149
  case uri.scheme
93
150
  when "tcp"
94
151
  if fd = @inherited_fds.delete(str)
95
152
  io = inherit_tcp_listener uri.host, uri.port, fd
96
- logger.log "* Inherited #{str}"
153
+ log_writer.log "* Inherited #{str}"
97
154
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
98
155
  io = inherit_tcp_listener uri.host, uri.port, sock
99
- logger.log "* Activated #{str}"
156
+ log_writer.log "* Activated #{str}"
100
157
  else
158
+ ios_len = @ios.length
101
159
  params = Util.parse_query uri.query
102
160
 
103
- opt = params.key?('low_latency')
104
- bak = params.fetch('backlog', 1024).to_i
161
+ opt = params.key?('low_latency') && params['low_latency'] != 'false'
162
+ backlog = params.fetch('backlog', 1024).to_i
105
163
 
106
- io = add_tcp_listener uri.host, uri.port, opt, bak
164
+ io = add_tcp_listener uri.host, uri.port, opt, backlog
107
165
 
108
- @ios.each do |i|
109
- next unless TCPServer === i
110
- addr = if i.local_address.ipv6?
111
- "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
112
- else
113
- i.local_address.ip_unpack.join(':')
114
- end
115
-
116
- logger.log "* Listening on tcp://#{addr}"
166
+ @ios[ios_len..-1].each do |i|
167
+ addr = loc_addr_str i
168
+ log_writer.log "* #{log_msg} on http://#{addr}"
117
169
  end
118
170
  end
119
171
 
120
172
  @listeners << [str, io] if io
121
173
  when "unix"
122
174
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
175
+ abstract = false
176
+ if str.start_with? 'unix://@'
177
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
178
+ abstract = true
179
+ path = "@#{path}"
180
+ end
123
181
 
124
182
  if fd = @inherited_fds.delete(str)
183
+ @unix_paths << path unless abstract || File.exist?(path)
125
184
  io = inherit_unix_listener path, fd
126
- logger.log "* Inherited #{str}"
127
- elsif sock = @activated_sockets.delete([ :unix, path ])
185
+ log_writer.log "* Inherited #{str}"
186
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
187
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
188
+ @unix_paths << path unless abstract || File.exist?(path)
128
189
  io = inherit_unix_listener path, sock
129
- logger.log "* Activated #{str}"
190
+ log_writer.log "* Activated #{str}"
130
191
  else
131
192
  umask = nil
132
193
  mode = nil
@@ -148,36 +209,66 @@ module Puma
148
209
  end
149
210
  end
150
211
 
212
+ @unix_paths << path unless abstract || File.exist?(path)
151
213
  io = add_unix_listener path, umask, mode, backlog
152
- logger.log "* Listening on #{str}"
214
+ log_writer.log "* #{log_msg} on #{str}"
153
215
  end
154
216
 
155
217
  @listeners << [str, io]
156
218
  when "ssl"
219
+ cert_key = %w[cert key]
220
+
221
+ raise "Puma compiled without SSL support" unless HAS_SSL
222
+
157
223
  params = Util.parse_query uri.query
158
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
224
+
225
+ # If key and certs are not defined and localhost gem is required.
226
+ # localhost gem will be used for self signed
227
+ # Load localhost authority if not loaded.
228
+ # Ruby 3 `values_at` accepts an array, earlier do not
229
+ if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
230
+ ctx = localhost_authority && localhost_authority_context
231
+ end
232
+
233
+ ctx ||=
234
+ begin
235
+ # Extract cert_pem and key_pem from options[:store] if present
236
+ cert_key.each do |v|
237
+ if params[v]&.start_with?('store:')
238
+ index = Integer(params.delete(v).split('store:').last)
239
+ params["#{v}_pem"] = @conf.options[:store][index]
240
+ end
241
+ end
242
+ MiniSSL::ContextBuilder.new(params, @log_writer).context
243
+ end
159
244
 
160
245
  if fd = @inherited_fds.delete(str)
161
- logger.log "* Inherited #{str}"
246
+ log_writer.log "* Inherited #{str}"
162
247
  io = inherit_ssl_listener fd, ctx
163
248
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
164
249
  io = inherit_ssl_listener sock, ctx
165
- logger.log "* Activated #{str}"
250
+ log_writer.log "* Activated #{str}"
166
251
  else
167
- io = add_ssl_listener uri.host, uri.port, ctx
168
- logger.log "* Listening on #{str}"
252
+ ios_len = @ios.length
253
+ backlog = params.fetch('backlog', 1024).to_i
254
+ io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
255
+
256
+ @ios[ios_len..-1].each do |i|
257
+ addr = loc_addr_str i
258
+ log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
259
+ end
169
260
  end
170
261
 
171
262
  @listeners << [str, io] if io
172
263
  else
173
- logger.error "Invalid URI: #{str}"
264
+ log_writer.error "Invalid URI: #{str}"
174
265
  end
175
266
  end
176
267
 
177
268
  # If we inherited fds but didn't use them (because of a
178
269
  # configuration change), then be sure to close them.
179
270
  @inherited_fds.each do |str, fd|
180
- logger.log "* Closing unused inherited connection: #{str}"
271
+ log_writer.log "* Closing unused inherited connection: #{str}"
181
272
 
182
273
  begin
183
274
  IO.for_fd(fd).close
@@ -193,21 +284,35 @@ module Puma
193
284
  end
194
285
 
195
286
  # Also close any unused activated sockets
196
- @activated_sockets.each do |key, sock|
197
- logger.log "* Closing unused activated socket: #{key.join ':'}"
198
- begin
199
- sock.close
200
- rescue SystemCallError
287
+ unless @activated_sockets.empty?
288
+ fds = @ios.map(&:to_i)
289
+ @activated_sockets.each do |key, sock|
290
+ next if fds.include? sock.to_i
291
+ log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
292
+ begin
293
+ sock.close
294
+ rescue SystemCallError
295
+ end
296
+ # We have to unlink a unix socket path that's not being used
297
+ File.unlink key[1] if key.first == :unix
201
298
  end
202
- # We have to unlink a unix socket path that's not being used
203
- File.unlink key[1] if key[0] == :unix
204
299
  end
205
300
  end
206
301
 
207
- def loopback_addresses
208
- Socket.ip_address_list.select do |addrinfo|
209
- addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
210
- end.map { |addrinfo| addrinfo.ip_address }.uniq
302
+ def localhost_authority
303
+ @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
304
+ end
305
+
306
+ def localhost_authority_context
307
+ return unless localhost_authority
308
+
309
+ key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
310
+ [localhost_authority.key_path, localhost_authority.certificate_path]
311
+ else
312
+ local_certificates_path = File.expand_path("~/.localhost")
313
+ [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
314
+ end
315
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
211
316
  end
212
317
 
213
318
  # Tell the server to listen on host +host+, port +port+.
@@ -226,26 +331,20 @@ module Puma
226
331
  end
227
332
 
228
333
  host = host[1..-2] if host and host[0..0] == '['
229
- s = TCPServer.new(host, port)
334
+ tcp_server = TCPServer.new(host, port)
335
+
230
336
  if optimize_for_latency
231
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
337
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
232
338
  end
233
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
234
- s.listen backlog
235
- @connected_port = s.addr[1]
339
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
340
+ tcp_server.listen backlog
236
341
 
237
- @ios << s
238
- s
342
+ @ios << tcp_server
343
+ tcp_server
239
344
  end
240
345
 
241
- attr_reader :connected_port
242
-
243
346
  def inherit_tcp_listener(host, port, fd)
244
- if fd.kind_of? TCPServer
245
- s = fd
246
- else
247
- s = TCPServer.for_fd(fd)
248
- end
347
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
249
348
 
250
349
  @ios << s
251
350
  s
@@ -253,9 +352,10 @@ module Puma
253
352
 
254
353
  def add_ssl_listener(host, port, ctx,
255
354
  optimize_for_latency=true, backlog=1024)
256
- require 'puma/minissl'
257
355
 
258
- MiniSSL.check
356
+ raise "Puma compiled without SSL support" unless HAS_SSL
357
+ # Puma will try to use local authority context if context is supplied nil
358
+ ctx ||= localhost_authority_context
259
359
 
260
360
  if host == "localhost"
261
361
  loopback_addresses.each do |addr|
@@ -272,7 +372,6 @@ module Puma
272
372
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
273
373
  s.listen backlog
274
374
 
275
-
276
375
  ssl = MiniSSL::Server.new s, ctx
277
376
  env = @proto_env.dup
278
377
  env[HTTPS_KEY] = HTTPS
@@ -283,14 +382,12 @@ module Puma
283
382
  end
284
383
 
285
384
  def inherit_ssl_listener(fd, ctx)
286
- require 'puma/minissl'
287
- MiniSSL.check
385
+ raise "Puma compiled without SSL support" unless HAS_SSL
386
+ # Puma will try to use local authority context if context is supplied nil
387
+ ctx ||= localhost_authority_context
388
+
389
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
288
390
 
289
- if fd.kind_of? TCPServer
290
- s = fd
291
- else
292
- s = TCPServer.for_fd(fd)
293
- end
294
391
  ssl = MiniSSL::Server.new(s, ctx)
295
392
 
296
393
  env = @proto_env.dup
@@ -305,8 +402,6 @@ module Puma
305
402
  # Tell the server to listen on +path+ as a UNIX domain socket.
306
403
  #
307
404
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
308
- @unix_paths << path unless File.exist? path
309
-
310
405
  # Let anyone connect by default
311
406
  umask ||= 0
312
407
 
@@ -323,8 +418,7 @@ module Puma
323
418
  raise "There is already a server bound to: #{path}"
324
419
  end
325
420
  end
326
-
327
- s = UNIXServer.new(path)
421
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
328
422
  s.listen backlog
329
423
  @ios << s
330
424
  ensure
@@ -343,13 +437,8 @@ module Puma
343
437
  end
344
438
 
345
439
  def inherit_unix_listener(path, fd)
346
- @unix_paths << path unless File.exist? path
440
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
347
441
 
348
- if fd.kind_of? TCPServer
349
- s = fd
350
- else
351
- s = UNIXServer.for_fd fd
352
- end
353
442
  @ios << s
354
443
 
355
444
  env = @proto_env.dup
@@ -361,25 +450,49 @@ module Puma
361
450
 
362
451
  def close_listeners
363
452
  @listeners.each do |l, io|
364
- io.close
365
- uri = URI.parse(l)
453
+ io.close unless io.closed?
454
+ uri = URI.parse l
366
455
  next unless uri.scheme == 'unix'
367
456
  unix_path = "#{uri.host}#{uri.path}"
368
- File.unlink unix_path if @unix_paths.include? unix_path
457
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
369
458
  end
370
459
  end
371
460
 
372
- def close_unix_paths
373
- @unix_paths.each { |up| File.unlink(up) if File.exist? up }
461
+ def redirects_for_restart
462
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
463
+ redirects[:close_others] = true
464
+ redirects
374
465
  end
375
466
 
376
- def redirects_for_restart
377
- redirects = {:close_others => true}
378
- @listeners.each_with_index do |(l, io), i|
379
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
380
- redirects[io.to_i] = io.to_i
467
+ # @version 5.0.0
468
+ def redirects_for_restart_env
469
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
470
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
381
471
  end
382
- redirects
472
+ end
473
+
474
+ private
475
+
476
+ # @!attribute [r] loopback_addresses
477
+ def loopback_addresses
478
+ t = Socket.ip_address_list.select do |addrinfo|
479
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
480
+ end
481
+ t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
482
+ end
483
+
484
+ def loc_addr_str(io)
485
+ loc_addr = io.to_io.local_address
486
+ if loc_addr.ipv6?
487
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
488
+ else
489
+ loc_addr.ip_unpack.join(':')
490
+ end
491
+ end
492
+
493
+ # @version 5.0.0
494
+ def socket_activation_fd(int)
495
+ int + 3 # 3 is the magic number you add to follow the SA protocol
383
496
  end
384
497
  end
385
498
  end
data/lib/puma/cli.rb CHANGED
@@ -3,36 +3,31 @@
3
3
  require 'optparse'
4
4
  require 'uri'
5
5
 
6
- require 'puma'
7
- require 'puma/configuration'
8
- require 'puma/launcher'
9
- require 'puma/const'
10
- require 'puma/events'
6
+ require_relative '../puma'
7
+ require_relative 'configuration'
8
+ require_relative 'launcher'
9
+ require_relative 'const'
10
+ require_relative 'log_writer'
11
11
 
12
12
  module Puma
13
13
  class << self
14
- # The CLI exports its Puma::Configuration object here to allow
15
- # apps to pick it up. An app needs to use it conditionally though
16
- # since it is not set if the app is launched via another
17
- # mechanism than the CLI class.
14
+ # The CLI exports a Puma::Configuration instance here to allow
15
+ # apps to pick it up. An app must load this object conditionally
16
+ # because it is not set if the app is launched via any mechanism
17
+ # other than the CLI class.
18
18
  attr_accessor :cli_config
19
19
  end
20
20
 
21
21
  # Handles invoke a Puma::Server in a command line style.
22
22
  #
23
23
  class CLI
24
- KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
25
-
26
24
  # Create a new CLI object using +argv+ as the command line
27
25
  # arguments.
28
26
  #
29
- # +stdout+ and +stderr+ can be set to IO-like objects which
30
- # this object will report status on.
31
- #
32
- def initialize(argv, events=Events.stdio)
27
+ def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
33
28
  @debug = false
34
29
  @argv = argv.dup
35
-
30
+ @log_writer = log_writer
36
31
  @events = events
37
32
 
38
33
  @conf = nil
@@ -68,7 +63,7 @@ module Puma
68
63
  end
69
64
  end
70
65
 
71
- @launcher = Puma::Launcher.new(@conf, :events => @events, :argv => argv)
66
+ @launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
72
67
  end
73
68
 
74
69
  attr_reader :launcher
@@ -80,9 +75,9 @@ module Puma
80
75
  @launcher.run
81
76
  end
82
77
 
83
- private
78
+ private
84
79
  def unsupported(str)
85
- @events.error(str)
80
+ @log_writer.error(str)
86
81
  raise UnsupportedOption
87
82
  end
88
83
 
@@ -104,16 +99,20 @@ module Puma
104
99
  user_config.bind arg
105
100
  end
106
101
 
102
+ o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
103
+ user_config.bind_to_activated_sockets(arg || true)
104
+ end
105
+
107
106
  o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
108
107
  file_config.load arg
109
108
  end
110
109
 
111
- o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
112
- configure_control_url(arg)
110
+ # Identical to supplying --config "-", but more semantic
111
+ o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
112
+ file_config.load "-"
113
113
  end
114
114
 
115
- # alias --control-url for backwards-compatibility
116
- o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
115
+ o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
117
116
  configure_control_url(arg)
118
117
  end
119
118
 
@@ -122,11 +121,6 @@ module Puma
122
121
  @control_options[:auth_token] = arg
123
122
  end
124
123
 
125
- o.on "-d", "--daemon", "Daemonize the server into the background" do
126
- user_config.daemonize
127
- user_config.quiet
128
- end
129
-
130
124
  o.on "--debug", "Log lowlevel debugging information" do
131
125
  user_config.debug
132
126
  end
@@ -140,13 +134,19 @@ module Puma
140
134
  user_config.environment arg
141
135
  end
142
136
 
137
+ o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
138
+ "Fork new workers from existing worker. Cluster mode only",
139
+ "Auto-refork after REQUESTS (default 1000)" do |*args|
140
+ user_config.fork_worker(*args.compact)
141
+ end
142
+
143
143
  o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
144
144
  $LOAD_PATH.unshift(*arg.split(':'))
145
145
  end
146
146
 
147
147
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
148
148
  "Use -b for more advanced options" do |arg|
149
- user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
149
+ user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
150
150
  end
151
151
 
152
152
  o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
@@ -179,6 +179,10 @@ module Puma
179
179
  user_config.restart_command cmd
180
180
  end
181
181
 
182
+ o.on "-s", "--silent", "Do not log prompt messages other than errors" do
183
+ @log_writer = LogWriter.new(NullIO.new, $stderr)
184
+ end
185
+
182
186
  o.on "-S", "--state PATH", "Where to store the state details" do |arg|
183
187
  user_config.state_path arg
184
188
  end
@@ -192,10 +196,6 @@ module Puma
192
196
  end
193
197
  end
194
198
 
195
- o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
196
- user_config.tcp_mode!
197
- end
198
-
199
199
  o.on "--early-hints", "Enable early hints support" do
200
200
  user_config.early_hints
201
201
  end