puma 4.3.12 → 6.3.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1729 -521
  3. data/LICENSE +23 -20
  4. data/README.md +169 -45
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  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 +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +84 -128
  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 -4
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +49 -12
  30. data/ext/puma_http11/http11_parser.c +46 -48
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +3 -3
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +2 -2
  35. data/ext/puma_http11/mini_ssl.c +278 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  40. data/ext/puma_http11/puma_http11.c +46 -57
  41. data/lib/puma/app/status.rb +53 -39
  42. data/lib/puma/binder.rb +237 -121
  43. data/lib/puma/cli.rb +34 -34
  44. data/lib/puma/client.rb +172 -98
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +226 -231
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +114 -87
  50. data/lib/puma/const.rb +139 -95
  51. data/lib/puma/control_cli.rb +99 -79
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +516 -110
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -115
  56. data/lib/puma/io_buffer.rb +44 -2
  57. data/lib/puma/jruby_restart.rb +2 -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 +164 -155
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +36 -19
  63. data/lib/puma/minissl.rb +230 -55
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +1 -1
  67. data/lib/puma/plugin.rb +3 -12
  68. data/lib/puma/rack/builder.rb +7 -11
  69. data/lib/puma/rack/urlmap.rb +0 -0
  70. data/lib/puma/rack_default.rb +19 -4
  71. data/lib/puma/reactor.rb +93 -368
  72. data/lib/puma/request.rb +671 -0
  73. data/lib/puma/runner.rb +92 -75
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +321 -794
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +140 -68
  79. data/lib/puma/util.rb +21 -4
  80. data/lib/puma.rb +54 -7
  81. data/lib/rack/handler/puma.rb +113 -87
  82. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  83. data/tools/trickletest.rb +0 -0
  84. metadata +33 -24
  85. data/docs/tcp_mode.md +0 -96
  86. data/ext/puma_http11/io_buffer.c +0 -155
  87. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  88. data/lib/puma/accept_nonblock.rb +0 -29
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. 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,7 +24,9 @@ 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;
26
31
  import java.nio.Buffer;
27
32
  import java.nio.ByteBuffer;
@@ -30,13 +35,18 @@ import java.security.KeyStore;
30
35
  import java.security.KeyStoreException;
31
36
  import java.security.NoSuchAlgorithmException;
32
37
  import java.security.UnrecoverableKeyException;
38
+ import java.security.cert.Certificate;
33
39
  import java.security.cert.CertificateEncodingException;
34
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;
35
45
 
36
46
  import static javax.net.ssl.SSLEngineResult.Status;
37
47
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
38
48
 
