puma 3.11.4 → 6.0.1

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1717 -432
  3. data/LICENSE +23 -20
  4. data/README.md +190 -64
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +95 -120
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +61 -3
  30. data/ext/puma_http11/http11_parser.c +106 -118
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +22 -38
  33. data/ext/puma_http11/http11_parser.rl +6 -4
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +376 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +250 -88
  40. data/ext/puma_http11/puma_http11.c +49 -57
  41. data/lib/puma/app/status.rb +71 -49
  42. data/lib/puma/binder.rb +243 -148
  43. data/lib/puma/cli.rb +50 -36
  44. data/lib/puma/client.rb +373 -233
  45. data/lib/puma/cluster/worker.rb +175 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +268 -235
  48. data/lib/puma/commonlogger.rb +4 -2
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +49 -30
  51. data/lib/puma/control_cli.rb +123 -76
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +685 -135
  54. data/lib/puma/error_logger.rb +112 -0
  55. data/lib/puma/events.rb +17 -111
  56. data/lib/puma/io_buffer.rb +44 -5
  57. data/lib/puma/jruby_restart.rb +4 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +196 -130
  61. data/lib/puma/log_writer.rb +137 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +249 -69
  64. data/lib/puma/null_io.rb +20 -1
  65. data/lib/puma/plugin/tmp_restart.rb +3 -1
  66. data/lib/puma/plugin.rb +9 -13
  67. data/lib/puma/rack/builder.rb +8 -9
  68. data/lib/puma/rack/urlmap.rb +2 -0
  69. data/lib/puma/rack_default.rb +3 -1
  70. data/lib/puma/reactor.rb +90 -187
  71. data/lib/puma/request.rb +644 -0
  72. data/lib/puma/runner.rb +94 -71
  73. data/lib/puma/server.rb +337 -715
  74. data/lib/puma/single.rb +27 -72
  75. data/lib/puma/state_file.rb +46 -7
  76. data/lib/puma/systemd.rb +47 -0
  77. data/lib/puma/thread_pool.rb +184 -93
  78. data/lib/puma/util.rb +23 -10
  79. data/lib/puma.rb +60 -3
  80. data/lib/rack/handler/puma.rb +17 -15
  81. data/tools/Dockerfile +16 -0
  82. data/tools/trickletest.rb +0 -1
  83. metadata +53 -33
  84. data/ext/puma_http11/io_buffer.c +0 -155
  85. data/lib/puma/accept_nonblock.rb +0 -23
  86. data/lib/puma/compat.rb +0 -14
  87. data/lib/puma/convenient.rb +0 -23
  88. data/lib/puma/daemon_ext.rb +0 -31
  89. data/lib/puma/delegation.rb +0 -11
  90. data/lib/puma/java_io_buffer.rb +0 -45
  91. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  92. data/lib/puma/tcp_logger.rb +0 -39
  93. data/tools/jungle/README.md +0 -19
  94. data/tools/jungle/init.d/README.md +0 -61
  95. data/tools/jungle/init.d/puma +0 -421
  96. data/tools/jungle/init.d/run-puma +0 -18
  97. data/tools/jungle/upstart/README.md +0 -61
  98. data/tools/jungle/upstart/puma-manager.conf +0 -31
  99. data/tools/jungle/upstart/puma.conf +0 -69
@@ -1,11 +1,13 @@
1
1
  package org.jruby.puma;
2
2
 
3
3
  import org.jruby.Ruby;
4
+ import org.jruby.RubyArray;
4
5
  import org.jruby.RubyClass;
5
6
  import org.jruby.RubyModule;
6
7
  import org.jruby.RubyObject;
7
8
  import org.jruby.RubyString;
8
9
  import org.jruby.anno.JRubyMethod;
10
+ import org.jruby.exceptions.RaiseException;
9
11
  import org.jruby.javasupport.JavaEmbedUtils;
10
12
  import org.jruby.runtime.Block;
11
13
  import org.jruby.runtime.ObjectAllocator;
@@ -14,6 +16,7 @@ import org.jruby.runtime.builtin.IRubyObject;
14
16
  import org.jruby.util.ByteList;
15
17
 
16
18
  import javax.net.ssl.KeyManagerFactory;
