puma 5.2.2 → 6.3.0

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +483 -4
  3. data/README.md +101 -20
  4. data/bin/puma-wild +1 -1
  5. data/docs/architecture.md +50 -16
  6. data/docs/compile_options.md +38 -2
  7. data/docs/deployment.md +53 -67
  8. data/docs/fork_worker.md +1 -3
  9. data/docs/jungle/rc.d/README.md +1 -1
  10. data/docs/kubernetes.md +1 -1
  11. data/docs/nginx.md +1 -1
  12. data/docs/plugins.md +15 -15
  13. data/docs/rails_dev_mode.md +2 -3
  14. data/docs/restart.md +7 -7
  15. data/docs/signals.md +11 -10
  16. data/docs/stats.md +8 -8
  17. data/docs/systemd.md +65 -69
  18. data/docs/testing_benchmarks_local_files.md +150 -0
  19. data/docs/testing_test_rackup_ci_files.md +36 -0
  20. data/ext/puma_http11/extconf.rb +44 -13
  21. data/ext/puma_http11/http11_parser.c +24 -11
  22. data/ext/puma_http11/http11_parser.h +2 -2
  23. data/ext/puma_http11/http11_parser.java.rl +2 -2
  24. data/ext/puma_http11/http11_parser.rl +2 -2
  25. data/ext/puma_http11/http11_parser_common.rl +3 -3
  26. data/ext/puma_http11/mini_ssl.c +150 -23
  27. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  28. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
  29. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  30. data/ext/puma_http11/puma_http11.c +18 -10
  31. data/lib/puma/app/status.rb +10 -7
  32. data/lib/puma/binder.rb +112 -62
  33. data/lib/puma/cli.rb +24 -20
  34. data/lib/puma/client.rb +162 -36
  35. data/lib/puma/cluster/worker.rb +31 -27
  36. data/lib/puma/cluster/worker_handle.rb +12 -1
  37. data/lib/puma/cluster.rb +102 -61
  38. data/lib/puma/commonlogger.rb +21 -14
  39. data/lib/puma/configuration.rb +78 -54
  40. data/lib/puma/const.rb +135 -97
  41. data/lib/puma/control_cli.rb +25 -20
  42. data/lib/puma/detect.rb +12 -2
  43. data/lib/puma/dsl.rb +308 -58
  44. data/lib/puma/error_logger.rb +20 -11
  45. data/lib/puma/events.rb +6 -126
  46. data/lib/puma/io_buffer.rb +39 -4
  47. data/lib/puma/jruby_restart.rb +2 -1
  48. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  49. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  50. data/lib/puma/launcher.rb +114 -173
  51. data/lib/puma/log_writer.rb +147 -0
  52. data/lib/puma/minissl/context_builder.rb +30 -16
  53. data/lib/puma/minissl.rb +132 -38
  54. data/lib/puma/null_io.rb +5 -0
  55. data/lib/puma/plugin/systemd.rb +90 -0
  56. data/lib/puma/plugin/tmp_restart.rb +1 -1
  57. data/lib/puma/plugin.rb +2 -2
  58. data/lib/puma/rack/builder.rb +7 -7
  59. data/lib/puma/rack_default.rb +19 -4
  60. data/lib/puma/reactor.rb +19 -10
  61. data/lib/puma/request.rb +373 -153
  62. data/lib/puma/runner.rb +74 -28
  63. data/lib/puma/sd_notify.rb +149 -0
  64. data/lib/puma/server.rb +127 -136
  65. data/lib/puma/single.rb +13 -11
  66. data/lib/puma/state_file.rb +39 -7
  67. data/lib/puma/thread_pool.rb +33 -26
  68. data/lib/puma/util.rb +20 -15
  69. data/lib/puma.rb +28 -11
  70. data/lib/rack/handler/puma.rb +113 -86
  71. data/tools/Dockerfile +1 -1
  72. metadata +15 -10
  73. data/lib/puma/queue_close.rb +0 -26
  74. data/lib/puma/systemd.rb +0 -46
