puma 4.3.6-java → 5.0.2-java

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1153 -518
  3. data/LICENSE +23 -20
  4. data/README.md +26 -13
  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/signals.md +7 -6
  16. data/docs/systemd.md +1 -63
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  18. data/ext/puma_http11/extconf.rb +4 -3
  19. data/ext/puma_http11/mini_ssl.c +15 -2
  20. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  21. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  23. data/ext/puma_http11/puma_http11.c +6 -38
  24. data/lib/puma.rb +20 -0
  25. data/lib/puma/app/status.rb +14 -1
  26. data/lib/puma/binder.rb +90 -68
  27. data/lib/puma/cli.rb +7 -15
  28. data/lib/puma/client.rb +62 -13
  29. data/lib/puma/cluster.rb +193 -74
  30. data/lib/puma/commonlogger.rb +2 -2
  31. data/lib/puma/configuration.rb +31 -42
  32. data/lib/puma/const.rb +3 -3
  33. data/lib/puma/control_cli.rb +29 -17
  34. data/lib/puma/detect.rb +17 -0
  35. data/lib/puma/dsl.rb +144 -70
  36. data/lib/puma/error_logger.rb +97 -0
  37. data/lib/puma/events.rb +37 -31
  38. data/lib/puma/io_buffer.rb +9 -2
  39. data/lib/puma/jruby_restart.rb +0 -58
  40. data/lib/puma/launcher.rb +57 -31
  41. data/lib/puma/minissl.rb +68 -18
  42. data/lib/puma/minissl/context_builder.rb +0 -3
  43. data/lib/puma/null_io.rb +1 -1
  44. data/lib/puma/plugin.rb +1 -10
  45. data/lib/puma/puma_http11.jar +0 -0
  46. data/lib/puma/rack/builder.rb +0 -4
  47. data/lib/puma/reactor.rb +10 -16
  48. data/lib/puma/runner.rb +8 -36
  49. data/lib/puma/server.rb +161 -218
  50. data/lib/puma/single.rb +8 -64
  51. data/lib/puma/state_file.rb +6 -3
  52. data/lib/puma/thread_pool.rb +116 -51
  53. data/lib/puma/util.rb +1 -0
  54. data/lib/rack/handler/puma.rb +1 -3
  55. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  56. metadata +17 -19
  57. data/docs/tcp_mode.md +0 -96
  58. data/ext/puma_http11/io_buffer.c +0 -155
  59. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  60. data/lib/puma/tcp_logger.rb +0 -41
  61. data/tools/jungle/README.md +0 -19
  62. data/tools/jungle/init.d/README.md +0 -61
  63. data/tools/jungle/init.d/puma +0 -421
  64. data/tools/jungle/init.d/run-puma +0 -18
