puma 4.1.1 → 5.0.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +149 -10
  3. data/LICENSE +23 -20
  4. data/README.md +30 -46
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/plugins.md +20 -10
  16. data/docs/signals.md +7 -6
  17. data/docs/systemd.md +1 -63
  18. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  19. data/ext/puma_http11/extconf.rb +6 -0
  20. data/ext/puma_http11/http11_parser.c +40 -63
  21. data/ext/puma_http11/http11_parser.java.rl +21 -37
  22. data/ext/puma_http11/http11_parser.rl +3 -1
  23. data/ext/puma_http11/http11_parser_common.rl +3 -3
  24. data/ext/puma_http11/mini_ssl.c +15 -2
  25. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  26. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  27. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
  28. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  29. data/ext/puma_http11/puma_http11.c +9 -38
  30. data/lib/puma.rb +23 -0
  31. data/lib/puma/app/status.rb +46 -30
  32. data/lib/puma/binder.rb +112 -124
  33. data/lib/puma/cli.rb +11 -15
  34. data/lib/puma/client.rb +250 -209
  35. data/lib/puma/cluster.rb +203 -85
  36. data/lib/puma/commonlogger.rb +2 -2
  37. data/lib/puma/configuration.rb +31 -42
  38. data/lib/puma/const.rb +24 -19
  39. data/lib/puma/control_cli.rb +46 -17
  40. data/lib/puma/detect.rb +17 -0
  41. data/lib/puma/dsl.rb +162 -70
  42. data/lib/puma/error_logger.rb +97 -0
  43. data/lib/puma/events.rb +35 -31
  44. data/lib/puma/io_buffer.rb +9 -2
  45. data/lib/puma/jruby_restart.rb +0 -58
  46. data/lib/puma/launcher.rb +117 -58
  47. data/lib/puma/minissl.rb +60 -18
  48. data/lib/puma/minissl/context_builder.rb +73 -0
  49. data/lib/puma/null_io.rb +1 -1
  50. data/lib/puma/plugin.rb +6 -12
  51. data/lib/puma/rack/builder.rb +0 -4
  52. data/lib/puma/reactor.rb +16 -9
  53. data/lib/puma/runner.rb +11 -32
  54. data/lib/puma/server.rb +173 -193
  55. data/lib/puma/single.rb +7 -64
  56. data/lib/puma/state_file.rb +6 -3
  57. data/lib/puma/thread_pool.rb +104 -81
  58. data/lib/rack/handler/puma.rb +1 -5
  59. data/tools/Dockerfile +16 -0
  60. data/tools/trickletest.rb +0 -1
  61. metadata +23 -24
  62. data/ext/puma_http11/io_buffer.c +0 -155
  63. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  64. data/lib/puma/convenient.rb +0 -25
  65. data/lib/puma/daemon_ext.rb +0 -33
  66. data/lib/puma/delegation.rb +0 -13
  67. data/lib/puma/tcp_logger.rb +0 -41
  68. data/tools/jungle/README.md +0 -19
  69. data/tools/jungle/init.d/README.md +0 -61
  70. data/tools/jungle/init.d/puma +0 -421
  71. data/tools/jungle/init.d/run-puma +0 -18
@@ -10,6 +10,7 @@
10
10
  #include "ext_help.h"
11
11
  #include <assert.h>
12
12
  #include <string.h>
13
+ #include <ctype.h>
13
14
  #include "http11_parser.h"
14
15
 
15
16
  #ifndef MANAGED_STRINGS
@@ -53,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
53
54
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
54
55
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
55
56
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
56
- DEF_MAX_LENGTH(REQUEST_PATH, 2048);
57
+ DEF_MAX_LENGTH(REQUEST_PATH, 8192);
57
58
  DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
58
59
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
59
60
 
@@ -111,21 +112,6 @@ static struct common_field common_http_fields[] = {
111
112
  # undef f
112
113
  };
113
114
 
