puma 3.0.0.rc1 → 5.0.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +703 -70
  3. data/LICENSE +23 -20
  4. data/README.md +173 -163
  5. data/docs/architecture.md +37 -0
  6. data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  9. data/docs/images/puma-connection-flow.png +0 -0
  10. data/docs/images/puma-general-arch.png +0 -0
  11. data/docs/jungle/README.md +13 -0
  12. data/docs/jungle/rc.d/README.md +74 -0
  13. data/docs/jungle/rc.d/puma +61 -0
  14. data/docs/jungle/rc.d/puma.conf +10 -0
  15. data/{tools → docs}/jungle/upstart/README.md +0 -0
  16. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  17. data/{tools → docs}/jungle/upstart/puma.conf +1 -1
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +38 -0
  20. data/docs/restart.md +41 -0
  21. data/docs/signals.md +57 -3
  22. data/docs/systemd.md +228 -0
  23. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  24. data/ext/puma_http11/extconf.rb +16 -0
  25. data/ext/puma_http11/http11_parser.c +287 -468
  26. data/ext/puma_http11/http11_parser.h +1 -0
  27. data/ext/puma_http11/http11_parser.java.rl +21 -37
  28. data/ext/puma_http11/http11_parser.rl +10 -9
  29. data/ext/puma_http11/http11_parser_common.rl +4 -4
  30. data/ext/puma_http11/mini_ssl.c +159 -10
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  34. data/ext/puma_http11/puma_http11.c +6 -38
  35. data/lib/puma.rb +25 -5
  36. data/lib/puma/accept_nonblock.rb +7 -1
  37. data/lib/puma/app/status.rb +53 -26
  38. data/lib/puma/binder.rb +150 -119
  39. data/lib/puma/cli.rb +56 -38
  40. data/lib/puma/client.rb +277 -80
  41. data/lib/puma/cluster.rb +326 -130
  42. data/lib/puma/commonlogger.rb +21 -20
  43. data/lib/puma/configuration.rb +160 -161
  44. data/lib/puma/const.rb +50 -47
  45. data/lib/puma/control_cli.rb +104 -63
  46. data/lib/puma/detect.rb +13 -1
  47. data/lib/puma/dsl.rb +463 -114
  48. data/lib/puma/events.rb +22 -13
  49. data/lib/puma/io_buffer.rb +9 -5
  50. data/lib/puma/jruby_restart.rb +2 -59
  51. data/lib/puma/launcher.rb +195 -105
  52. data/lib/puma/minissl.rb +110 -4
  53. data/lib/puma/minissl/context_builder.rb +76 -0
  54. data/lib/puma/null_io.rb +9 -14
  55. data/lib/puma/plugin.rb +32 -12
  56. data/lib/puma/plugin/tmp_restart.rb +19 -6
  57. data/lib/puma/rack/builder.rb +7 -5
  58. data/lib/puma/rack/urlmap.rb +11 -8
  59. data/lib/puma/rack_default.rb +2 -0
  60. data/lib/puma/reactor.rb +242 -32
  61. data/lib/puma/runner.rb +41 -30
  62. data/lib/puma/server.rb +265 -183
  63. data/lib/puma/single.rb +22 -63
  64. data/lib/puma/state_file.rb +9 -2
  65. data/lib/puma/thread_pool.rb +179 -68
  66. data/lib/puma/util.rb +3 -11
  67. data/lib/rack/handler/puma.rb +60 -11
  68. data/tools/Dockerfile +16 -0
  69. data/tools/trickletest.rb +1 -2
  70. metadata +35 -99
  71. data/COPYING +0 -55
  72. data/Gemfile +0 -13
  73. data/Manifest.txt +0 -79
  74. data/Rakefile +0 -158
  75. data/docs/config.md +0 -0
  76. data/ext/puma_http11/io_buffer.c +0 -155
  77. data/lib/puma/capistrano.rb +0 -94
  78. data/lib/puma/compat.rb +0 -18
  79. data/lib/puma/convenient.rb +0 -23
  80. data/lib/puma/daemon_ext.rb +0 -31
  81. data/lib/puma/delegation.rb +0 -11
  82. data/lib/puma/java_io_buffer.rb +0 -45
  83. data/lib/puma/rack/backports/uri/common_18.rb +0 -56
  84. data/lib/puma/rack/backports/uri/common_192.rb +0 -52
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -32
  87. data/puma.gemspec +0 -52
  88. data/tools/jungle/README.md +0 -9
  89. data/tools/jungle/init.d/README.md +0 -54
  90. data/tools/jungle/init.d/puma +0 -394
  91. data/tools/jungle/init.d/run-puma +0 -3