@@ -0,0 +1,15 @@
1
+ package puma;
2
+
3
+ import java.io.IOException;
4
+
5
+ import org.jruby.Ruby;
6
+ import org.jruby.runtime.load.BasicLibraryService;
7
+
8
+ import org.jruby.puma.Http11;
9
+
10
+ public class PumaHttp11Service implements BasicLibraryService {
11
+ public boolean basicLoad(final Ruby runtime) throws IOException {
12
+ Http11.createHttp11(runtime);
13
+ return true;
14
+ }
15
+ }
@@ -30,8 +30,8 @@ public class Http11 extends RubyObject {
30
30
  public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length.";
31
31
  public final static int MAX_FRAGMENT_LENGTH = 1024;
32
32
  public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
33
- public final static int MAX_REQUEST_PATH_LENGTH = 2048;
34
- public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 2048 allowed length.";
33
+ public final static int MAX_REQUEST_PATH_LENGTH = 8192;
34
+ public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length.";
35
35
  public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
36
36
  public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
37
37
  public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
@@ -197,7 +197,7 @@ public class Http11 extends RubyObject {
197
197
  validateMaxLength(runtime, parser.nread,MAX_HEADER_LENGTH, MAX_HEADER_LENGTH_ERR);
198
198
 
199
199
  if(hp.has_error()) {
200
- throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails.");
200
+ throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
201
201
  } else {
202
202
  return runtime.newFixnum(parser.nread);
203
203
  }
@@ -22,6 +22,7 @@ import javax.net.ssl.SSLException;
22
22
  import javax.net.ssl.SSLPeerUnverifiedException;
23
23
  import javax.net.ssl.SSLSession;
24
24
  import java.io.FileInputStream;
25
+ import java.io.InputStream;
25
26
  import java.io.IOException;
26
27
  import java.nio.Buffer;
27
28
  import java.nio.ByteBuffer;
@@ -32,6 +33,8 @@ import java.security.NoSuchAlgorithmException;
32
33
  import java.security.UnrecoverableKeyException;
33
34
  import java.security.cert.CertificateEncodingException;
34
35
  import java.security.cert.CertificateException;
36
+ import java.util.concurrent.ConcurrentHashMap;
37
+ import java.util.Map;
35
38
 
36
39
  import static javax.net.ssl.SSLEngineResult.Status;
37
40
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
@@ -120,6 +123,8 @@ public class MiniSSL extends RubyObject {
120
123
  }
121
124
 
122
125
  private SSLEngine engine;
126
+ private boolean closed;
127
+ private boolean handshake;
123
128
  private MiniSSLBuffer inboundNetData;
124
129
  private MiniSSLBuffer outboundAppData;
125
130
  private MiniSSLBuffer outboundNetData;
@@ -128,10 +133,39 @@ public class MiniSSL extends RubyObject {
128
133
  super(runtime, klass);
129
134
  }
130
135
 
136
+ private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
137
+ private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
138
+
131
139
  @JRubyMethod(meta = true)
132
- public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) {
133
- RubyClass klass = (RubyClass) recv;
140
+ public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
141
+ throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
142
+ // Create the KeyManagerFactory and TrustManagerFactory for this server
143
+ String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
144
+ char[] password = miniSSLContext.callMethod(context, "keystore_pass").convertToString().asJavaString().toCharArray();
134
145
 
146
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
147
+ InputStream is = new FileInputStream(keystoreFile);
148
+ try {
149
+ ks.load(is, password);
150
+ } finally {
151
+ is.close();
152
+ }
153
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
154
+ kmf.init(ks, password);
155
+ keyManagerFactoryMap.put(keystoreFile, kmf);
156
+
157
+ KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
158
+ is = new FileInputStream(keystoreFile);
159
+ try {
160
+ ts.load(is, password);
161
+ } finally {
162
+ is.close();
163
+ }
164
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
165
+ tmf.init(ts);
166
+ trustManagerFactoryMap.put(keystoreFile, tmf);
167
+
168
+ RubyClass klass = (RubyClass) recv;
135
169
  return klass.newInstance(context,
136
170
  new IRubyObject[] { miniSSLContext },
137
171
  Block.NULL_BLOCK);
@@ -139,24 +173,22 @@ public class MiniSSL extends RubyObject {
139
173
 
140
174
  @JRubyMethod
141
175
  public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
142
- throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
176
+ throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
143
177
  KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
144
178
  KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
145
179
 
146
- char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
147
180
  String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
148
- ks.load(new FileInputStream(keystoreFile), password);
149
- ts.load(new FileInputStream(keystoreFile), password);
150
-
151
- KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
152
- kmf.init(ks, password);
153
-
154
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
155
- tmf.init(ts);
181
+ KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
182
+ TrustManagerFactory tmf = trustManagerFactoryMap.get(keystoreFile);
183
+ if(kmf == null || tmf == null) {
184
+ throw new KeyStoreException("Could not find KeyManagerFactory/TrustManagerFactory for keystore: " + keystoreFile);
185
+ }
156
186
 
157
187
  SSLContext sslCtx = SSLContext.getInstance("TLS");
158
188
 
159
189
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
190
+ closed = false;
191
+ handshake = false;
160
192
  engine = sslCtx.createSSLEngine();
161
193
 
162
194
  String[] protocols;
@@ -173,7 +205,7 @@ public class MiniSSL extends RubyObject {
173
205
  engine.setEnabledProtocols(protocols);
174
206
  engine.setUseClientMode(false);
175
207
 
176
- long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
208
+ long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
177
209
  if ((verify_mode & 0x1) != 0) { // 'peer'
178
210
  engine.setWantClientAuth(true);
179
211
  }
@@ -240,14 +272,21 @@ public class MiniSSL extends RubyObject {
240
272
  // need to wait for more data to come in before we retry
241
273
  retryOp = false;
242
274
  break;
275
+ case CLOSED:
276
+ closed = true;
277
+ retryOp = false;
278
+ break;
243
279
  default:
244
- // other cases are OK and CLOSED. We're done here.
280
+ // other case is OK. We're done here.
245
281
  retryOp = false;
246
282
  }
283
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
284
+ handshake = true;
285
+ }
247
286
  }
248
287
 
249
288
  // after each op, run any delegated tasks if needed
250
- if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
289
+ if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
251
290
  Runnable runnable;
252
291
  while ((runnable = engine.getDelegatedTask()) != null) {
253
292
  runnable.run();
@@ -271,13 +310,14 @@ public class MiniSSL extends RubyObject {
271
310
 
272
311
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
273
312
  boolean done = false;
313
+ SSLEngineResult res = null;
274
314
  while (!done) {
275
315
  switch (handshakeStatus) {
276
316
  case NEED_WRAP:
277
- doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
317
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
278
318
  break;
279
319
  case NEED_UNWRAP:
280
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
320
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
281
321
  if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
282
322
  // need more data before we can shake more hands
283
323
  done = true;
@@ -286,7 +326,9 @@ public class MiniSSL extends RubyObject {
286
326
  default:
287
327
  done = true;
288
328
  }
289
- handshakeStatus = engine.getHandshakeStatus();
329
+ if (!done) {
330
+ handshakeStatus = res.getHandshakeStatus();
331
+ }
290
332
  }
291
333
 
292
334
  if (inboundNetData.hasRemaining()) {
@@ -360,4 +402,21 @@ public class MiniSSL extends RubyObject {
360
402
  return getRuntime().getNil();
361
403
  }
362
404
  }
405
+
406
+ @JRubyMethod(name = "init?")
407
+ public IRubyObject isInit(ThreadContext context) {
408
+ return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
409
+ }
410
+
411
+ @JRubyMethod
412
+ public IRubyObject shutdown() {
413
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
414
+ if (engine.isOutboundDone()) {
415
+ engine.closeOutbound();
416
+ }
417
+ return getRuntime().getTrue();
418
+ } else {
419
+ return getRuntime().getFalse();
420
+ }
421
+ }
363
422
  }
@@ -54,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
54
54
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
55
55
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
56
56
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
57
- DEF_MAX_LENGTH(REQUEST_PATH, 2048);
57
+ DEF_MAX_LENGTH(REQUEST_PATH, 8192);
58
58
  DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
59
59
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
60
60
 
@@ -112,21 +112,6 @@ static struct common_field common_http_fields[] = {
112
112
  # undef f
113
113
  };
114
114
 
115
- /*
116
- * qsort(3) and bsearch(3) improve average performance slightly, but may
117
- * not be worth it for lack of portability to certain platforms...
118
- */
119
- #if defined(HAVE_QSORT_BSEARCH)
120
- /* sort by length, then by name if there's a tie */
121
- static int common_field_cmp(const void *a, const void *b)
122
- {
123
- struct common_field *cfa = (struct common_field *)a;
124
- struct common_field *cfb = (struct common_field *)b;
125
- signed long diff = cfa->len - cfb->len;
126
- return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
127
- }
128
- #endif /* HAVE_QSORT_BSEARCH */
129
-
130
115
  static void init_common_fields(void)
131
116
  {
132
117
  unsigned i;
@@ -143,28 +128,10 @@ static void init_common_fields(void)
143
128
  }
144
129
  rb_global_variable(&cf->value);
145
130
  }
146
-
147
- #if defined(HAVE_QSORT_BSEARCH)
148
- qsort(common_http_fields,
149
- ARRAY_SIZE(common_http_fields),
150
- sizeof(struct common_field),
151
- common_field_cmp);
152
- #endif /* HAVE_QSORT_BSEARCH */
153
131
  }
154
132
 
155
133
  static VALUE find_common_field_value(const char *field, size_t flen)
156
134
  {
157
- #if defined(HAVE_QSORT_BSEARCH)
158
- struct common_field key;
159
- struct common_field *found;
160
- key.name = field;
161
- key.len = (signed long)flen;
162
- found = (struct common_field *)bsearch(&key, common_http_fields,
163
- ARRAY_SIZE(common_http_fields),
164
- sizeof(struct common_field),
165
- common_field_cmp);
166
- return found ? found->value : Qnil;
167
- #else /* !HAVE_QSORT_BSEARCH */
168
135
  unsigned i;
169
136
  struct common_field *cf = common_http_fields;
170
137
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
@@ -172,7 +139,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
172
139
  return cf->value;
173
140
  }
174
141
  return Qnil;
175
- #endif /* !HAVE_QSORT_BSEARCH */
176
142
  }