114
- /*
115
- * qsort(3) and bsearch(3) improve average performance slightly, but may
116
- * not be worth it for lack of portability to certain platforms...
117
- */
118
- #if defined(HAVE_QSORT_BSEARCH)
119
- /* sort by length, then by name if there's a tie */
120
- static int common_field_cmp(const void *a, const void *b)
121
- {
122
- struct common_field *cfa = (struct common_field *)a;
123
- struct common_field *cfb = (struct common_field *)b;
124
- signed long diff = cfa->len - cfb->len;
125
- return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
126
- }
127
- #endif /* HAVE_QSORT_BSEARCH */
128
-
129
115
  static void init_common_fields(void)
130
116
  {
131
117
  unsigned i;
@@ -142,28 +128,10 @@ static void init_common_fields(void)
142
128
  }
143
129
  rb_global_variable(&cf->value);
144
130
  }
145
-
146
- #if defined(HAVE_QSORT_BSEARCH)
147
- qsort(common_http_fields,
148
- ARRAY_SIZE(common_http_fields),
149
- sizeof(struct common_field),
150
- common_field_cmp);
151
- #endif /* HAVE_QSORT_BSEARCH */
152
131
  }
153
132
 
154
133
  static VALUE find_common_field_value(const char *field, size_t flen)
155
134
  {
156
- #if defined(HAVE_QSORT_BSEARCH)
157
- struct common_field key;
158
- struct common_field *found;
159
- key.name = field;
160
- key.len = (signed long)flen;
161
- found = (struct common_field *)bsearch(&key, common_http_fields,
162
- ARRAY_SIZE(common_http_fields),
163
- sizeof(struct common_field),
164
- common_field_cmp);
165
- return found ? found->value : Qnil;
166
- #else /* !HAVE_QSORT_BSEARCH */
167
135
  unsigned i;
168
136
  struct common_field *cf = common_http_fields;
169
137
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
@@ -171,7 +139,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
171
139
  return cf->value;
172
140
  }
173
141
  return Qnil;
174
- #endif /* !HAVE_QSORT_BSEARCH */
175
142
  }
176
143
 
177
144
  void http_field(puma_parser* hp, const char *field, size_t flen,
@@ -200,6 +167,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
200
167
  f = rb_str_new(hp->buf, new_size);
201
168
  }
202
169
 
170
+ while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
171
+
203
172
  /* check for duplicate header */
204
173
  v = rb_hash_aref(hp->request, f);
205
174
 
@@ -398,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
398
367
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
399
368
 
400
369
  if(puma_parser_has_error(http)) {
401
- rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
370
+ rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
402
371
  } else {
403
372
  return INT2FIX(puma_parser_nread(http));
404
373
  }
@@ -465,8 +434,9 @@ VALUE HttpParser_body(VALUE self) {
465
434
  return http->body;
466
435
  }
467
436
 
468
- void Init_io_buffer(VALUE puma);
437
+ #ifdef HAVE_OPENSSL_BIO_H
469
438
  void Init_mini_ssl(VALUE mod);
439
+ #endif
470
440
 
471
441
  void Init_puma_http11()
472
442
  {
@@ -495,6 +465,7 @@ void Init_puma_http11()
495
465
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
496
466
  init_common_fields();
497
467
 
498
- Init_io_buffer(mPuma);
468
+ #ifdef HAVE_OPENSSL_BIO_H
499
469
  Init_mini_ssl(mPuma);
470
+ #endif
500
471
  }
@@ -10,6 +10,9 @@ require 'stringio'
10
10
 
11
11
  require 'thread'
12
12
 
13
+ require_relative 'puma/puma_http11'
14
+ require_relative 'puma/detect'
15
+
13
16
  module Puma
14
17
  autoload :Const, 'puma/const'
15
18
  autoload :Server, 'puma/server'
@@ -20,6 +23,26 @@ module Puma
20
23
  end
21
24
 
22
25
  def self.stats
26
+ require 'json'
27
+ @get_stats.stats.to_json
28
+ end
29
+
30
+ # @version 5.0.0
31
+ def self.stats_hash
23
32
  @get_stats.stats