19
+ import javax.net.ssl.TrustManager;
17
20
  import javax.net.ssl.TrustManagerFactory;
18
21
  import javax.net.ssl.SSLContext;
19
22
  import javax.net.ssl.SSLEngine;
@@ -21,21 +24,29 @@ import javax.net.ssl.SSLEngineResult;
21
24
  import javax.net.ssl.SSLException;
22
25
  import javax.net.ssl.SSLPeerUnverifiedException;
23
26
  import javax.net.ssl.SSLSession;
27
+ import javax.net.ssl.X509TrustManager;
24
28
  import java.io.FileInputStream;
29
+ import java.io.InputStream;
25
30
  import java.io.IOException;
31
+ import java.nio.Buffer;
26
32
  import java.nio.ByteBuffer;
27
33
  import java.security.KeyManagementException;
28
34
  import java.security.KeyStore;
29
35
  import java.security.KeyStoreException;
30
36
  import java.security.NoSuchAlgorithmException;
31
37
  import java.security.UnrecoverableKeyException;
38
+ import java.security.cert.Certificate;
32
39
  import java.security.cert.CertificateEncodingException;
33
40
  import java.security.cert.CertificateException;
41
+ import java.security.cert.X509Certificate;
42
+ import java.util.concurrent.ConcurrentHashMap;
43
+ import java.util.Map;
44
+ import java.util.function.Supplier;
34
45
 
35
46
  import static javax.net.ssl.SSLEngineResult.Status;
36
47
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
37
48
 