@@ -6,6 +6,7 @@ import org.jruby.RubyModule;
6
6
  import org.jruby.RubyObject;
7
7
  import org.jruby.RubyString;
8
8
  import org.jruby.anno.JRubyMethod;
9
+ import org.jruby.javasupport.JavaEmbedUtils;
9
10
  import org.jruby.runtime.Block;
10
11
  import org.jruby.runtime.ObjectAllocator;
11
12
  import org.jruby.runtime.ThreadContext;
@@ -18,15 +19,18 @@ import javax.net.ssl.SSLContext;
18
19
  import javax.net.ssl.SSLEngine;
19
20
  import javax.net.ssl.SSLEngineResult;
20
21
  import javax.net.ssl.SSLException;
22
+ import javax.net.ssl.SSLPeerUnverifiedException;
21
23
  import javax.net.ssl.SSLSession;
22
24
  import java.io.FileInputStream;
23
25
  import java.io.IOException;
26
+ import java.nio.Buffer;
24
27
  import java.nio.ByteBuffer;
25
28
  import java.security.KeyManagementException;
26
29
  import java.security.KeyStore;
27
30
  import java.security.KeyStoreException;
28
31
  import java.security.NoSuchAlgorithmException;
29
32
  import java.security.UnrecoverableKeyException;
33
+ import java.security.cert.CertificateEncodingException;
30
34
  import java.security.cert.CertificateException;
31
35
 
32
36
  import static javax.net.ssl.SSLEngineResult.Status;