24
33
  end
34
+
35
+ # Thread name is new in Ruby 2.3
36
+ def self.set_thread_name(name)
37
+ return unless Thread.current.respond_to?(:name=)
38
+ Thread.current.name = "puma #{name}"
39
+ end
40
+
41
+ unless HAS_SSL
42
+ module MiniSSL
43
+ # this class is defined so that it exists when Puma is compiled
44
+ # without ssl support, as Server and Reactor use it in rescue statements.
45
+ class SSLError < StandardError ; end
46
+ end
47
+ end
25
48
  end
@@ -1,32 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Puma
6
4
  module App
7
5
  # Check out {#call}'s source code to see what actions this web application
8
6
  # can respond to.
9
7
  class Status
10
- def initialize(cli)
11
- @cli = cli
12
- @auth_token = nil
13
- end
14
8
  OK_STATUS = '{ "status": "ok" }'.freeze
15
9
 
16
- attr_accessor :auth_token
17
-
18
- def authenticate(env)
19
- return true unless @auth_token
20
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
21
- end
22
-
23
- def rack_response(status, body, content_type='application/json')
24
- headers = {
25
- 'Content-Type' => content_type,
26
- 'Content-Length' => body.bytesize.to_s
27
- }
28
-
29
- [status, headers, [body]]
10
+ def initialize(cli, token = nil)
11
+ @cli = cli
12
+ @auth_token = token
30
13
  end
31
14
 
32
15
  def call(env)
@@ -34,46 +17,79 @@ module Puma
34
17
  return rack_response(403, 'Invalid auth token', 'text/plain')
35
18
  end
36
19
 
20
+ if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
+ require 'json'
22
+ end
23
+
37
24
  case env['PATH_INFO']
38
25
  when /\/stop$/
39
26
  @cli.stop
40
- return rack_response(200, OK_STATUS)
27
+ rack_response(200, OK_STATUS)
41
28
 
42
29
  when /\/halt$/
43
30
  @cli.halt
44
- return rack_response(200, OK_STATUS)
31
+ rack_response(200, OK_STATUS)
45
32
 
46
33
  when /\/restart$/
47
34
  @cli.restart
48
- return rack_response(200, OK_STATUS)
35
+ rack_response(200, OK_STATUS)
49
36
 
50
37
  when /\/phased-restart$/
51
38
  if !@cli.phased_restart
52
- return rack_response(404, '{ "error": "phased restart not available" }')
39
+ rack_response(404, '{ "error": "phased restart not available" }')
53
40
  else
54
- return rack_response(200, OK_STATUS)
41
+ rack_response(200, OK_STATUS)
55
42
  end
56
43
 
57
44
  when /\/reload-worker-directory$/
58
45
  if !@cli.send(:reload_worker_directory)
59
- return rack_response(404, '{ "error": "reload_worker_directory not available" }')
46
+ rack_response(404, '{ "error": "reload_worker_directory not available" }')
60
47
  else
61
- return rack_response(200, OK_STATUS)
48
+ rack_response(200, OK_STATUS)
62
49
  end
63
50
 
64
51
  when /\/gc$/
65
52
  GC.start
66
- return rack_response(200, OK_STATUS)
53
+ rack_response(200, OK_STATUS)
67
54
 
68
55
  when /\/gc-stats$/
69
- return rack_response(200, GC.stat.to_json)
56
+ rack_response(200, GC.stat.to_json)
70
57
 
71
58
  when /\/stats$/
72
- return rack_response(200, @cli.stats)
59
+ rack_response(200, @cli.stats.to_json)
60
+
61
+ when /\/thread-backtraces$/
62
+ backtraces = []
63
+ @cli.thread_status do |name, backtrace|
64
+ backtraces << { name: name, backtrace: backtrace }
65
+ end
66
+
67
+ rack_response(200, backtraces.to_json)
68
+
69
+ when /\/refork$/
70
+ Process.kill "SIGURG", $$
71
+ rack_response(200, OK_STATUS)
72
+
73
73
  else