38
- public class MiniSSL extends RubyObject {
49
+ public class MiniSSL extends RubyObject { // MiniSSL::Engine
39
50
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
40
51
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
41
52
  return new MiniSSL(runtime, klass);
@@ -46,11 +57,10 @@ public class MiniSSL extends RubyObject {
46
57
  RubyModule mPuma = runtime.defineModule("Puma");
47
58
  RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
48
59
 
49
- mPuma.defineClassUnder("SSLError",
50
- runtime.getClass("IOError"),
51
- runtime.getClass("IOError").getAllocator());
60
+ // Puma::MiniSSL::SSLError
61
+ ssl.defineClassUnder("SSLError", runtime.getStandardError(), runtime.getStandardError().getAllocator());
52
62
 
53
- RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
63
+ RubyClass eng = ssl.defineClassUnder("Engine", runtime.getObject(), ALLOCATOR);
54
64
  eng.defineAnnotatedMethods(MiniSSL.class);
55
65
  }
56
66
 
@@ -65,7 +75,7 @@ public class MiniSSL extends RubyObject {
65
75
 
66
76
  public void clear() { buffer.clear(); }
67
77
  public void compact() { buffer.compact(); }
68
- public void flip() { buffer.flip(); }
78
+ public void flip() { ((Buffer) buffer).flip(); }
69
79
  public boolean hasRemaining() { return buffer.hasRemaining(); }
70
80
  public int position() { return buffer.position(); }
71
81
 
@@ -76,11 +86,11 @@ public class MiniSSL extends RubyObject {
76
86
  /**
77
87
  * Writes bytes to the buffer after ensuring there's room
78
88
  */
79
- public void put(byte[] bytes) {
80
- if (buffer.remaining() < bytes.length) {
81
- resize(buffer.limit() + bytes.length);
89
+ private void put(byte[] bytes, final int offset, final int length) {
90
+ if (buffer.remaining() < length) {
91
+ resize(buffer.limit() + length);
82
92
  }
83
- buffer.put(bytes);
93
+ buffer.put(bytes, offset, length);
84
94
  }
85
95
 
86
96
  /**
@@ -89,7 +99,7 @@ public class MiniSSL extends RubyObject {
89
99
  public void resize(int newCapacity) {
90
100
  if (newCapacity > buffer.capacity()) {
91
101
  ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
92
- buffer.flip();
102
+ flip();
93
103
  dstTmp.put(buffer);
94
104
  buffer = dstTmp;
95
105
  } else {
@@ -101,7 +111,7 @@ public class MiniSSL extends RubyObject {
101
111
  * Drains the buffer to a ByteList, or returns null for an empty buffer
102
112
  */
103
113
  public ByteList asByteList() {
104
- buffer.flip();
114
+ flip();
105
115
  if (!buffer.hasRemaining()) {
106
116
  buffer.clear();
107
117
  return null;
@@ -111,7 +121,7 @@ public class MiniSSL extends RubyObject {
111
121
 
112
122
  buffer.get(bss);
113
123
  buffer.clear();
114
- return new ByteList(bss);
124
+ return new ByteList(bss, false);
115
125
  }
116
126
 
117
127
  @Override
@@ -119,6 +129,8 @@ public class MiniSSL extends RubyObject {
119
129
  }
120
130
 
121
131
  private SSLEngine engine;
132
+ private boolean closed;
133
+ private boolean handshake;
122
134
  private MiniSSLBuffer inboundNetData;
123
135
  private MiniSSLBuffer outboundAppData;
124
136
  private MiniSSLBuffer outboundNetData;
@@ -127,42 +139,119 @@ public class MiniSSL extends RubyObject {
127
139
  super(runtime, klass);
128
140
  }
129
141
 
130
- @JRubyMethod(meta = true)
131
- public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) {
132
- RubyClass klass = (RubyClass) recv;
142
+ private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
143
+ private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
144
+
145
+ @JRubyMethod(meta = true) // Engine.server
146
+ public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
147
+ throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
148
+ // Create the KeyManagerFactory and TrustManagerFactory for this server
149
+ String keystoreFile = asStringValue(miniSSLContext.callMethod(context, "keystore"), null);
150
+ char[] keystorePass = asStringValue(miniSSLContext.callMethod(context, "keystore_pass"), null).toCharArray();
151
+ String keystoreType = asStringValue(miniSSLContext.callMethod(context, "keystore_type"), KeyStore::getDefaultType);
152
+
153
+ String truststoreFile;
154
+ char[] truststorePass;
155
+ String truststoreType;
156
+ IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
157
+ if (truststore.isNil()) {
158
+ truststoreFile = keystoreFile;
159
+ truststorePass = keystorePass;
160
+ truststoreType = keystoreType;
161
+ } else if (!isDefaultSymbol(context, truststore)) {
162
+ truststoreFile = truststore.convertToString().asJavaString();
163
+ IRubyObject pass = miniSSLContext.callMethod(context, "truststore_pass");
164
+ if (pass.isNil()) {
165
+ truststorePass = null;
166
+ } else {
167
+ truststorePass = asStringValue(pass, null).toCharArray();
168
+ }
169
+ truststoreType = asStringValue(miniSSLContext.callMethod(context, "truststore_type"), KeyStore::getDefaultType);
170
+ } else { // self.truststore = :default
171
+ truststoreFile = null;
172
+ truststorePass = null;
173
+ truststoreType = null;
174
+ }
133
175
 
134
- return klass.newInstance(context,
135
- new IRubyObject[] { miniSSLContext },
136
- Block.NULL_BLOCK);
137
- }
176
+ KeyStore ks = KeyStore.getInstance(keystoreType);
177
+ InputStream is = new FileInputStream(keystoreFile);
178
+ try {
179
+ ks.load(is, keystorePass);
180
+ } finally {
181
+ is.close();
182
+ }
183
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
184
+ kmf.init(ks, keystorePass);
185
+ keyManagerFactoryMap.put(keystoreFile, kmf);
186
+
187
+ if (truststoreFile != null) {
188
+ KeyStore ts = KeyStore.getInstance(truststoreType);
189
+ is = new FileInputStream(truststoreFile);
190
+ try {
191
+ ts.load(is, truststorePass);
192
+ } finally {
193
+ is.close();
194
+ }
195
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
196
+ tmf.init(ts);
197
+ trustManagerFactoryMap.put(truststoreFile, tmf);
198
+ }
138
199
 
139
- @JRubyMethod
140
- public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
141
- throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
142
- KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
143
- KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
200
+ RubyClass klass = (RubyClass) recv;
201
+ return klass.newInstance(context, miniSSLContext, Block.NULL_BLOCK);
202
+ }
144
203
 
145
- char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
146
- String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
147
- ks.load(new FileInputStream(keystoreFile), password);
148
- ts.load(new FileInputStream(keystoreFile), password);
204
+ private static String asStringValue(IRubyObject value, Supplier<String> defaultValue) {
205
+ if (defaultValue != null && value.isNil()) return defaultValue.get();
206
+ return value.convertToString().asJavaString();
207
+ }
149
208
 
150
- KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
151
- kmf.init(ks, password);
209
+ private static boolean isDefaultSymbol(ThreadContext context, IRubyObject truststore) {
210
+ return context.runtime.newSymbol("default").equals(truststore);
211
+ }
152
212
 
153
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
154
- tmf.init(ts);
213
+ @JRubyMethod
214
+ public IRubyObject initialize(ThreadContext context, IRubyObject miniSSLContext)
215
+ throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
216
+
217
+ String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
218
+ KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
219
+ IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
220
+ String truststoreFile = isDefaultSymbol(context, truststore) ? "" : asStringValue(truststore, () -> keystoreFile);
221
+ TrustManagerFactory tmf = trustManagerFactoryMap.get(truststoreFile); // null if self.truststore = :default
222
+ if (kmf == null) {
223
+ throw new KeyStoreException("Could not find KeyManagerFactory for keystore: " + keystoreFile + " truststore: " + truststoreFile);
224
+ }
155
225
 
156
226
  SSLContext sslCtx = SSLContext.getInstance("TLS");
157
227
 
158
- sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
228
+ sslCtx.init(kmf.getKeyManagers(), getTrustManagers(tmf), null);
229
+ closed = false;
230
+ handshake = false;
159
231
  engine = sslCtx.createSSLEngine();
160
232
 
161
- String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
162
- engine.setEnabledProtocols(protocols);
233
+ String[] enabledProtocols;
234
+ IRubyObject protocols = miniSSLContext.callMethod(context, "protocols");
235
+ if (protocols.isNil()) {
236
+ if (miniSSLContext.callMethod(context, "no_tlsv1").isTrue()) {
237
+ enabledProtocols = new String[] { "TLSv1.1", "TLSv1.2", "TLSv1.3" };
238
+ } else {
239
+ enabledProtocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
240
+ }
241
+
242
+ if (miniSSLContext.callMethod(context, "no_tlsv1_1").isTrue()) {
243
+ enabledProtocols = new String[] { "TLSv1.2", "TLSv1.3" };
244
+ }
245
+ } else if (protocols instanceof RubyArray) {
246
+ enabledProtocols = (String[]) ((RubyArray) protocols).toArray(new String[0]);
247
+ } else {
248
+ throw context.runtime.newTypeError(protocols, context.runtime.getArray());
249
+ }
250
+ engine.setEnabledProtocols(enabledProtocols);
251
+
163
252
  engine.setUseClientMode(false);
164
253
 
165
- long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
254
+ long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
166
255
  if ((verify_mode & 0x1) != 0) { // 'peer'
167
256
  engine.setWantClientAuth(true);
168
257
  }
@@ -170,6 +259,13 @@ public class MiniSSL extends RubyObject {
170
259
  engine.setNeedClientAuth(true);
171
260
  }
172
261
 
262
+ IRubyObject cipher_suites = miniSSLContext.callMethod(context, "cipher_suites");
263
+ if (cipher_suites instanceof RubyArray) {
264
+ engine.setEnabledCipherSuites((String[]) ((RubyArray) cipher_suites).toArray(new String[0]));
265
+ } else if (!cipher_suites.isNil()) {
266
+ throw context.runtime.newTypeError(cipher_suites, context.runtime.getArray());
267
+ }
268
+
173
269
  SSLSession session = engine.getSession();
174
270
  inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
175
271
  outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
@@ -179,16 +275,53 @@ public class MiniSSL extends RubyObject {
179
275
  return this;
180
276
  }
181
277
 
278
+ private TrustManager[] getTrustManagers(TrustManagerFactory factory) {
279
+ if (factory == null) return null; // use JDK trust defaults
280
+ final TrustManager[] tms = factory.getTrustManagers();
281
+ if (tms != null) {
282
+ for (int i=0; i<tms.length; i++) {
283
+ final TrustManager tm = tms[i];
284
+ if (tm instanceof X509TrustManager) {
285
+ tms[i] = new TrustManagerWrapper((X509TrustManager) tm);
286
+ }
287
+ }
288
+ }
289
+ return tms;
290
+ }
291
+
292
+ private volatile transient X509Certificate lastCheckedCert0;
293
+
294
+ private class TrustManagerWrapper implements X509TrustManager {
295
+
296
+ private final X509TrustManager delegate;
297
+
298
+ TrustManagerWrapper(X509TrustManager delegate) {
299
+ this.delegate = delegate;
300
+ }
301
+
302
+ @Override
303
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
304
+ lastCheckedCert0 = chain.length > 0 ? chain[0] : null;
305
+ delegate.checkClientTrusted(chain, authType);
306
+ }
307
+
308
+ @Override
309
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
310
+ delegate.checkServerTrusted(chain, authType);
311
+ }
312
+
313
+ @Override
314
+ public X509Certificate[] getAcceptedIssuers() {
315
+ return delegate.getAcceptedIssuers();
316
+ }
317
+
318
+ }
319
+
182
320
  @JRubyMethod
183
321
  public IRubyObject inject(IRubyObject arg) {
184
- try {
185
- byte[] bytes = arg.convertToString().getBytes();
186
- inboundNetData.put(bytes);
187
- return this;
188
- } catch (Exception e) {
189
- e.printStackTrace();
190
- throw new RuntimeException(e);
191
- }
322
+ ByteList bytes = arg.convertToString().getByteList();
323
+ inboundNetData.put(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
324
+ return this;
192
325
  }
193
326
 
194
327
  private enum SSLOperation {
@@ -208,7 +341,7 @@ public class MiniSSL extends RubyObject {
208
341
  res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
209
342
  break;
210
343
  default:
211
- throw new IllegalStateException("Unknown SSLOperation: " + sslOp);
344
+ throw new AssertionError("Unknown SSLOperation: " + sslOp);
212
345
  }
213
346
 
214
347
  switch (res.getStatus()) {
@@ -223,17 +356,16 @@ public class MiniSSL extends RubyObject {
223
356
  // need to wait for more data to come in before we retry
224
357
  retryOp = false;
225
358
  break;
359
+ case CLOSED:
360
+ closed = true;
361
+ retryOp = false;
362
+ break;
226
363
  default:
227
- // other cases are OK and CLOSED. We're done here.
364
+ // other case is OK. We're done here.
228
365
  retryOp = false;
229
366
  }
230
- }
231
-
232
- // after each op, run any delegated tasks if needed
233
- if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
234
- Runnable runnable;
235
- while ((runnable = engine.getDelegatedTask()) != null) {
236
- runnable.run();
367
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
368
+ handshake = true;
237
369
  }
238
370
  }
239
371
 
@@ -241,7 +373,7 @@ public class MiniSSL extends RubyObject {
241
373
  }
242
374
 
243
375
  @JRubyMethod
244
- public IRubyObject read() throws Exception {
376
+ public IRubyObject read() {
245
377
  try {
246
378
  inboundNetData.flip();
247
379
 
@@ -255,21 +387,30 @@ public class MiniSSL extends RubyObject {
255
387
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
256
388
  boolean done = false;
257
389
  while (!done) {
390
+ SSLEngineResult res;
258
391
  switch (handshakeStatus) {
259
392
  case NEED_WRAP:
260
- doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
393
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
394
+ handshakeStatus = res.getHandshakeStatus();
261
395
  break;
262
396
  case NEED_UNWRAP:
263
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
397
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
264
398
  if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
265
399
  // need more data before we can shake more hands
266
400
  done = true;
267
401
  }
402
+ handshakeStatus = res.getHandshakeStatus();
403
+ break;
404
+ case NEED_TASK:
405
+ Runnable runnable;
406
+ while ((runnable = engine.getDelegatedTask()) != null) {
407
+ runnable.run();
408
+ }
409
+ handshakeStatus = engine.getHandshakeStatus();
268
410
  break;
269
411
  default:
270
412
  done = true;
271
413
  }
272
- handshakeStatus = engine.getHandshakeStatus();
273
414
  }
274
415
 
275
416
  if (inboundNetData.hasRemaining()) {
@@ -283,64 +424,85 @@ public class MiniSSL extends RubyObject {
283
424
  return getRuntime().getNil();
284
425
  }
285
426
 
286
- RubyString str = getRuntime().newString("");
287
- str.setValue(appDataByteList);
288
- return str;
289
- } catch (Exception e) {
290
- throw getRuntime().newEOFError(e.getMessage());
427
+ return RubyString.newString(getRuntime(), appDataByteList);
428
+ } catch (SSLException e) {
429
+ throw newSSLError(getRuntime(), e);
291
430
  }
292
431
  }
293
432
 
294
433
  @JRubyMethod
295
434
  public IRubyObject write(IRubyObject arg) {
296
- try {
297
- byte[] bls = arg.convertToString().getBytes();
298
- outboundAppData = new MiniSSLBuffer(bls);
435
+ byte[] bls = arg.convertToString().getBytes();
436
+ outboundAppData = new MiniSSLBuffer(bls);
299
437
 
300
- return getRuntime().newFixnum(bls.length);
301
- } catch (Exception e) {
302
- e.printStackTrace();
303
- throw new RuntimeException(e);
304
- }
438
+ return getRuntime().newFixnum(bls.length);
305
439
  }
306
440
 
307
441
  @JRubyMethod
308
- public IRubyObject extract() throws SSLException {
442
+ public IRubyObject extract(ThreadContext context) {
309
443
  try {
310
444
  ByteList dataByteList = outboundNetData.asByteList();
311
445
  if (dataByteList != null) {
312
- RubyString str = getRuntime().newString("");
313
- str.setValue(dataByteList);
314
- return str;
446
+ return RubyString.newString(context.runtime, dataByteList);
315
447
  }
316
448
 
317
449
  if (!outboundAppData.hasRemaining()) {
318
- return getRuntime().getNil();
450
+ return context.nil;
319
451
  }
320
452
 
321
453
  outboundNetData.clear();
322
454
  doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
323
455
  dataByteList = outboundNetData.asByteList();
324
456
  if (dataByteList == null) {
325
- return getRuntime().getNil();
457
+ return context.nil;
326
458
  }
327
459
 
328
- RubyString str = getRuntime().newString("");
329
- str.setValue(dataByteList);
330
-
331
- return str;
332
- } catch (Exception e) {
333
- e.printStackTrace();
334
- throw new RuntimeException(e);
460
+ return RubyString.newString(context.runtime, dataByteList);
461
+ } catch (SSLException e) {
462
+ throw newSSLError(getRuntime(), e);
335
463
  }
336
464
  }
337
465
 
338
466
  @JRubyMethod
339
- public IRubyObject peercert() throws CertificateEncodingException {
467
+ public IRubyObject peercert(ThreadContext context) throws CertificateEncodingException {
468
+ Certificate peerCert;
340
469
  try {
341
- return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
342
- } catch (SSLPeerUnverifiedException ex) {
343
- return getRuntime().getNil();
470
+ peerCert = engine.getSession().getPeerCertificates()[0];
471
+ } catch (SSLPeerUnverifiedException e) {
472
+ peerCert = lastCheckedCert0; // null if trust check did not happen
473
+ }
474
+ return peerCert == null ? context.nil : JavaEmbedUtils.javaToRuby(context.runtime, peerCert.getEncoded());
475
+ }
476
+
477
+ @JRubyMethod(name = "init?")
478
+ public IRubyObject isInit(ThreadContext context) {
479
+ return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
480
+ }
481
+
482
+ @JRubyMethod
483
+ public IRubyObject shutdown() {
484
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
485
+ if (engine.isOutboundDone()) {
486
+ engine.closeOutbound();
487
+ }
488
+ return getRuntime().getTrue();
489
+ } else {
490
+ return getRuntime().getFalse();
344
491
  }
345
492
  }
493
+
494
+ private static RubyClass getSSLError(Ruby runtime) {
495
+ return (RubyClass) ((RubyModule) runtime.getModule("Puma").getConstantAt("MiniSSL")).getConstantAt("SSLError");
496
+ }
497
+
498
+ private static RaiseException newSSLError(Ruby runtime, SSLException cause) {
499
+ return newError(runtime, getSSLError(runtime), cause.toString(), cause);
500
+ }
501
+
502
+ private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
503
+ RaiseException ex = new RaiseException(runtime, errorClass, message, true);
504
+ ex.initCause(cause);
505
+ return ex;
506
+ }
507
+
346
508
  }