@@ -36,13 +36,13 @@ static VALUE global_request_method;
36
36
  static VALUE global_request_uri;
37
37
  static VALUE global_fragment;
38
38
  static VALUE global_query_string;
39
- static VALUE global_http_version;
39
+ static VALUE global_server_protocol;
40
40
  static VALUE global_request_path;
41
41
 
42
42
  /** Defines common length and error messages for input length validation. */
43
43
  #define QUOTE(s) #s
44
- #define EXPLAIN_MAX_LENGTH_VALUE(s) QUOTE(s)
45
- #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPLAIN_MAX_LENGTH_VALUE(length) " allowed length (was %d)"
44
+ #define EXPAND_MAX_LENGTH_VALUE(s) QUOTE(s)
45
+ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPAND_MAX_LENGTH_VALUE(length) " allowed length (was %d)"
46
46
 
47
47
  /** Validates the max length of given input and throws an HttpParserError exception if over. */
48
48
  #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
@@ -52,15 +52,23 @@ static VALUE global_request_path;
52
52
 
53
53
 
54
54
  /* Defines the maximum allowed lengths for various input elements.*/
55
+ #ifndef PUMA_REQUEST_URI_MAX_LENGTH
56
+ #define PUMA_REQUEST_URI_MAX_LENGTH (1024 * 12)
57
+ #endif
58
+
59
+ #ifndef PUMA_REQUEST_PATH_MAX_LENGTH
60
+ #define PUMA_REQUEST_PATH_MAX_LENGTH (8192)
61
+ #endif
62
+
55
63
  #ifndef PUMA_QUERY_STRING_MAX_LENGTH
56
64
  #define PUMA_QUERY_STRING_MAX_LENGTH (1024 * 10)
57
65
  #endif
58
66
 
59
67
  DEF_MAX_LENGTH(FIELD_NAME, 256);
60
68
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
61
- DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
69
+ DEF_MAX_LENGTH(REQUEST_URI, PUMA_REQUEST_URI_MAX_LENGTH);
62
70
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
63
- DEF_MAX_LENGTH(REQUEST_PATH, 8192);
71
+ DEF_MAX_LENGTH(REQUEST_PATH, PUMA_REQUEST_PATH_MAX_LENGTH);
64
72
  DEF_MAX_LENGTH(QUERY_STRING, PUMA_QUERY_STRING_MAX_LENGTH);
65
73
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
66
74
 
@@ -236,10 +244,10 @@ void query_string(puma_parser* hp, const char *at, size_t length)
236
244
  rb_hash_aset(hp->request, global_query_string, val);
237
245
  }
238
246
 
239
- void http_version(puma_parser* hp, const char *at, size_t length)
247
+ void server_protocol(puma_parser* hp, const char *at, size_t length)
240
248
  {
241
249
  VALUE val = rb_str_new(at, length);
242
- rb_hash_aset(hp->request, global_http_version, val);
250
+ rb_hash_aset(hp->request, global_server_protocol, val);
243
251
  }
244
252
 