74
74
  rack_response 404, "Unsupported action", 'text/plain'
75
75
  end
76
76
  end
77
+
78
+ private
79
+
80
+ def authenticate(env)
81
+ return true unless @auth_token
82
+ env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
83
+ end
84
+
85
+ def rack_response(status, body, content_type='application/json')
86
+ headers = {
87
+ 'Content-Type' => content_type,
88
+ 'Content-Length' => body.bytesize.to_s
89
+ }
90
+
91
+ [status, headers, [body]]
92
+ end
77
93
  end
78
94
  end
79
95
  end
@@ -5,14 +5,22 @@ 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
+ require 'puma/accept_nonblock'
16
+ end
17
+
10
18
  class Binder
11
19
  include Puma::Const
12
20
 
13
- RACK_VERSION = [1,3].freeze
21
+ RACK_VERSION = [1,6].freeze
14
22
 
15
- def initialize(events)
23
+ def initialize(events, conf = Configuration.new)
16
24
  @events = events
17
25
  @listeners = []
18
26
  @inherited_fds = {}
@@ -22,8 +30,8 @@ module Puma
22
30
  @proto_env = {
23
31
  "rack.version".freeze => RACK_VERSION,
24
32
  "rack.errors".freeze => events.stderr,
25
- "rack.multithread".freeze => true,
26
- "rack.multiprocess".freeze => false,
33
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
34
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
27
35
  "rack.run_once".freeze => false,
28
36
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
29
37
 
@@ -42,7 +50,13 @@ module Puma
42
50
  @ios = []
43
51
  end
44
52
 
45
- attr_reader :listeners, :ios
53
+ attr_reader :ios
54
+
55
+ # @version 5.0.0
56
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
57
+
58
+ # @version 5.0.0
59
+ attr_writer :ios, :listeners
46
60
 
47
61
  def env(sock)
48
62
  @envs.fetch(sock, @proto_env)
@@ -50,50 +64,45 @@ module Puma
50
64
 
51
65
  def close
52
66
  @ios.each { |i| i.close }
53
- @unix_paths.each do |i|
54
- # Errno::ENOENT is intermittently raised
55
- begin
56
- unix_socket = UNIXSocket.new i
57
- unix_socket.close
58
- rescue Errno::ENOENT
59
- end
60
- end
61
67
  end
62
68
 
63
- def import_from_env
64
- remove = []
65
-
66
- ENV.each do |k,v|
67
- if k =~ /PUMA_INHERIT_\d+/
68
- fd, url = v.split(":", 2)
69
- @inherited_fds[url] = fd.to_i
70
- remove << k
71
- elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
72
- v.to_i.times do |num|
73
- fd = num + 3
74
- sock = TCPServer.for_fd(fd)
75
- begin
76
- key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
77
- rescue ArgumentError
78
- port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
79
- if addr =~ /\:/
80
- addr = "[#{addr}]"
81
- end
82
- key = [ :tcp, addr, port ]
83
- end
84
- @activated_sockets[key] = sock
85
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
86
- end
87
- remove << k << 'LISTEN_PID'
88
- end
89
- end
69
+ # @version 5.0.0
70
+ def connected_ports
71
+ ios.map { |io| io.addr[1] }.uniq
72
+ end
90
73
 
91
- remove.each do |k|
92
- ENV.delete k
74
+ # @version 5.0.0
75
+ def create_inherited_fds(env_hash)
76
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
77
+ fd, url = v.split(":", 2)
78
+ @inherited_fds[url] = fd.to_i
79
+ end.keys # pass keys back for removal
80
+ end
81
+
82
+ # systemd socket activation.
83
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
84
+ # LISTEN_PID = PID of the service process, aka us
85
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
86
+ # @version 5.0.0
87
+ #
88
+ def create_activated_fds(env_hash)
89
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
90
+ env_hash['LISTEN_FDS'].to_i.times do |index|
91
+ sock = TCPServer.for_fd(socket_activation_fd(index))
92
+ key = begin # Try to parse as a path
93
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
94
+ rescue ArgumentError # Try to parse as a port/ip
95
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
96
+ addr = "[#{addr}]" if addr =~ /\:/
97
+ [:tcp, addr, port]
98
+ end
99
+ @activated_sockets[key] = sock
100
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
93
101
  end
102
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
94
103
  end
95
104
 
96
- def parse(binds, logger)
105
+ def parse(binds, logger, log_msg = 'Listening')
97
106
  binds.each do |str|
98
107
  uri = URI.parse str
99
108
  case uri.scheme
@@ -111,7 +120,17 @@ module Puma
111
120
  bak = params.fetch('backlog', 1024).to_i
112
121
 
113
122
  io = add_tcp_listener uri.host, uri.port, opt, bak
114
- logger.log "* Listening on #{str}"
123
+
124
+ @ios.each do |i|
125
+ next unless TCPServer === i
126
+ addr = if i.local_address.ipv6?
127
+ "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
128
+ else
129
+ i.local_address.ip_unpack.join(':')
130
+ end
131
+
132
+ logger.log "* #{log_msg} on http://#{addr}"
133
+ end
115
134
  end
116
135
 
117
136
  @listeners << [str, io] if io
@@ -146,70 +165,16 @@ module Puma
146
165
  end
147
166
 
148
167
  io = add_unix_listener path, umask, mode, backlog
149
- logger.log "* Listening on #{str}"
168
+ logger.log "* #{log_msg} on #{str}"
150
169
  end
151
170
 
152
171
  @listeners << [str, io]
153
172
  when "ssl"
154
- params = Util.parse_query uri.query
155
- require 'puma/minissl'
156
-
157
- MiniSSL.check
158
-
159
- ctx = MiniSSL::Context.new
160
-
161
- if defined?(JRUBY_VERSION)
162
- unless params['keystore']
163
- @events.error "Please specify the Java keystore via 'keystore='"
164
- end
165
-
166
- ctx.keystore = params['keystore']
167
-
168
- unless params['keystore-pass']
169
- @events.error "Please specify the Java keystore password via 'keystore-pass='"
170
- end
171
-
172
- ctx.keystore_pass = params['keystore-pass']
173
- ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
174
- else
175
- unless params['key']
176
- @events.error "Please specify the SSL key via 'key='"
177
- end
178
-
179
- ctx.key = params['key']
180
-
181
- unless params['cert']
182
- @events.error "Please specify the SSL cert via 'cert='"
183
- end
184
173
 
185
- ctx.cert = params['cert']
174
+ raise "Puma compiled without SSL support" unless HAS_SSL
186
175
 
187
- if ['peer', 'force_peer'].include?(params['verify_mode'])
188
- unless params['ca']
189
- @events.error "Please specify the SSL ca via 'ca='"
190
- end
191
- end
192
-
193
- ctx.ca = params['ca'] if params['ca']
194
- ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
195
- end
196
-
197
- ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
198
- ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
199
-
200
- if params['verify_mode']
201
- ctx.verify_mode = case params['verify_mode']
202
- when "peer"
203
- MiniSSL::VERIFY_PEER
204
- when "force_peer"
205
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
206
- when "none"
207
- MiniSSL::VERIFY_NONE
208
- else
209
- @events.error "Please specify a valid verify_mode="
210
- MiniSSL::VERIFY_NONE
211
- end
212
- end
176
+ params = Util.parse_query uri.query
177
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
213
178
 
214
179
  if fd = @inherited_fds.delete(str)
215
180
  logger.log "* Inherited #{str}"
@@ -258,12 +223,6 @@ module Puma
258
223
  end
259
224
  end
260
225
 
261
- def loopback_addresses
262
- Socket.ip_address_list.select do |addrinfo|
263
- addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
264
- end.map { |addrinfo| addrinfo.ip_address }.uniq
265
- end
266
-
267
226
  # Tell the server to listen on host +host+, port +port+.
268
227
  # If +optimize_for_latency+ is true (the default) then clients connecting
269
228
  # will be optimized for latency over throughput.
@@ -280,20 +239,17 @@ module Puma
280
239
  end
281
240
 
282
241
  host = host[1..-2] if host and host[0..0] == '['
283
- s = TCPServer.new(host, port)
242
+ tcp_server = TCPServer.new(host, port)
284
243
  if optimize_for_latency
285
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
244
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
286
245
  end
287
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
288
- s.listen backlog
289
- @connected_port = s.addr[1]
246
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
247
+ tcp_server.listen backlog
290
248
 
291
- @ios << s
292
- s
249
+ @ios << tcp_server
250
+ tcp_server
293
251
  end
294
252
 
295
- attr_reader :connected_port
296
-
297
253
  def inherit_tcp_listener(host, port, fd)
298
254
  if fd.kind_of? TCPServer
299
255
  s = fd
@@ -307,9 +263,8 @@ module Puma
307
263
 
308
264
  def add_ssl_listener(host, port, ctx,
309
265
  optimize_for_latency=true, backlog=1024)
310
- require 'puma/minissl'
311
266
 
312
- MiniSSL.check
267
+ raise "Puma compiled without SSL support" unless HAS_SSL
313
268
 
314
269
  if host == "localhost"
315
270
  loopback_addresses.each do |addr|
@@ -326,7 +281,6 @@ module Puma
326
281
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
327
282
  s.listen backlog
328
283
 
329
-
330
284
  ssl = MiniSSL::Server.new s, ctx
331
285
  env = @proto_env.dup
332
286
  env[HTTPS_KEY] = HTTPS
@@ -337,8 +291,7 @@ module Puma
337
291
  end
338
292
 
339
293
  def inherit_ssl_listener(fd, ctx)
340
- require 'puma/minissl'
341
- MiniSSL.check
294
+ raise "Puma compiled without SSL support" unless HAS_SSL
342
295
 
343
296
  if fd.kind_of? TCPServer
344
297
  s = fd
@@ -359,7 +312,7 @@ module Puma
359
312
  # Tell the server to listen on +path+ as a UNIX domain socket.
360
313
  #
361
314
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
362
- @unix_paths << path
315
+ @unix_paths << path unless File.exist? path
363
316
 
364
317
  # Let anyone connect by default
365
318
  umask ||= 0
@@ -397,7 +350,7 @@ module Puma
397
350
  end
398
351
 
399
352
  def inherit_unix_listener(path, fd)
400
- @unix_paths << path
353
+ @unix_paths << path unless File.exist? path
401
354
 
402
355
  if fd.kind_of? TCPServer
403
356
  s = fd
@@ -413,5 +366,40 @@ module Puma
413
366
  s
414
367
  end
415
368
 
369
+ def close_listeners
370
+ listeners.each do |l, io|
371
+ io.close unless io.closed? # Ruby 2.2 issue
372
+ uri = URI.parse(l)
373
+ next unless uri.scheme == 'unix'
374
+ unix_path = "#{uri.host}#{uri.path}"
375
+ File.unlink unix_path if unix_paths.include? unix_path
376
+ end
377
+ end
378
+
379
+ def redirects_for_restart
380
+ redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
381
+ redirects[:close_others] = true
382
+ redirects
383
+ end
384
+
385
+ # @version 5.0.0
386
+ def redirects_for_restart_env
387
+ listeners.each_with_object({}).with_index do |(listen, memo), i|
388
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
389
+ end
390
+ end
391
+
392
+ private
393
+
394
+ def loopback_addresses
395
+ Socket.ip_address_list.select do |addrinfo|
396
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
397
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
398
+ end
399
+
400
+ # @version 5.0.0
401
+ def socket_activation_fd(int)
402
+ int + 3 # 3 is the magic number you add to follow the SA protocol
403
+ end
416
404
  end
417
405
  end