@@ -62,7 +66,7 @@ public class MiniSSL extends RubyObject {
62
66
 
63
67
  public void clear() { buffer.clear(); }
64
68
  public void compact() { buffer.compact(); }
65
- public void flip() { buffer.flip(); }
69
+ public void flip() { ((Buffer) buffer).flip(); }
66
70
  public boolean hasRemaining() { return buffer.hasRemaining(); }
67
71
  public int position() { return buffer.position(); }
68
72
 
@@ -86,7 +90,7 @@ public class MiniSSL extends RubyObject {
86
90
  public void resize(int newCapacity) {
87
91
  if (newCapacity > buffer.capacity()) {
88
92
  ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
89
- buffer.flip();
93
+ flip();
90
94
  dstTmp.put(buffer);
91
95
  buffer = dstTmp;
92
96
  } else {
@@ -98,7 +102,7 @@ public class MiniSSL extends RubyObject {
98
102
  * Drains the buffer to a ByteList, or returns null for an empty buffer
99
103
  */
100
104
  public ByteList asByteList() {
101
- buffer.flip();
105
+ flip();
102
106
  if (!buffer.hasRemaining()) {
103
107
  buffer.clear();
104
108
  return null;
@@ -155,7 +159,17 @@ public class MiniSSL extends RubyObject {
155
159
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
156
160
  engine = sslCtx.createSSLEngine();
157
161
 
158
- String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
162
+ String[] protocols;
163
+ if(miniSSLContext.callMethod(threadContext, "no_tlsv1").isTrue()) {
164
+ protocols = new String[] { "TLSv1.1", "TLSv1.2" };
165
+ } else {
166
+ protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
167
+ }
168
+
169
+ if(miniSSLContext.callMethod(threadContext, "no_tlsv1_1").isTrue()) {
170
+ protocols = new String[] { "TLSv1.2" };
171
+ }
172
+
159
173
  engine.setEnabledProtocols(protocols);
160
174
  engine.setUseClientMode(false);
161
175
 
@@ -167,6 +181,12 @@ public class MiniSSL extends RubyObject {
167
181
  engine.setNeedClientAuth(true);
168
182
  }
169
183
 
184
+ IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
185
+ if (!sslCipherListObject.isNil()) {
186
+ String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
187
+ engine.setEnabledCipherSuites(sslCipherList);
188
+ }
189
+
170
190
  SSLSession session = engine.getSession();
171
191
  inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
172
192
  outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
@@ -333,7 +353,11 @@ public class MiniSSL extends RubyObject {
333
353
  }
334
354
 
335
355
  @JRubyMethod
336
- public IRubyObject peercert() {
337
- return getRuntime().getNil();
356
+ public IRubyObject peercert() throws CertificateEncodingException {
357
+ try {
358
+ return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
359
+ } catch (SSLPeerUnverifiedException ex) {
360
+ return getRuntime().getNil();
361
+ }
338
362
  }
339
363
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Copyright (c) 2005 Zed A. Shaw
3
3
  * You can redistribute it and/or modify it under the same terms as Ruby.
4
+ * License 3-clause BSD
4
5
  */
5
6
 
6
7
  #define RSTRING_NOT_MODIFIED 1
@@ -9,6 +10,7 @@
9
10
  #include "ext_help.h"
10
11
  #include <assert.h>
11
12
  #include <string.h>
13
+ #include <ctype.h>
12
14
  #include "http11_parser.h"
13
15
 
14
16
  #ifndef MANAGED_STRINGS
@@ -52,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
52
54
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
53
55
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
54
56
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
55
- DEF_MAX_LENGTH(REQUEST_PATH, 2048);
57
+ DEF_MAX_LENGTH(REQUEST_PATH, 8196);
56
58
  DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
57
59
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
58
60
 
@@ -110,21 +112,6 @@ static struct common_field common_http_fields[] = {
110
112
  # undef f
111
113
  };
112
114
 
113
- /*
114
- * qsort(3) and bsearch(3) improve average performance slightly, but may
115
- * not be worth it for lack of portability to certain platforms...
116
- */
117
- #if defined(HAVE_QSORT_BSEARCH)
118
- /* sort by length, then by name if there's a tie */
119
- static int common_field_cmp(const void *a, const void *b)
120
- {
121
- struct common_field *cfa = (struct common_field *)a;
122
- struct common_field *cfb = (struct common_field *)b;
123
- signed long diff = cfa->len - cfb->len;
124
- return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
125
- }
126
- #endif /* HAVE_QSORT_BSEARCH */
127
-
128
115
  static void init_common_fields(void)
129
116
  {
130
117
  unsigned i;
@@ -141,28 +128,10 @@ static void init_common_fields(void)
141
128
  }
142
129
  rb_global_variable(&cf->value);
143
130
  }
144
-
145
- #if defined(HAVE_QSORT_BSEARCH)
146
- qsort(common_http_fields,
147
- ARRAY_SIZE(common_http_fields),
148
- sizeof(struct common_field),
149
- common_field_cmp);
150
- #endif /* HAVE_QSORT_BSEARCH */
151
131
  }
152
132
 
153
133
  static VALUE find_common_field_value(const char *field, size_t flen)
154
134
  {
155
- #if defined(HAVE_QSORT_BSEARCH)
156
- struct common_field key;
157
- struct common_field *found;
158
- key.name = field;
159
- key.len = (signed long)flen;
160
- found = (struct common_field *)bsearch(&key, common_http_fields,
161
- ARRAY_SIZE(common_http_fields),
162
- sizeof(struct common_field),
163
- common_field_cmp);
164
- return found ? found->value : Qnil;
165
- #else /* !HAVE_QSORT_BSEARCH */
166
135
  unsigned i;
167
136
  struct common_field *cf = common_http_fields;
168
137
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
@@ -170,7 +139,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
170
139
  return cf->value;
171
140
  }
172
141
  return Qnil;
173
- #endif /* !HAVE_QSORT_BSEARCH */
174
142
  }
175
143
 
176
144
  void http_field(puma_parser* hp, const char *field, size_t flen,
@@ -199,6 +167,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
199
167
  f = rb_str_new(hp->buf, new_size);
200
168
  }
201
169
 
170
+ while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
171
+
202
172
  /* check for duplicate header */
203
173
  v = rb_hash_aref(hp->request, f);
204
174
 
@@ -397,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
397
367
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
398
368
 
399
369
  if(puma_parser_has_error(http)) {
400
- 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?");
401
371
  } else {
402
372
  return INT2FIX(puma_parser_nread(http));
403
373
  }
@@ -464,7 +434,6 @@ VALUE HttpParser_body(VALUE self) {
464
434
  return http->body;
465
435
  }
466
436
 
467
- void Init_io_buffer(VALUE puma);
468
437
  void Init_mini_ssl(VALUE mod);
469
438
 
470
439
  void Init_puma_http11()
@@ -494,6 +463,5 @@ void Init_puma_http11()
494
463
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
495
464
  init_common_fields();
496
465
 
497
- Init_io_buffer(mPuma);
498
466
  Init_mini_ssl(mPuma);
499
467
  }
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Standard libraries
2
4
  require 'socket'
3
5
  require 'tempfile'
4
- require 'yaml'
5
6
  require 'time'
6
7
  require 'etc'
7
8
  require 'uri'
@@ -9,7 +10,26 @@ require 'stringio'
9
10
 
10
11
  require 'thread'
11
12
 
12
- # Ruby Puma
13
- require 'puma/const'
14
- require 'puma/server'
15
- require 'puma/launcher'
13
+ module Puma
14
+ autoload :Const, 'puma/const'
15
+ autoload :Server, 'puma/server'
16
+ autoload :Launcher, 'puma/launcher'
17
+
18
+ def self.stats_object=(val)
19
+ @get_stats = val
20
+ end
21
+
22
+ def self.stats
23
+ @get_stats.stats.to_json
24
+ end
25
+
26
+ def self.stats_hash
27
+ @get_stats.stats
28
+ end
29
+
30
+ # Thread name is new in Ruby 2.3
31
+ def self.set_thread_name(name)
32
+ return unless Thread.current.respond_to?(:name=)
33
+ Thread.current.name = "puma #{name}"
34
+ end
35
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
 
3
5
  module OpenSSL
@@ -13,7 +15,11 @@ module OpenSSL
13
15
  ssl.accept if @start_immediately
14
16
  ssl
15
17
  rescue SSLError => ex
16
- sock.close
18
+ if ssl
19
+ ssl.close
20
+ else
21
+ sock.close
22
+ end
17
23
  raise ex
18
24
  end
19
25
  end
@@ -1,26 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
1
5
  module Puma
2
6
  module App
7
+ # Check out {#call}'s source code to see what actions this web application
8
+ # can respond to.
3
9
  class Status
4
- def initialize(cli)
5
- @cli = cli
6
- @auth_token = nil
7
- end
8
10
  OK_STATUS = '{ "status": "ok" }'.freeze
9
11
 
10
- attr_accessor :auth_token
11
-
12
- def authenticate(env)
13
- return true unless @auth_token
14
- env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
15
- end
16
-
17
- def rack_response(status, body, content_type='application/json')
18
- headers = {
19
- 'Content-Type' => content_type,
20
- 'Content-Length' => body.bytesize.to_s
21
- }
22
-
23
- [status, headers, [body]]
12
+ def initialize(cli, token = nil)
13
+ @cli = cli
14
+ @auth_token = token
24
15
  end
25
16
 
26
17
  def call(env)
@@ -31,36 +22,72 @@ module Puma
31
22
  case env['PATH_INFO']
32
23
  when /\/stop$/
33
24
  @cli.stop
34
- return rack_response(200, OK_STATUS)
25
+ rack_response(200, OK_STATUS)
35
26
 
36
27
  when /\/halt$/
37
28
  @cli.halt
38
- return rack_response(200, OK_STATUS)
29
+ rack_response(200, OK_STATUS)
39
30
 
40
31
  when /\/restart$/
41
32
  @cli.restart
42
- return rack_response(200, OK_STATUS)
33
+ rack_response(200, OK_STATUS)
43
34
 
44
35
  when /\/phased-restart$/
45
36
  if !@cli.phased_restart
46
- return rack_response(404, '{ "error": "phased restart not available" }')
37
+ rack_response(404, '{ "error": "phased restart not available" }')
47
38
  else
48
- return rack_response(200, OK_STATUS)
39
+ rack_response(200, OK_STATUS)
49
40
  end
50
41
 
51
42
  when /\/reload-worker-directory$/
52
43
  if !@cli.send(:reload_worker_directory)
53
- return rack_response(404, '{ "error": "reload_worker_directory not available" }')
44
+ rack_response(404, '{ "error": "reload_worker_directory not available" }')
54
45
  else
55
- return rack_response(200, OK_STATUS)
46
+ rack_response(200, OK_STATUS)
56
47
  end
57
48
 
49
+ when /\/gc$/
50
+ GC.start
51
+ rack_response(200, OK_STATUS)
52
+
53
+ when /\/gc-stats$/
54
+ rack_response(200, GC.stat.to_json)
55
+
58
56
  when /\/stats$/
59
- return rack_response(200, @cli.stats)
57
+ rack_response(200, @cli.stats.to_json)
58
+
59
+ when /\/thread-backtraces$/
60
+ backtraces = []
61
+ @cli.thread_status do |name, backtrace|
62
+ backtraces << { name: name, backtrace: backtrace }
63
+ end
64
+
65
+ rack_response(200, backtraces.to_json)
66
+
67
+ when /\/refork$/
68
+ Process.kill "SIGURG", $$
69
+ rack_response(200, OK_STATUS)
70
+
60
71
  else
61
72
  rack_response 404, "Unsupported action", 'text/plain'
62
73
  end
63
74
  end
75
+
76
+ private
77
+
78
+ def authenticate(env)
79
+ return true unless @auth_token
80
+ env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
81
+ end
82
+
83
+ def rack_response(status, body, content_type='application/json')
84
+ headers = {
85
+ 'Content-Type' => content_type,
86
+ 'Content-Length' => body.bytesize.to_s
87
+ }
88
+
89
+ [status, headers, [body]]
90
+ end
64
91
  end
65
92
  end
66
93
  end
@@ -1,16 +1,23 @@
1
- require 'puma/const'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'uri'
4
+ require 'socket'
5
+
6
+ require 'puma/const'
7
+ require 'puma/util'
8
+ require 'puma/minissl/context_builder'
3
9
 
4
10
  module Puma
5
11
  class Binder
6
12
  include Puma::Const
7
13
 
8
- RACK_VERSION = [1,3].freeze
14
+ RACK_VERSION = [1,6].freeze
9
15
 
10
16
  def initialize(events)
11
17
  @events = events
12
18
  @listeners = []
13
19
  @inherited_fds = {}
20
+ @activated_sockets = {}
14
21
  @unix_paths = []
15
22
 
16
23
  @proto_env = {
@@ -22,13 +29,13 @@ module Puma
22
29
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
23
30
 
24
31
  # I'd like to set a default CONTENT_TYPE here but some things
25
- # depend on their not being a default set and infering
32
+ # depend on their not being a default set and inferring
26
33
  # it from the content. And so if i set it here, it won't
27
34
  # infer properly.
28
35
 
29
36
  "QUERY_STRING".freeze => "",
30
37
  SERVER_PROTOCOL => HTTP_11,
31
- SERVER_SOFTWARE => PUMA_VERSION,
38
+ SERVER_SOFTWARE => PUMA_SERVER_STRING,
32
39
  GATEWAY_INTERFACE => CGI_VER
33
40
  }
34
41
 
@@ -36,7 +43,8 @@ module Puma
36
43
  @ios = []
37
44
  end
38
45
 
39
- attr_reader :listeners, :ios
46
+ attr_reader :ios, :listeners, :unix_paths, :proto_env, :envs, :activated_sockets, :inherited_fds
47
+ attr_writer :ios, :listeners
40
48
 
41
49
  def env(sock)
42
50
  @envs.fetch(sock, @proto_env)
@@ -44,73 +52,85 @@ module Puma
44
52
 
45
53
  def close
46
54
  @ios.each { |i| i.close }
47
- @unix_paths.each { |i| File.unlink i }
48
55
  end
49
56
 
50
- def import_from_env
51
- remove = []
57
+ def connected_ports
58
+ ios.map { |io| io.addr[1] }.uniq
59
+ end
52
60
 
53
- ENV.each do |k,v|
54
- if k =~ /PUMA_INHERIT_\d+/
55
- fd, url = v.split(":", 2)
56
- @inherited_fds[url] = fd.to_i
57
- remove << k
58
- end
59
- if k =~ /LISTEN_FDS/ && ENV['LISTEN_PID'].to_i == $$
60
- v.to_i.times do |num|
61
- fd = num + 3
62
- sock = TCPServer.for_fd(fd)
63
- begin
64
- url = "unix://" + Socket.unpack_sockaddr_un(sock.getsockname)
65
- rescue ArgumentError
66
- port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
67
- if addr =~ /\:/
68
- addr = "[#{addr}]"
69
- end
70
- url = "tcp://#{addr}:#{port}"
71
- end
72
- @inherited_fds[url] = sock
73
- end
74
- ENV.delete k
75
- ENV.delete 'LISTEN_PID'
76
- end
77
- end
61
+ def create_inherited_fds(env_hash)
62
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
63
+ fd, url = v.split(":", 2)
64
+ @inherited_fds[url] = fd.to_i
65
+ end.keys # pass keys back for removal
66
+ end
78
67
 
79
- remove.each do |k|
80
- ENV.delete k
68
+ # systemd socket activation.
69
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
70
+ # LISTEN_PID = PID of the service process, aka us
71
+ # see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
72
+ def create_activated_fds(env_hash)
73
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
74
+ env_hash['LISTEN_FDS'].to_i.times do |index|
75
+ sock = TCPServer.for_fd(socket_activation_fd(index))
76
+ key = begin # Try to parse as a path
77
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
78
+ rescue ArgumentError # Try to parse as a port/ip
79
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
80
+ addr = "[#{addr}]" if addr =~ /\:/
81
+ [:tcp, addr, port]
82
+ end
83
+ @activated_sockets[key] = sock
84
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
81
85
  end
86
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
82
87
  end
83
88
 
84
- def parse(binds, logger)
89
+ def parse(binds, logger, log_msg = 'Listening')
85
90
  binds.each do |str|
86
91
  uri = URI.parse str
87
92
  case uri.scheme
88
93
  when "tcp"
89
94
  if fd = @inherited_fds.delete(str)
90
- logger.log "* Inherited #{str}"
91
95
  io = inherit_tcp_listener uri.host, uri.port, fd
96
+ logger.log "* Inherited #{str}"
97
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
98
+ io = inherit_tcp_listener uri.host, uri.port, sock
99
+ logger.log "* Activated #{str}"
92
100
  else
93
101
  params = Util.parse_query uri.query
94
102
 
95
103
  opt = params.key?('low_latency')
96
104
  bak = params.fetch('backlog', 1024).to_i
97
105
 
98
- logger.log "* Listening on #{str}"
99
106
  io = add_tcp_listener uri.host, uri.port, opt, bak
107
+
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 "* #{log_msg} on tcp://#{addr}"
117
+ end
100
118
  end
101
119
 
102
- @listeners << [str, io]
120
+ @listeners << [str, io] if io
103
121
  when "unix"
104
122
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
105
123
 
106
124
  if fd = @inherited_fds.delete(str)
107
- logger.log "* Inherited #{str}"
108
125
  io = inherit_unix_listener path, fd
126
+ logger.log "* Inherited #{str}"
127
+ elsif sock = @activated_sockets.delete([ :unix, path ])
128
+ io = inherit_unix_listener path, sock
129
+ logger.log "* Activated #{str}"
109
130
  else
110
- logger.log "* Listening on #{str}"
111
-
112
131
  umask = nil
113
132
  mode = nil
133
+ backlog = 1024
114
134
 
115
135
  if uri.query
116
136
  params = Util.parse_query uri.query
@@ -122,77 +142,33 @@ module Puma
122
142
  if u = params['mode']
123
143
  mode = Integer('0'+u)
124
144
  end
145
+
146
+ if u = params['backlog']
147
+ backlog = Integer(u)
148
+ end
125
149
  end
126
150
 
127
- io = add_unix_listener path, umask, mode
151
+ io = add_unix_listener path, umask, mode, backlog
152
+ logger.log "* #{log_msg} on #{str}"
128
153
  end
129
154
 
130
155
  @listeners << [str, io]
131
156
  when "ssl"
132
- MiniSSL.check
133
-
134
157
  params = Util.parse_query uri.query
135
- require 'puma/minissl'
136
-
137
- ctx = MiniSSL::Context.new
138
-
139
- if defined?(JRUBY_VERSION)
140
- unless params['keystore']
141
- @events.error "Please specify the Java keystore via 'keystore='"
142
- end
143
-
144
- ctx.keystore = params['keystore']
145
-
146
- unless params['keystore-pass']
147
- @events.error "Please specify the Java keystore password via 'keystore-pass='"
148
- end
149
-
150
- ctx.keystore_pass = params['keystore-pass']
151
- else
152
- unless params['key']
153
- @events.error "Please specify the SSL key via 'key='"
154
- end
155
-
156
- ctx.key = params['key']
157
-
158
- unless params['cert']
159
- @events.error "Please specify the SSL cert via 'cert='"
160
- end
161
-
162
- ctx.cert = params['cert']
163
-
164
- if ['peer', 'force_peer'].include?(params['verify_mode'])
165
- unless params['ca']
166
- @events.error "Please specify the SSL ca via 'ca='"
167
- end
168
- end
169
-
170
- ctx.ca = params['ca'] if params['ca']
171
-
172
- if params['verify_mode']
173
- ctx.verify_mode = case params['verify_mode']
174
- when "peer"
175
- MiniSSL::VERIFY_PEER
176
- when "force_peer"
177
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
178
- when "none"
179
- MiniSSL::VERIFY_NONE
180
- else
181
- @events.error "Please specify a valid verify_mode="
182
- MiniSSL::VERIFY_NONE
183
- end
184
- end
185
- end
158
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
186
159
 
187
160
  if fd = @inherited_fds.delete(str)
188
161
  logger.log "* Inherited #{str}"
189
- io = inherited_ssl_listener fd, ctx
162
+ io = inherit_ssl_listener fd, ctx
163
+ elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
164
+ io = inherit_ssl_listener sock, ctx
165
+ logger.log "* Activated #{str}"
190
166
  else
191
- logger.log "* Listening on #{str}"
192
167
  io = add_ssl_listener uri.host, uri.port, ctx
168
+ logger.log "* Listening on #{str}"
193
169
  end
194
170
 
195
- @listeners << [str, io]
171
+ @listeners << [str, io] if io
196
172
  else
197
173
  logger.error "Invalid URI: #{str}"
198
174
  end
@@ -204,12 +180,7 @@ module Puma
204
180
  logger.log "* Closing unused inherited connection: #{str}"
205
181
 
206
182
  begin
207
- if fd.kind_of? TCPServer
208
- fd.close
209
- else
210
- IO.for_fd(fd).close
211
- end
212
-
183
+ IO.for_fd(fd).close
213
184
  rescue SystemCallError
214
185
  end
215
186
 
@@ -221,6 +192,16 @@ module Puma
221
192
  end
222
193
  end
223
194
 
195
+ # 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
201
+ 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
+ end
224
205
  end
225
206
 
226
207
  # Tell the server to listen on host +host+, port +port+.
@@ -231,21 +212,25 @@ module Puma
231
212
  # allow to accumulate before returning connection refused.
232
213
  #
233
214
  def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
215
+ if host == "localhost"
216
+ loopback_addresses.each do |addr|
217
+ add_tcp_listener addr, port, optimize_for_latency, backlog
218
+ end
219
+ return
220
+ end
221
+
234
222
  host = host[1..-2] if host and host[0..0] == '['
235
- s = TCPServer.new(host, port)
223
+ tcp_server = TCPServer.new(host, port)
236
224
  if optimize_for_latency
237
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
225
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
238
226
  end
239
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
240
- s.listen backlog
241
- @connected_port = s.addr[1]
227
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
228
+ tcp_server.listen backlog
242
229
 
243
- @ios << s
244
- s
230
+ @ios << tcp_server
231
+ tcp_server
245
232
  end
246
233
 
247
- attr_reader :connected_port
248
-
249
234
  def inherit_tcp_listener(host, port, fd)
250
235
  if fd.kind_of? TCPServer
251
236
  s = fd
@@ -263,6 +248,13 @@ module Puma
263
248
 
264
249
  MiniSSL.check
265
250
 
251
+ if host == "localhost"
252
+ loopback_addresses.each do |addr|
253
+ add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
254
+ end
255
+ return
256
+ end
257
+
266
258
  host = host[1..-2] if host[0..0] == '['
267
259
  s = TCPServer.new(host, port)
268
260
  if optimize_for_latency
@@ -271,6 +263,7 @@ module Puma
271
263
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
272
264
  s.listen backlog
273
265
 
266
+
274
267
  ssl = MiniSSL::Server.new s, ctx
275
268
  env = @proto_env.dup
276
269
  env[HTTPS_KEY] = HTTPS
@@ -280,11 +273,15 @@ module Puma
280
273
  s
281
274
  end
282
275
 
283
- def inherited_ssl_listener(fd, ctx)
276
+ def inherit_ssl_listener(fd, ctx)
284
277
  require 'puma/minissl'
285
278
  MiniSSL.check
286
279
 
287
- s = TCPServer.for_fd(fd)
280
+ if fd.kind_of? TCPServer
281
+ s = fd
282
+ else
283
+ s = TCPServer.for_fd(fd)
284
+ end
288
285
  ssl = MiniSSL::Server.new(s, ctx)
289
286
 
290
287
  env = @proto_env.dup
@@ -298,8 +295,8 @@ module Puma
298
295
 
299
296
  # Tell the server to listen on +path+ as a UNIX domain socket.
300
297
  #
301
- def add_unix_listener(path, umask=nil, mode=nil)
302
- @unix_paths << path
298
+ def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
299
+ @unix_paths << path unless File.exist? path
303
300
 
304
301
  # Let anyone connect by default
305
302
  umask ||= 0
@@ -319,6 +316,7 @@ module Puma
319
316
  end
320
317
 
321
318
  s = UNIXServer.new(path)
319
+ s.listen backlog
322
320
  @ios << s
323
321
  ensure
324
322
  File.umask old_mask
@@ -336,7 +334,7 @@ module Puma
336
334
  end
337
335
 
338
336
  def inherit_unix_listener(path, fd)
339
- @unix_paths << path
337
+ @unix_paths << path unless File.exist? path
340
338
 
341
339
  if fd.kind_of? TCPServer
342
340
  s = fd
@@ -352,5 +350,38 @@ module Puma
352
350
  s
353
351
  end
354
352
 
353
+ def close_listeners
354
+ listeners.each do |l, io|
355
+ io.close unless io.closed? # Ruby 2.2 issue
356
+ uri = URI.parse(l)
357
+ next unless uri.scheme == 'unix'
358
+ unix_path = "#{uri.host}#{uri.path}"
359
+ File.unlink unix_path if unix_paths.include? unix_path
360
+ end
361
+ end
362
+
363
+ def redirects_for_restart
364
+ redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
365
+ redirects[:close_others] = true
366
+ redirects
367
+ end
368
+
369
+ def redirects_for_restart_env
370
+ listeners.each_with_object({}).with_index do |(listen, memo), i|
371
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
372
+ end
373
+ end
374
+
375
+ private
376
+
377
+ def loopback_addresses
378
+ Socket.ip_address_list.select do |addrinfo|
379
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
380
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
381
+ end
382
+
383
+ def socket_activation_fd(int)
384
+ int + 3 # 3 is the magic number you add to follow the SA protocol
385
+ end
355
386
  end
356
387
  end