245
253
  /** Finalizes the request header to have a bunch of stuff that's
@@ -281,7 +289,7 @@ VALUE HttpParser_alloc(VALUE klass)
281
289
  hp->fragment = fragment;
282
290
  hp->request_path = request_path;
283
291
  hp->query_string = query_string;
284
- hp->http_version = http_version;
292
+ hp->server_protocol = server_protocol;
285
293
  hp->header_done = header_done;
286
294
  hp->request = Qnil;
287
295
 
@@ -451,7 +459,7 @@ VALUE HttpParser_body(VALUE self) {
451
459
  void Init_mini_ssl(VALUE mod);
452
460
  #endif
453
461
 
454
- void Init_puma_http11()
462
+ void Init_puma_http11(void)
455
463
  {
456
464
 
457
465
  VALUE mPuma = rb_define_module("Puma");
@@ -461,7 +469,7 @@ void Init_puma_http11()
461
469
  DEF_GLOBAL(request_uri, "REQUEST_URI");
462
470
  DEF_GLOBAL(fragment, "FRAGMENT");
463
471
  DEF_GLOBAL(query_string, "QUERY_STRING");
464
- DEF_GLOBAL(http_version, "HTTP_VERSION");
472
+ DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
465
473
  DEF_GLOBAL(request_path, "REQUEST_PATH");
466
474
 
467
475
  eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'puma/json'
2
+ require_relative '../json_serialization'
3
3
 
4
4
  module Puma
5
5
  module App
@@ -39,6 +39,9 @@ module Puma
39
39
  when 'phased-restart'
40
40
  @launcher.phased_restart ? 200 : 404
41
41
 
42
+ when 'refork'
43
+ @launcher.refork ? 200 : 404
44
+
42
45
  when 'reload-worker-directory'
43
46
  @launcher.send(:reload_worker_directory) ? 200 : 404
44
47
 
@@ -46,17 +49,17 @@ module Puma
46
49
  GC.start ; 200
47
50
 
48
51
  when 'gc-stats'
49
- Puma::JSON.generate GC.stat
52
+ Puma::JSONSerialization.generate GC.stat
50
53
 
51
54
  when 'stats'
52
- Puma::JSON.generate @launcher.stats
55
+ Puma::JSONSerialization.generate @launcher.stats
53
56
 
54
57
  when 'thread-backtraces'
55
58
  backtraces = []
56
59
  @launcher.thread_status do |name, backtrace|
57
60
  backtraces << { name: name, backtrace: backtrace }
58
61
  end
59
- Puma::JSON.generate backtraces
62
+ Puma::JSONSerialization.generate backtraces
60
63
 
61
64
  else
62
65
  return rack_response(404, "Unsupported action", 'text/plain')
@@ -77,13 +80,13 @@ module Puma
77
80
 
78
81
  def authenticate(env)
79
82
  return true unless @auth_token
80
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
83
+ env['QUERY_STRING'].to_s.split('&;').include? "token=#{@auth_token}"
81
84
  end
82
85
 
83
86
  def rack_response(status, body, content_type='application/json')
84
87
  headers = {
85
- 'Content-Type' => content_type,
86
- 'Content-Length' => body.bytesize.to_s
88
+ 'content-type' => content_type,
89
+ 'content-length' => body.bytesize.to_s
87
90
  }
88
91
 
89
92
  [status, headers, [body]]
data/lib/puma/binder.rb CHANGED
@@ -3,24 +3,15 @@
3
3
  require 'uri'
4
4
  require 'socket'
5
5
 
6
- require 'puma/const'
7
- require 'puma/util'
8
- require 'puma/configuration'
6
+ require_relative 'const'
7
+ require_relative 'util'
8
+ require_relative 'configuration'
9
9
 
10
10
  module Puma
11
11
 
12
12
  if HAS_SSL
13
- require 'puma/minissl'
14
- require 'puma/minissl/context_builder'
15
-
16
- # Odd bug in 'pure Ruby' nio4r verion 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
13
+ require_relative 'minissl'
14
+ require_relative 'minissl/context_builder'
24
15
  end
25
16
 
26
17
  class Binder
@@ -28,8 +19,9 @@ module Puma
28
19
 
29
20
  RACK_VERSION = [1,6].freeze
30
21
 
31
- def initialize(events, conf = Configuration.new)
32
- @events = events
22
+ def initialize(log_writer, conf = Configuration.new)
23
+ @log_writer = log_writer
24
+ @conf = conf
33
25
  @listeners = []
34
26
  @inherited_fds = {}
35
27
  @activated_sockets = {}
@@ -37,10 +29,11 @@ module Puma
37
29
 
38
30
  @proto_env = {
39
31
  "rack.version".freeze => RACK_VERSION,
40
- "rack.errors".freeze => events.stderr,
32
+ "rack.errors".freeze => log_writer.stderr,
41
33
  "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
34
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
43
35
  "rack.run_once".freeze => false,
36
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
44
37
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
45
38
 
46
39
  # I'd like to set a default CONTENT_TYPE here but some things
@@ -49,7 +42,6 @@ module Puma
49
42
  # infer properly.
50
43
 
51
44
  "QUERY_STRING".freeze => "",
52
- SERVER_PROTOCOL => HTTP_11,
53
45
  SERVER_SOFTWARE => PUMA_SERVER_STRING,
54
46
  GATEWAY_INTERFACE => CGI_VER
55
47
  }
@@ -77,7 +69,7 @@ module Puma
77
69
  # @!attribute [r] connected_ports
78
70
  # @version 5.0.0
79
71
  def connected_ports
80
- ios.map { |io| io.addr[1] }.uniq
72
+ t = ios.map { |io| io.addr[1] }; t.uniq!; t
81
73
  end
82
74
 
83
75
  # @version 5.0.0
@@ -95,6 +87,7 @@ module Puma
95
87
  # @version 5.0.0
96
88
  #
97
89
  def create_activated_fds(env_hash)
90
+ @log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
98
91
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
99
92
  env_hash['LISTEN_FDS'].to_i.times do |index|
100
93
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -102,11 +95,11 @@ module Puma
102
95
  [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
103
96
  rescue ArgumentError # Try to parse as a port/ip
104
97
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
105
- addr = "[#{addr}]" if addr =~ /\:/
98
+ addr = "[#{addr}]" if addr&.include? ':'
106
99
  [:tcp, addr, port]
107
100
  end
108
101
  @activated_sockets[key] = sock
109
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
102
+ @log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
110
103
  end
111
104
  ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
112
105
  end
@@ -148,42 +141,52 @@ module Puma
148
141
  end
149
142
  end
150
143
 
151
- def parse(binds, logger, log_msg = 'Listening')
144
+ def parse(binds, log_writer = nil, log_msg = 'Listening')
145
+ log_writer ||= @log_writer
152
146
  binds.each do |str|
153
147
  uri = URI.parse str
154
148
  case uri.scheme
155
149
  when "tcp"
156
150
  if fd = @inherited_fds.delete(str)
157
151
  io = inherit_tcp_listener uri.host, uri.port, fd
158
- logger.log "* Inherited #{str}"
152
+ log_writer.log "* Inherited #{str}"
159
153
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
160
154
  io = inherit_tcp_listener uri.host, uri.port, sock
161
- logger.log "* Activated #{str}"
155
+ log_writer.log "* Activated #{str}"
162
156
  else
163
157
  ios_len = @ios.length
164
158
  params = Util.parse_query uri.query
165
159
 
166
- opt = params.key?('low_latency')
167
- bak = params.fetch('backlog', 1024).to_i
160
+ low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
161
+ backlog = params.fetch('backlog', 1024).to_i
168
162
 
169
- io = add_tcp_listener uri.host, uri.port, opt, bak
163
+ io = add_tcp_listener uri.host, uri.port, low_latency, backlog
170
164
 
171
165
  @ios[ios_len..-1].each do |i|
172
166
  addr = loc_addr_str i
173
- logger.log "* #{log_msg} on http://#{addr}"
167
+ log_writer.log "* #{log_msg} on http://#{addr}"
174
168
  end
175
169
  end
176
170
 
177
171
  @listeners << [str, io] if io
178
172
  when "unix"
179
173
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
174
+ abstract = false
175
+ if str.start_with? 'unix://@'
176
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
177
+ abstract = true
178
+ path = "@#{path}"
179
+ end
180
180
 
181
181
  if fd = @inherited_fds.delete(str)
182
+ @unix_paths << path unless abstract || File.exist?(path)
182
183
  io = inherit_unix_listener path, fd
183
- logger.log "* Inherited #{str}"
184
- elsif sock = @activated_sockets.delete([ :unix, path ])
184
+ log_writer.log "* Inherited #{str}"
185
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
186
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
187
+ @unix_paths << path unless abstract || File.exist?(path)
185
188
  io = inherit_unix_listener path, sock
186
- logger.log "* Activated #{str}"
189
+ log_writer.log "* Activated #{str}"
187
190
  else
188
191
  umask = nil
189
192
  mode = nil
@@ -205,44 +208,67 @@ module Puma
205
208
  end
206
209
  end
207
210
 
211
+ @unix_paths << path unless abstract || File.exist?(path)
208
212
  io = add_unix_listener path, umask, mode, backlog
209
- logger.log "* #{log_msg} on #{str}"
213
+ log_writer.log "* #{log_msg} on #{str}"
210
214
  end
211
215
 
212
216
  @listeners << [str, io]
213
217
  when "ssl"
218
+ cert_key = %w[cert key]
214
219
 
215
220
  raise "Puma compiled without SSL support" unless HAS_SSL
216
221
 
217
222
  params = Util.parse_query uri.query
218
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
223
+
224
+ # If key and certs are not defined and localhost gem is required.
225
+ # localhost gem will be used for self signed
226
+ # Load localhost authority if not loaded.
227
+ # Ruby 3 `values_at` accepts an array, earlier do not
228
+ if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
229
+ ctx = localhost_authority && localhost_authority_context
230
+ end
231
+
232
+ ctx ||=
233
+ begin
234
+ # Extract cert_pem and key_pem from options[:store] if present
235
+ cert_key.each do |v|
236
+ if params[v]&.start_with?('store:')
237
+ index = Integer(params.delete(v).split('store:').last)
238
+ params["#{v}_pem"] = @conf.options[:store][index]
239
+ end
240
+ end
241
+ MiniSSL::ContextBuilder.new(params, @log_writer).context
242
+ end
219
243
 
220
244
  if fd = @inherited_fds.delete(str)
221
- logger.log "* Inherited #{str}"
245
+ log_writer.log "* Inherited #{str}"
222
246
  io = inherit_ssl_listener fd, ctx
223
247
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
224
248
  io = inherit_ssl_listener sock, ctx
225
- logger.log "* Activated #{str}"
249
+ log_writer.log "* Activated #{str}"
226
250
  else
227
251
  ios_len = @ios.length
228
- io = add_ssl_listener uri.host, uri.port, ctx
252
+ backlog = params.fetch('backlog', 1024).to_i
253
+ low_latency = params['low_latency'] != 'false'
254
+ io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
229
255
 
230
256
  @ios[ios_len..-1].each do |i|
231
257
  addr = loc_addr_str i
232
- logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
258
+ log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
233
259
  end
234
260
  end
235
261
 
236
262
  @listeners << [str, io] if io
237
263
  else
238
- logger.error "Invalid URI: #{str}"
264
+ log_writer.error "Invalid URI: #{str}"
239
265
  end
240
266
  end
241
267
 
242
268
  # If we inherited fds but didn't use them (because of a
243
269
  # configuration change), then be sure to close them.
244
270
  @inherited_fds.each do |str, fd|
245
- logger.log "* Closing unused inherited connection: #{str}"
271
+ log_writer.log "* Closing unused inherited connection: #{str}"
246
272
 
247
273
  begin
248
274
  IO.for_fd(fd).close
@@ -258,17 +284,37 @@ module Puma
258
284
  end
259
285
 
260
286
  # Also close any unused activated sockets
261
- @activated_sockets.each do |key, sock|
262
- logger.log "* Closing unused activated socket: #{key.join ':'}"
263
- begin
264
- sock.close
265
- 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
266
298
  end
267
- # We have to unlink a unix socket path that's not being used
268
- File.unlink key[1] if key[0] == :unix
269
299
  end
270
300
  end
271
301
 
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
316
+ end
317
+
272
318
  # Tell the server to listen on host +host+, port +port+.
273
319
  # If +optimize_for_latency+ is true (the default) then clients connecting
274
320
  # will be optimized for latency over throughput.
@@ -286,6 +332,7 @@ module Puma
286
332
 
287
333
  host = host[1..-2] if host and host[0..0] == '['
288
334
  tcp_server = TCPServer.new(host, port)
335
+
289
336
  if optimize_for_latency
290
337
  tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
291
338
  end
@@ -307,6 +354,8 @@ module Puma
307
354
  optimize_for_latency=true, backlog=1024)
308
355
 
309
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
310
359
 
311
360
  if host == "localhost"
312
361
  loopback_addresses.each do |addr|
@@ -334,6 +383,8 @@ module Puma
334
383
 
335
384
  def inherit_ssl_listener(fd, ctx)
336
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
337
388
 
338
389
  s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
339
390
 
@@ -351,8 +402,6 @@ module Puma
351
402
  # Tell the server to listen on +path+ as a UNIX domain socket.
352
403
  #
353
404
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
354
- @unix_paths << path unless File.exist? path
355
-
356
405
  # Let anyone connect by default
357
406
  umask ||= 0
358
407
 
@@ -369,8 +418,7 @@ module Puma
369
418
  raise "There is already a server bound to: #{path}"
370
419
  end
371
420
  end
372
-
373
- s = UNIXServer.new(path)
421
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
374
422
  s.listen backlog
375
423
  @ios << s
376
424
  ensure
@@ -389,8 +437,6 @@ module Puma
389
437
  end
390
438
 
391
439
  def inherit_unix_listener(path, fd)
392
- @unix_paths << path unless File.exist? path
393
-
394
440
  s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
395
441
 
396
442
  @ios << s
@@ -403,24 +449,27 @@ module Puma
403
449
  end
404
450
 
405
451
  def close_listeners
406
- listeners.each do |l, io|
407
- io.close unless io.closed? # Ruby 2.2 issue
408
- uri = URI.parse(l)
409
- next unless uri.scheme == 'unix'
410
- unix_path = "#{uri.host}#{uri.path}"
411
- File.unlink unix_path if unix_paths.include? unix_path
452
+ @listeners.each do |l, io|
453
+ begin
454
+ io.close unless io.closed?
455
+ uri = URI.parse l
456
+ next unless uri.scheme == 'unix'
457
+ unix_path = "#{uri.host}#{uri.path}"
458
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
459
+ rescue Errno::EBADF
460
+ end
412
461
  end
413
462
  end
414
463
 
415
464
  def redirects_for_restart
416
- redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
465
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
417
466
  redirects[:close_others] = true
418
467
  redirects
419
468
  end
420
469
 
421
470
  # @version 5.0.0
422
471
  def redirects_for_restart_env
423
- listeners.each_with_object({}).with_index do |(listen, memo), i|
472
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
424
473
  memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
425
474
  end
426
475
  end
@@ -429,9 +478,10 @@ module Puma
429
478
 
430
479
  # @!attribute [r] loopback_addresses
431
480
  def loopback_addresses
432
- Socket.ip_address_list.select do |addrinfo|
481
+ t = Socket.ip_address_list.select do |addrinfo|
433
482
  addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
434
- end.map { |addrinfo| addrinfo.ip_address }.uniq
483
+ end
484
+ t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
435
485
  end
436
486
 
437
487
  def loc_addr_str(io)
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
@@ -82,7 +77,7 @@ module Puma
82
77
 
83
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
 
@@ -98,7 +93,7 @@ module Puma
98
93
  #
99
94
 
100
95
  def setup_options
101
- @conf = Configuration.new do |user_config, file_config|
96
+ @conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
102
97
  @parser = OptionParser.new do |o|
103
98
  o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
104
99
  user_config.bind arg
@@ -112,6 +107,11 @@ module Puma
112
107
  file_config.load arg
113
108
  end
114
109
 
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
+ end
114
+
115
115
  o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
116
116
  configure_control_url(arg)
117
117
  end
@@ -146,7 +146,7 @@ module Puma
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