39
- public class MiniSSL extends RubyObject {
49
+ public class MiniSSL extends RubyObject { // MiniSSL::Engine
40
50
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
41
51
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
42
52
  return new MiniSSL(runtime, klass);
@@ -47,11 +57,10 @@ public class MiniSSL extends RubyObject {
47
57
  RubyModule mPuma = runtime.defineModule("Puma");
48
58
  RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
49
59
 
50
- mPuma.defineClassUnder("SSLError",
51
- runtime.getClass("IOError"),
52
- runtime.getClass("IOError").getAllocator());
60
+ // Puma::MiniSSL::SSLError
61
+ ssl.defineClassUnder("SSLError", runtime.getStandardError(), runtime.getStandardError().getAllocator());
53
62
 
54
- RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
63
+ RubyClass eng = ssl.defineClassUnder("Engine", runtime.getObject(), ALLOCATOR);
55
64
  eng.defineAnnotatedMethods(MiniSSL.class);
56
65
  }
57
66
 
@@ -77,11 +86,11 @@ public class MiniSSL extends RubyObject {
77
86
  /**
78
87
  * Writes bytes to the buffer after ensuring there's room
79
88
  */
80
- public void put(byte[] bytes) {
81
- if (buffer.remaining() < bytes.length) {
82
- 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);
83
92
  }
84
- buffer.put(bytes);
93
+ buffer.put(bytes, offset, length);
85
94
  }
86
95
 
87
96
  /**
@@ -112,7 +121,7 @@ public class MiniSSL extends RubyObject {
112
121
 
113
122
  buffer.get(bss);
114
123
  buffer.clear();
115
- return new ByteList(bss);
124
+ return new ByteList(bss, false);
116
125
  }
117
126
 
118
127
  @Override
@@ -120,6 +129,8 @@ public class MiniSSL extends RubyObject {
120
129
  }
121
130
 
122
131
  private SSLEngine engine;
132
+ private boolean closed;
133
+ private boolean handshake;
123
134
  private MiniSSLBuffer inboundNetData;
124
135
  private MiniSSLBuffer outboundAppData;
125
136
  private MiniSSLBuffer outboundNetData;
@@ -128,52 +139,119 @@ public class MiniSSL extends RubyObject {
128
139
  super(runtime, klass);
129
140
  }
130
141
 
131
- @JRubyMethod(meta = true)
132
- public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) {
133
- 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
+ }
134
175
 
135
- return klass.newInstance(context,
136
- new IRubyObject[] { miniSSLContext },
137
- Block.NULL_BLOCK);
138
- }
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
+ }
139
199
 
140
- @JRubyMethod
141
- public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
142
- throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
143
- KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
144
- KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
200
+ RubyClass klass = (RubyClass) recv;
201
+ return klass.newInstance(context, miniSSLContext, Block.NULL_BLOCK);
202
+ }
145
203
 
146
- char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
147
- String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
148
- ks.load(new FileInputStream(keystoreFile), password);
149
- 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
+ }
150
208
 
151
- KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
152
- kmf.init(ks, password);
209
+ private static boolean isDefaultSymbol(ThreadContext context, IRubyObject truststore) {
210
+ return context.runtime.newSymbol("default").equals(truststore);
211
+ }
153
212
 
154
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
155
- 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
+ }
156
225
 
157
226
  SSLContext sslCtx = SSLContext.getInstance("TLS");
158
227
 
159
- sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
228
+ sslCtx.init(kmf.getKeyManagers(), getTrustManagers(tmf), null);
229
+ closed = false;
230
+ handshake = false;
160
231
  engine = sslCtx.createSSLEngine();
161
232
 
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
- }
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
+ }
168
241
 
169
- if(miniSSLContext.callMethod(threadContext, "no_tlsv1_1").isTrue()) {
170
- protocols = new String[] { "TLSv1.2" };
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());
171
249
  }
250
+ engine.setEnabledProtocols(enabledProtocols);
172
251
 
173
- engine.setEnabledProtocols(protocols);
174
252
  engine.setUseClientMode(false);
175
253
 
176
- long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
254
+ long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
177
255
  if ((verify_mode & 0x1) != 0) { // 'peer'
178
256
  engine.setWantClientAuth(true);
179
257
  }
@@ -181,10 +259,11 @@ public class MiniSSL extends RubyObject {
181
259
  engine.setNeedClientAuth(true);
182
260
  }
183
261
 
184
- IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
185
- if (!sslCipherListObject.isNil()) {
186
- String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
187
- engine.setEnabledCipherSuites(sslCipherList);
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());
188
267
  }
189
268
 
190
269
  SSLSession session = engine.getSession();
@@ -196,16 +275,53 @@ public class MiniSSL extends RubyObject {
196
275
  return this;
197
276
  }
198
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
+
199
320
  @JRubyMethod
200
321
  public IRubyObject inject(IRubyObject arg) {
201
- try {
202
- byte[] bytes = arg.convertToString().getBytes();
203
- inboundNetData.put(bytes);
204
- return this;
205
- } catch (Exception e) {
206
- e.printStackTrace();
207
- throw new RuntimeException(e);
208
- }
322
+ ByteList bytes = arg.convertToString().getByteList();
323
+ inboundNetData.put(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
324
+ return this;
209
325
  }
210
326
 
211
327
  private enum SSLOperation {
@@ -225,7 +341,7 @@ public class MiniSSL extends RubyObject {
225
341
  res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
226
342
  break;
227
343
  default:
228
- throw new IllegalStateException("Unknown SSLOperation: " + sslOp);
344
+ throw new AssertionError("Unknown SSLOperation: " + sslOp);
229
345
  }
230
346
 
231
347
  switch (res.getStatus()) {
@@ -240,17 +356,16 @@ public class MiniSSL extends RubyObject {
240
356
  // need to wait for more data to come in before we retry
241
357
  retryOp = false;
242
358
  break;
359
+ case CLOSED:
360
+ closed = true;
361
+ retryOp = false;
362
+ break;
243
363
  default:
244
- // other cases are OK and CLOSED. We're done here.
364
+ // other case is OK. We're done here.
245
365
  retryOp = false;
246
366
  }
247
- }
248
-
249
- // after each op, run any delegated tasks if needed
250
- if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
251
- Runnable runnable;
252
- while ((runnable = engine.getDelegatedTask()) != null) {
253
- runnable.run();
367
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
368
+ handshake = true;
254
369
  }
255
370
  }
256
371
 
@@ -258,7 +373,7 @@ public class MiniSSL extends RubyObject {
258
373
  }
259
374
 
260
375
  @JRubyMethod
261
- public IRubyObject read() throws Exception {
376
+ public IRubyObject read() {
262
377
  try {
263
378
  inboundNetData.flip();
264
379
 
@@ -272,21 +387,30 @@ public class MiniSSL extends RubyObject {
272
387
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
273
388
  boolean done = false;
274
389
  while (!done) {
390
+ SSLEngineResult res;
275
391
  switch (handshakeStatus) {
276
392
  case NEED_WRAP:
277
- doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
393
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
394
+ handshakeStatus = res.getHandshakeStatus();
278
395
  break;
279
396
  case NEED_UNWRAP:
280
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
397
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
281
398
  if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
282
399
  // need more data before we can shake more hands
283
400
  done = true;
284
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();
285
410
  break;
286
411
  default:
287
412
  done = true;
288
413
  }
289
- handshakeStatus = engine.getHandshakeStatus();
290
414
  }
291
415
 
292
416
  if (inboundNetData.hasRemaining()) {
@@ -300,64 +424,85 @@ public class MiniSSL extends RubyObject {
300
424
  return getRuntime().getNil();
301
425
  }
302
426
 
303
- RubyString str = getRuntime().newString("");
304
- str.setValue(appDataByteList);
305
- return str;
306
- } catch (Exception e) {
307
- throw getRuntime().newEOFError(e.getMessage());
427
+ return RubyString.newString(getRuntime(), appDataByteList);
428
+ } catch (SSLException e) {
429
+ throw newSSLError(getRuntime(), e);
308
430
  }
309
431
  }
310
432
 
311
433
  @JRubyMethod
312
434
  public IRubyObject write(IRubyObject arg) {
313
- try {
314
- byte[] bls = arg.convertToString().getBytes();
315
- outboundAppData = new MiniSSLBuffer(bls);
435
+ byte[] bls = arg.convertToString().getBytes();
436
+ outboundAppData = new MiniSSLBuffer(bls);
316
437
 
317
- return getRuntime().newFixnum(bls.length);
318
- } catch (Exception e) {
319
- e.printStackTrace();
320
- throw new RuntimeException(e);
321
- }
438
+ return getRuntime().newFixnum(bls.length);
322
439
  }
323
440
 
324
441
  @JRubyMethod
325
- public IRubyObject extract() throws SSLException {
442
+ public IRubyObject extract(ThreadContext context) {
326
443
  try {
327
444
  ByteList dataByteList = outboundNetData.asByteList();
328
445
  if (dataByteList != null) {
329
- RubyString str = getRuntime().newString("");
330
- str.setValue(dataByteList);
331
- return str;
446
+ return RubyString.newString(context.runtime, dataByteList);
332
447
  }
333
448
 
334
449
  if (!outboundAppData.hasRemaining()) {
335
- return getRuntime().getNil();
450
+ return context.nil;
336
451
  }
337
452
 
338
453
  outboundNetData.clear();
339
454
  doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
340
455
  dataByteList = outboundNetData.asByteList();
341
456
  if (dataByteList == null) {
342
- return getRuntime().getNil();
457
+ return context.nil;
343
458
  }
344
459
 
345
- RubyString str = getRuntime().newString("");
346
- str.setValue(dataByteList);
347
-
348
- return str;
349
- } catch (Exception e) {
350
- e.printStackTrace();
351
- throw new RuntimeException(e);
460
+ return RubyString.newString(context.runtime, dataByteList);
461
+ } catch (SSLException e) {
462
+ throw newSSLError(getRuntime(), e);
352
463
  }
353
464
  }
354
465
 
355
466
  @JRubyMethod
356
- public IRubyObject peercert() throws CertificateEncodingException {
467
+ public IRubyObject peercert(ThreadContext context) throws CertificateEncodingException {
468
+ Certificate peerCert;
357
469
  try {
358
- return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
359
- } catch (SSLPeerUnverifiedException ex) {
360
- 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();
361
491
  }
362
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
+
363
508
  }