177
143
 
178
144
  void http_field(puma_parser* hp, const char *field, size_t flen,
@@ -401,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
401
367
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
402
368
 
403
369
  if(puma_parser_has_error(http)) {
404
- 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?");
405
371
  } else {
406
372
  return INT2FIX(puma_parser_nread(http));
407
373
  }
@@ -468,8 +434,9 @@ VALUE HttpParser_body(VALUE self) {
468
434
  return http->body;
469
435
  }
470
436
 
471
- void Init_io_buffer(VALUE puma);
437
+ #ifdef HAVE_OPENSSL_BIO_H
472
438
  void Init_mini_ssl(VALUE mod);
439
+ #endif
473
440
 
474
441
  void Init_puma_http11()
475
442
  {
@@ -498,6 +465,7 @@ void Init_puma_http11()
498
465
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
499
466
  init_common_fields();
500
467
 
501
- Init_io_buffer(mPuma);
468
+ #ifdef HAVE_OPENSSL_BIO_H
502
469
  Init_mini_ssl(mPuma);
470
+ #endif
503
471
  }
@@ -10,16 +10,28 @@ require 'stringio'
10
10
 
11
11
  require 'thread'
12
12
 
13
+ require 'puma/puma_http11'
14
+ require 'puma/detect'
15
+
13
16
  module Puma
14
17
  autoload :Const, 'puma/const'
15
18
  autoload :Server, 'puma/server'
16
19
  autoload :Launcher, 'puma/launcher'
17
20
 
21
+ # @!attribute [rw] stats_object=
18
22
  def self.stats_object=(val)
19
23
  @get_stats = val
20
24
  end
21
25
 
26
+ # @!attribute [rw] stats_object
22
27
  def self.stats
28
+ require 'json'
29
+ @get_stats.stats.to_json
30
+ end
31
+
32
+ # @!attribute [r] stats_hash
33
+ # @version 5.0.0
34
+ def self.stats_hash
23
35
  @get_stats.stats
24
36
  end
25
37
 
@@ -28,4 +40,12 @@ module Puma
28
40
  return unless Thread.current.respond_to?(:name=)
29
41
  Thread.current.name = "puma #{name}"
30
42
  end
43
+
44
+ unless HAS_SSL
45
+ module MiniSSL
46
+ # this class is defined so that it exists when Puma is compiled
47
+ # without ssl support, as Server and Reactor use it in rescue statements.
48
+ class SSLError < StandardError ; end
49
+ end
50
+ end
31
51
  end
@@ -56,7 +56,20 @@ module Puma
56
56
  rack_response(200, GC.stat.to_json)
57
57
 
58
58
  when /\/stats$/
59
- 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
+
60
73
  else
61
74
  rack_response 404, "Unsupported action", 'text/plain'
62
75
  end
@@ -5,15 +5,22 @@ require 'socket'
5
5
 
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
- require 'puma/minissl/context_builder'
8
+ require 'puma/configuration'
9
9
 
10
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
+
11
18
  class Binder
12
19
  include Puma::Const
13
20
 
14
- RACK_VERSION = [1,3].freeze
21
+ RACK_VERSION = [1,6].freeze
15
22
 
16
- def initialize(events)
23
+ def initialize(events, conf = Configuration.new)
17
24
  @events = events
18
25
  @listeners = []
19
26
  @inherited_fds = {}
@@ -23,8 +30,8 @@ module Puma
23
30
  @proto_env = {
24
31
  "rack.version".freeze => RACK_VERSION,
25
32
  "rack.errors".freeze => events.stderr,
26
- "rack.multithread".freeze => true,
27
- "rack.multiprocess".freeze => false,
33
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
34
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
28
35
  "rack.run_once".freeze => false,
29
36
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
37
 
@@ -45,6 +52,12 @@ module Puma
45
52
 
46
53
  attr_reader :ios
47
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
60
+
48
61
  def env(sock)
49
62
  @envs.fetch(sock, @proto_env)
50
63
  end
@@ -53,40 +66,44 @@ module Puma
53
66
  @ios.each { |i| i.close }
54
67
  end
55
68
 
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'
81
- end
82
- end
69
+ # @!attribute [r] connected_ports
70
+ # @version 5.0.0
71
+ def connected_ports
72
+ ios.map { |io| io.addr[1] }.uniq
73
+ end
83
74
 
84
- remove.each do |k|
85
- ENV.delete k
75
+ # @version 5.0.0
76
+ def create_inherited_fds(env_hash)
77
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
78
+ fd, url = v.split(":", 2)
79
+ @inherited_fds[url] = fd.to_i
80
+ end.keys # pass keys back for removal
81
+ end
82
+
83
+ # systemd socket activation.
84
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
85
+ # LISTEN_PID = PID of the service process, aka us
86
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
87
+ # @version 5.0.0
88
+ #
89
+ def create_activated_fds(env_hash)
90
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
91
+ env_hash['LISTEN_FDS'].to_i.times do |index|
92
+ sock = TCPServer.for_fd(socket_activation_fd(index))
93
+ key = begin # Try to parse as a path
94
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
95
+ rescue ArgumentError # Try to parse as a port/ip
96
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
97
+ addr = "[#{addr}]" if addr =~ /\:/
98
+ [:tcp, addr, port]
99
+ end
100
+ @activated_sockets[key] = sock
101
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
86
102
  end
103
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
87
104
  end
88
105
 
89
- def parse(binds, logger)
106
+ def parse(binds, logger, log_msg = 'Listening')
90
107
  binds.each do |str|
91
108
  uri = URI.parse str
92
109
  case uri.scheme
@@ -113,7 +130,7 @@ module Puma
113
130
  i.local_address.ip_unpack.join(':')
114
131
  end
115
132
 
116
- logger.log "* Listening on tcp://#{addr}"
133
+ logger.log "* #{log_msg} on http://#{addr}"
117
134
  end
118
135
  end
119
136
 
@@ -149,11 +166,14 @@ module Puma
149
166
  end
150
167
 
151
168
  io = add_unix_listener path, umask, mode, backlog
152
- logger.log "* Listening on #{str}"
169
+ logger.log "* #{log_msg} on #{str}"
153
170
  end
154
171
 
155
172
  @listeners << [str, io]
156
173
  when "ssl"
174
+
175
+ raise "Puma compiled without SSL support" unless HAS_SSL
176
+
157
177
  params = Util.parse_query uri.query
158
178
  ctx = MiniSSL::ContextBuilder.new(params, @events).context
159
179
 
@@ -204,12 +224,6 @@ module Puma
204
224
  end
205
225
  end
206
226
 
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
211
- end
212
-
213
227
  # Tell the server to listen on host +host+, port +port+.
214
228
  # If +optimize_for_latency+ is true (the default) then clients connecting
215
229
  # will be optimized for latency over throughput.
@@ -226,20 +240,17 @@ module Puma
226
240
  end
227
241
 
228
242
  host = host[1..-2] if host and host[0..0] == '['
229
- s = TCPServer.new(host, port)
243
+ tcp_server = TCPServer.new(host, port)
230
244
  if optimize_for_latency
231
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
245
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
232
246
  end
233
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
234
- s.listen backlog
235
- @connected_port = s.addr[1]
247
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
248
+ tcp_server.listen backlog
236
249
 
237
- @ios << s
238
- s
250
+ @ios << tcp_server
251
+ tcp_server
239
252
  end
240
253
 
241
- attr_reader :connected_port
242
-
243
254
  def inherit_tcp_listener(host, port, fd)
244
255
  if fd.kind_of? TCPServer
245
256
  s = fd
@@ -253,9 +264,8 @@ module Puma
253
264
 
254
265
  def add_ssl_listener(host, port, ctx,
255
266
  optimize_for_latency=true, backlog=1024)
256
- require 'puma/minissl'
257
267
 
258
- MiniSSL.check
268
+ raise "Puma compiled without SSL support" unless HAS_SSL
259
269
 
260
270
  if host == "localhost"
261
271
  loopback_addresses.each do |addr|
@@ -272,7 +282,6 @@ module Puma
272
282
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
273
283
  s.listen backlog
274
284
 
275
-
276
285
  ssl = MiniSSL::Server.new s, ctx
277
286
  env = @proto_env.dup
278
287
  env[HTTPS_KEY] = HTTPS
@@ -283,8 +292,7 @@ module Puma
283
292
  end
284
293
 
285
294
  def inherit_ssl_listener(fd, ctx)
286
- require 'puma/minissl'
287
- MiniSSL.check
295
+ raise "Puma compiled without SSL support" unless HAS_SSL
288
296
 
289
297
  if fd.kind_of? TCPServer
290
298
  s = fd
@@ -360,26 +368,40 @@ module Puma
360
368
  end
361
369
 
362
370
  def close_listeners
363
- @listeners.each do |l, io|
364
- io.close
371
+ listeners.each do |l, io|
372
+ io.close unless io.closed? # Ruby 2.2 issue
365
373
  uri = URI.parse(l)
366
374
  next unless uri.scheme == 'unix'
367
375
  unix_path = "#{uri.host}#{uri.path}"
368
- File.unlink unix_path if @unix_paths.include? unix_path
376
+ File.unlink unix_path if unix_paths.include? unix_path
369
377
  end
370
378
  end
371
379
 
372
- def close_unix_paths
373
- @unix_paths.each { |up| File.unlink(up) if File.exist? up }
380
+ def redirects_for_restart
381
+ redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
382
+ redirects[:close_others] = true
383
+ redirects
374
384
  end
375
385
 
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
386
+ # @version 5.0.0
387
+ def redirects_for_restart_env
388
+ listeners.each_with_object({}).with_index do |(listen, memo), i|
389
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
381
390
  end
382
- redirects
391
+ end
392
+
393
+ private
394
+
395
+ # @!attribute [r] loopback_addresses
396
+ def loopback_addresses
397
+ Socket.ip_address_list.select do |addrinfo|
398
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
399
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
400
+ end
401
+
402
+ # @version 5.0.0
403
+ def socket_activation_fd(int)
404
+ int + 3 # 3 is the magic number you add to follow the SA protocol
383
405
  end
384
406
  end
385
407
  end