gitlab-puma 4.3.1.gitlab.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1537 -0
  3. data/LICENSE +26 -0
  4. data/README.md +291 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +37 -0
  9. data/docs/deployment.md +111 -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/nginx.md +80 -0
  14. data/docs/plugins.md +38 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/docs/tcp_mode.md +96 -0
  19. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  20. data/ext/puma_http11/ext_help.h +15 -0
  21. data/ext/puma_http11/extconf.rb +28 -0
  22. data/ext/puma_http11/http11_parser.c +1044 -0
  23. data/ext/puma_http11/http11_parser.h +65 -0
  24. data/ext/puma_http11/http11_parser.java.rl +145 -0
  25. data/ext/puma_http11/http11_parser.rl +147 -0
  26. data/ext/puma_http11/http11_parser_common.rl +54 -0
  27. data/ext/puma_http11/io_buffer.c +155 -0
  28. data/ext/puma_http11/mini_ssl.c +553 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11.java +226 -0
  30. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  31. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  32. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  33. data/ext/puma_http11/puma_http11.c +502 -0
  34. data/lib/puma.rb +31 -0
  35. data/lib/puma/accept_nonblock.rb +29 -0
  36. data/lib/puma/app/status.rb +80 -0
  37. data/lib/puma/binder.rb +385 -0
  38. data/lib/puma/cli.rb +239 -0
  39. data/lib/puma/client.rb +494 -0
  40. data/lib/puma/cluster.rb +554 -0
  41. data/lib/puma/commonlogger.rb +108 -0
  42. data/lib/puma/configuration.rb +362 -0
  43. data/lib/puma/const.rb +242 -0
  44. data/lib/puma/control_cli.rb +289 -0
  45. data/lib/puma/detect.rb +15 -0
  46. data/lib/puma/dsl.rb +740 -0
  47. data/lib/puma/events.rb +156 -0
  48. data/lib/puma/io_buffer.rb +4 -0
  49. data/lib/puma/jruby_restart.rb +84 -0
  50. data/lib/puma/launcher.rb +475 -0
  51. data/lib/puma/minissl.rb +278 -0
  52. data/lib/puma/minissl/context_builder.rb +76 -0
  53. data/lib/puma/null_io.rb +44 -0
  54. data/lib/puma/plugin.rb +120 -0
  55. data/lib/puma/plugin/tmp_restart.rb +36 -0
  56. data/lib/puma/rack/builder.rb +301 -0
  57. data/lib/puma/rack/urlmap.rb +93 -0
  58. data/lib/puma/rack_default.rb +9 -0
  59. data/lib/puma/reactor.rb +400 -0
  60. data/lib/puma/runner.rb +192 -0
  61. data/lib/puma/server.rb +1053 -0
  62. data/lib/puma/single.rb +123 -0
  63. data/lib/puma/state_file.rb +31 -0
  64. data/lib/puma/tcp_logger.rb +41 -0
  65. data/lib/puma/thread_pool.rb +348 -0
  66. data/lib/puma/util.rb +124 -0
  67. data/lib/rack/handler/puma.rb +115 -0
  68. data/tools/docker/Dockerfile +16 -0
  69. data/tools/jungle/README.md +19 -0
  70. data/tools/jungle/init.d/README.md +61 -0
  71. data/tools/jungle/init.d/puma +421 -0
  72. data/tools/jungle/init.d/run-puma +18 -0
  73. data/tools/jungle/rc.d/README.md +74 -0
  74. data/tools/jungle/rc.d/puma +61 -0
  75. data/tools/jungle/rc.d/puma.conf +10 -0
  76. data/tools/jungle/upstart/README.md +61 -0
  77. data/tools/jungle/upstart/puma-manager.conf +31 -0
  78. data/tools/jungle/upstart/puma.conf +69 -0
  79. data/tools/trickletest.rb +44 -0
  80. metadata +147 -0
@@ -0,0 +1,72 @@
1
+ package org.jruby.puma;
2
+
3
+ import org.jruby.*;
4
+ import org.jruby.anno.JRubyMethod;
5
+ import org.jruby.runtime.ObjectAllocator;
6
+ import org.jruby.runtime.ThreadContext;
7
+ import org.jruby.runtime.builtin.IRubyObject;
8
+ import org.jruby.util.ByteList;
9
+
10
+ /**
11
+ * @author kares
12
+ */
13
+ public class IOBuffer extends RubyObject {
14
+
15
+ private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
16
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
17
+ return new IOBuffer(runtime, klass);
18
+ }
19
+ };
20
+
21
+ public static void createIOBuffer(Ruby runtime) {
22
+ RubyModule mPuma = runtime.defineModule("Puma");
23
+ RubyClass cIOBuffer = mPuma.defineClassUnder("IOBuffer", runtime.getObject(), ALLOCATOR);
24
+ cIOBuffer.defineAnnotatedMethods(IOBuffer.class);
25
+ }
26
+
27
+ private static final int DEFAULT_SIZE = 4096;
28
+
29
+ final ByteList buffer = new ByteList(DEFAULT_SIZE);
30
+
31
+ IOBuffer(Ruby runtime, RubyClass klass) {
32
+ super(runtime, klass);
33
+ }
34
+
35
+ @JRubyMethod
36
+ public RubyInteger used(ThreadContext context) {
37
+ return context.runtime.newFixnum(buffer.getRealSize());
38
+ }
39
+
40
+ @JRubyMethod
41
+ public RubyInteger capacity(ThreadContext context) {
42
+ return context.runtime.newFixnum(buffer.unsafeBytes().length);
43
+ }
44
+
45
+ @JRubyMethod
46
+ public IRubyObject reset() {
47
+ buffer.setRealSize(0);
48
+ return this;
49
+ }
50
+
51
+ @JRubyMethod(name = { "to_s", "to_str" })
52
+ public RubyString to_s(ThreadContext context) {
53
+ return RubyString.newStringShared(context.runtime, buffer.unsafeBytes(), 0, buffer.getRealSize());
54
+ }
55
+
56
+ @JRubyMethod(name = "<<")
57
+ public IRubyObject add(IRubyObject str) {
58
+ addImpl(str.convertToString());
59
+ return this;
60
+ }
61
+
62
+ @JRubyMethod(rest = true)
63
+ public IRubyObject append(IRubyObject[] strs) {
64
+ for (IRubyObject str : strs) addImpl(str.convertToString());
65
+ return this;
66
+ }
67
+
68
+ private void addImpl(RubyString str) {
69
+ buffer.append(str.getByteList());
70
+ }
71
+
72
+ }
@@ -0,0 +1,363 @@
1
+ package org.jruby.puma;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.RubyClass;
5
+ import org.jruby.RubyModule;
6
+ import org.jruby.RubyObject;
7
+ import org.jruby.RubyString;
8
+ import org.jruby.anno.JRubyMethod;
9
+ import org.jruby.javasupport.JavaEmbedUtils;
10
+ import org.jruby.runtime.Block;
11
+ import org.jruby.runtime.ObjectAllocator;
12
+ import org.jruby.runtime.ThreadContext;
13
+ import org.jruby.runtime.builtin.IRubyObject;
14
+ import org.jruby.util.ByteList;
15
+
16
+ import javax.net.ssl.KeyManagerFactory;
17
+ import javax.net.ssl.TrustManagerFactory;
18
+ import javax.net.ssl.SSLContext;
19
+ import javax.net.ssl.SSLEngine;
20
+ import javax.net.ssl.SSLEngineResult;
21
+ import javax.net.ssl.SSLException;
22
+ import javax.net.ssl.SSLPeerUnverifiedException;
23
+ import javax.net.ssl.SSLSession;
24
+ import java.io.FileInputStream;
25
+ import java.io.IOException;
26
+ import java.nio.Buffer;
27
+ import java.nio.ByteBuffer;
28
+ import java.security.KeyManagementException;
29
+ import java.security.KeyStore;
30
+ import java.security.KeyStoreException;
31
+ import java.security.NoSuchAlgorithmException;
32
+ import java.security.UnrecoverableKeyException;
33
+ import java.security.cert.CertificateEncodingException;
34
+ import java.security.cert.CertificateException;
35
+
36
+ import static javax.net.ssl.SSLEngineResult.Status;
37
+ import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
38
+
39
+ public class MiniSSL extends RubyObject {
40
+ private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
41
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
42
+ return new MiniSSL(runtime, klass);
43
+ }
44
+ };
45
+
46
+ public static void createMiniSSL(Ruby runtime) {
47
+ RubyModule mPuma = runtime.defineModule("Puma");
48
+ RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
49
+
50
+ mPuma.defineClassUnder("SSLError",
51
+ runtime.getClass("IOError"),
52
+ runtime.getClass("IOError").getAllocator());
53
+
54
+ RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
55
+ eng.defineAnnotatedMethods(MiniSSL.class);
56
+ }
57
+
58
+ /**
59
+ * Fairly transparent wrapper around {@link java.nio.ByteBuffer} which adds the enhancements we need
60
+ */
61
+ private static class MiniSSLBuffer {
62
+ ByteBuffer buffer;
63
+
64
+ private MiniSSLBuffer(int capacity) { buffer = ByteBuffer.allocate(capacity); }
65
+ private MiniSSLBuffer(byte[] initialContents) { buffer = ByteBuffer.wrap(initialContents); }
66
+
67
+ public void clear() { buffer.clear(); }
68
+ public void compact() { buffer.compact(); }
69
+ public void flip() { ((Buffer) buffer).flip(); }
70
+ public boolean hasRemaining() { return buffer.hasRemaining(); }
71
+ public int position() { return buffer.position(); }
72
+
73
+ public ByteBuffer getRawBuffer() {
74
+ return buffer;
75
+ }
76
+
77
+ /**
78
+ * Writes bytes to the buffer after ensuring there's room
79
+ */
80
+ public void put(byte[] bytes) {
81
+ if (buffer.remaining() < bytes.length) {
82
+ resize(buffer.limit() + bytes.length);
83
+ }
84
+ buffer.put(bytes);
85
+ }
86
+
87
+ /**
88
+ * Ensures that newCapacity bytes can be written to this buffer, only re-allocating if necessary
89
+ */
90
+ public void resize(int newCapacity) {
91
+ if (newCapacity > buffer.capacity()) {
92
+ ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
93
+ flip();
94
+ dstTmp.put(buffer);
95
+ buffer = dstTmp;
96
+ } else {
97
+ buffer.limit(newCapacity);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Drains the buffer to a ByteList, or returns null for an empty buffer
103
+ */
104
+ public ByteList asByteList() {
105
+ flip();
106
+ if (!buffer.hasRemaining()) {
107
+ buffer.clear();
108
+ return null;
109
+ }
110
+
111
+ byte[] bss = new byte[buffer.limit()];
112
+
113
+ buffer.get(bss);
114
+ buffer.clear();
115
+ return new ByteList(bss);
116
+ }
117
+
118
+ @Override
119
+ public String toString() { return buffer.toString(); }
120
+ }
121
+
122
+ private SSLEngine engine;
123
+ private MiniSSLBuffer inboundNetData;
124
+ private MiniSSLBuffer outboundAppData;
125
+ private MiniSSLBuffer outboundNetData;
126
+
127
+ public MiniSSL(Ruby runtime, RubyClass klass) {
128
+ super(runtime, klass);
129
+ }
130
+
131
+ @JRubyMethod(meta = true)
132
+ public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) {
133
+ RubyClass klass = (RubyClass) recv;
134
+
135
+ return klass.newInstance(context,
136
+ new IRubyObject[] { miniSSLContext },
137
+ Block.NULL_BLOCK);
138
+ }
139
+
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());
145
+
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);
150
+
151
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
152
+ kmf.init(ks, password);
153
+
154
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
155
+ tmf.init(ts);
156
+
157
+ SSLContext sslCtx = SSLContext.getInstance("TLS");
158
+
159
+ sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
160
+ engine = sslCtx.createSSLEngine();
161
+
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
+
173
+ engine.setEnabledProtocols(protocols);
174
+ engine.setUseClientMode(false);
175
+
176
+ long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
177
+ if ((verify_mode & 0x1) != 0) { // 'peer'
178
+ engine.setWantClientAuth(true);
179
+ }
180
+ if ((verify_mode & 0x2) != 0) { // 'force_peer'
181
+ engine.setNeedClientAuth(true);
182
+ }
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
+
190
+ SSLSession session = engine.getSession();
191
+ inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
192
+ outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
193
+ outboundAppData.flip();
194
+ outboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
195
+
196
+ return this;
197
+ }
198
+
199
+ @JRubyMethod
200
+ 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
+ }
209
+ }
210
+
211
+ private enum SSLOperation {
212
+ WRAP,
213
+ UNWRAP
214
+ }
215
+
216
+ private SSLEngineResult doOp(SSLOperation sslOp, MiniSSLBuffer src, MiniSSLBuffer dst) throws SSLException {
217
+ SSLEngineResult res = null;
218
+ boolean retryOp = true;
219
+ while (retryOp) {
220
+ switch (sslOp) {
221
+ case WRAP:
222
+ res = engine.wrap(src.getRawBuffer(), dst.getRawBuffer());
223
+ break;
224
+ case UNWRAP:
225
+ res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
226
+ break;
227
+ default:
228
+ throw new IllegalStateException("Unknown SSLOperation: " + sslOp);
229
+ }
230
+
231
+ switch (res.getStatus()) {
232
+ case BUFFER_OVERFLOW:
233
+ // increase the buffer size to accommodate the overflowing data
234
+ int newSize = Math.max(engine.getSession().getPacketBufferSize(), engine.getSession().getApplicationBufferSize());
235
+ dst.resize(newSize + dst.position());
236
+ // retry the operation
237
+ retryOp = true;
238
+ break;
239
+ case BUFFER_UNDERFLOW:
240
+ // need to wait for more data to come in before we retry
241
+ retryOp = false;
242
+ break;
243
+ default:
244
+ // other cases are OK and CLOSED. We're done here.
245
+ retryOp = false;
246
+ }
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();
254
+ }
255
+ }
256
+
257
+ return res;
258
+ }
259
+
260
+ @JRubyMethod
261
+ public IRubyObject read() throws Exception {
262
+ try {
263
+ inboundNetData.flip();
264
+
265
+ if(!inboundNetData.hasRemaining()) {
266
+ return getRuntime().getNil();
267
+ }
268
+
269
+ MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize());
270
+ doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
271
+
272
+ HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
273
+ boolean done = false;
274
+ while (!done) {
275
+ switch (handshakeStatus) {
276
+ case NEED_WRAP:
277
+ doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
278
+ break;
279
+ case NEED_UNWRAP:
280
+ SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
281
+ if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
282
+ // need more data before we can shake more hands
283
+ done = true;
284
+ }
285
+ break;
286
+ default:
287
+ done = true;
288
+ }
289
+ handshakeStatus = engine.getHandshakeStatus();
290
+ }
291
+
292
+ if (inboundNetData.hasRemaining()) {
293
+ inboundNetData.compact();
294
+ } else {
295
+ inboundNetData.clear();
296
+ }
297
+
298
+ ByteList appDataByteList = inboundAppData.asByteList();
299
+ if (appDataByteList == null) {
300
+ return getRuntime().getNil();
301
+ }
302
+
303
+ RubyString str = getRuntime().newString("");
304
+ str.setValue(appDataByteList);
305
+ return str;
306
+ } catch (Exception e) {
307
+ throw getRuntime().newEOFError(e.getMessage());
308
+ }
309
+ }
310
+
311
+ @JRubyMethod
312
+ public IRubyObject write(IRubyObject arg) {
313
+ try {
314
+ byte[] bls = arg.convertToString().getBytes();
315
+ outboundAppData = new MiniSSLBuffer(bls);
316
+
317
+ return getRuntime().newFixnum(bls.length);
318
+ } catch (Exception e) {
319
+ e.printStackTrace();
320
+ throw new RuntimeException(e);
321
+ }
322
+ }
323
+
324
+ @JRubyMethod
325
+ public IRubyObject extract() throws SSLException {
326
+ try {
327
+ ByteList dataByteList = outboundNetData.asByteList();
328
+ if (dataByteList != null) {
329
+ RubyString str = getRuntime().newString("");
330
+ str.setValue(dataByteList);
331
+ return str;
332
+ }
333
+
334
+ if (!outboundAppData.hasRemaining()) {
335
+ return getRuntime().getNil();
336
+ }
337
+
338
+ outboundNetData.clear();
339
+ doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
340
+ dataByteList = outboundNetData.asByteList();
341
+ if (dataByteList == null) {
342
+ return getRuntime().getNil();
343
+ }
344
+
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);
352
+ }
353
+ }
354
+
355
+ @JRubyMethod
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
+ }
362
+ }
363
+ }
@@ -0,0 +1,502 @@
1
+ /**
2
+ * Copyright (c) 2005 Zed A. Shaw
3
+ * You can redistribute it and/or modify it under the same terms as Ruby.
4
+ * License 3-clause BSD
5
+ */
6
+
7
+ #define RSTRING_NOT_MODIFIED 1
8
+
9
+ #include "ruby.h"
10
+ #include "ext_help.h"
11
+ #include <assert.h>
12
+ #include <string.h>
13
+ #include "http11_parser.h"
14
+
15
+ #ifndef MANAGED_STRINGS
16
+
17
+ #ifndef RSTRING_PTR
18
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
19
+ #endif
20
+ #ifndef RSTRING_LEN
21
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
22
+ #endif
23
+
24
+ #define rb_extract_chars(e, sz) (*sz = RSTRING_LEN(e), RSTRING_PTR(e))
25
+ #define rb_free_chars(e) /* nothing */
26
+
27
+ #endif
28
+
29
+ static VALUE eHttpParserError;
30
+
31
+ #define HTTP_PREFIX "HTTP_"
32
+ #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
33
+
34
+ static VALUE global_request_method;
35
+ static VALUE global_request_uri;
36
+ static VALUE global_fragment;
37
+ static VALUE global_query_string;
38
+ static VALUE global_http_version;
39
+ static VALUE global_request_path;
40
+
41
+ /** Defines common length and error messages for input length validation. */
42
+ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length (was %d)"
43
+
44
+ /** Validates the max length of given input and throws an HttpParserError exception if over. */
45
+ #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
46
+
47
+ /** Defines global strings in the init method. */
48
+ #define DEF_GLOBAL(N, val) global_##N = rb_str_new2(val); rb_global_variable(&global_##N)
49
+
50
+
51
+ /* Defines the maximum allowed lengths for various input elements.*/
52
+ DEF_MAX_LENGTH(FIELD_NAME, 256);
53
+ DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
54
+ DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
55
+ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
56
+ DEF_MAX_LENGTH(REQUEST_PATH, 2048);
57
+ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
58
+ DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
59
+
60
+ struct common_field {
61
+ const size_t len;
62
+ const char *name;
63
+ int raw;
64
+ VALUE value;
65
+ };
66
+
67
+ /*
68
+ * A list of common HTTP headers we expect to receive.
69
+ * This allows us to avoid repeatedly creating identical string
70
+ * objects to be used with rb_hash_aset().
71
+ */
72
+ static struct common_field common_http_fields[] = {
73
+ # define f(N) { (sizeof(N) - 1), N, 0, Qnil }
74
+ # define fr(N) { (sizeof(N) - 1), N, 1, Qnil }
75
+ f("ACCEPT"),
76
+ f("ACCEPT_CHARSET"),
77
+ f("ACCEPT_ENCODING"),
78
+ f("ACCEPT_LANGUAGE"),
79
+ f("ALLOW"),
80
+ f("AUTHORIZATION"),
81
+ f("CACHE_CONTROL"),
82
+ f("CONNECTION"),
83
+ f("CONTENT_ENCODING"),
84
+ fr("CONTENT_LENGTH"),
85
+ fr("CONTENT_TYPE"),
86
+ f("COOKIE"),
87
+ f("DATE"),
88
+ f("EXPECT"),
89
+ f("FROM"),
90
+ f("HOST"),
91
+ f("IF_MATCH"),
92
+ f("IF_MODIFIED_SINCE"),
93
+ f("IF_NONE_MATCH"),
94
+ f("IF_RANGE"),
95
+ f("IF_UNMODIFIED_SINCE"),
96
+ f("KEEP_ALIVE"), /* Firefox sends this */
97
+ f("MAX_FORWARDS"),
98
+ f("PRAGMA"),
99
+ f("PROXY_AUTHORIZATION"),
100
+ f("RANGE"),
101
+ f("REFERER"),
102
+ f("TE"),
103
+ f("TRAILER"),
104
+ f("TRANSFER_ENCODING"),
105
+ f("UPGRADE"),
106
+ f("USER_AGENT"),
107
+ f("VIA"),
108
+ f("X_FORWARDED_FOR"), /* common for proxies */
109
+ f("X_REAL_IP"), /* common for proxies */
110
+ f("WARNING")
111
+ # undef f
112
+ };
113
+
114
+ /*
115
+ * qsort(3) and bsearch(3) improve average performance slightly, but may
116
+ * not be worth it for lack of portability to certain platforms...
117
+ */
118
+ #if defined(HAVE_QSORT_BSEARCH)
119
+ /* sort by length, then by name if there's a tie */
120
+ static int common_field_cmp(const void *a, const void *b)
121
+ {
122
+ struct common_field *cfa = (struct common_field *)a;
123
+ struct common_field *cfb = (struct common_field *)b;
124
+ signed long diff = cfa->len - cfb->len;
125
+ return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
126
+ }
127
+ #endif /* HAVE_QSORT_BSEARCH */
128
+
129
+ static void init_common_fields(void)
130
+ {
131
+ unsigned i;
132
+ struct common_field *cf = common_http_fields;
133
+ char tmp[256]; /* MAX_FIELD_NAME_LENGTH */
134
+ memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
135
+
136
+ for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
137
+ if(cf->raw) {
138
+ cf->value = rb_str_new(cf->name, cf->len);
139
+ } else {
140
+ memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
141
+ cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
142
+ }
143
+ rb_global_variable(&cf->value);
144
+ }
145
+
146
+ #if defined(HAVE_QSORT_BSEARCH)
147
+ qsort(common_http_fields,
148
+ ARRAY_SIZE(common_http_fields),
149
+ sizeof(struct common_field),
150
+ common_field_cmp);
151
+ #endif /* HAVE_QSORT_BSEARCH */
152
+ }
153
+
154
+ static VALUE find_common_field_value(const char *field, size_t flen)
155
+ {
156
+ #if defined(HAVE_QSORT_BSEARCH)
157
+ struct common_field key;
158
+ struct common_field *found;
159
+ key.name = field;
160
+ key.len = (signed long)flen;
161
+ found = (struct common_field *)bsearch(&key, common_http_fields,
162
+ ARRAY_SIZE(common_http_fields),
163
+ sizeof(struct common_field),
164
+ common_field_cmp);
165
+ return found ? found->value : Qnil;
166
+ #else /* !HAVE_QSORT_BSEARCH */
167
+ unsigned i;
168
+ struct common_field *cf = common_http_fields;
169
+ for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
170
+ if (cf->len == flen && !memcmp(cf->name, field, flen))
171
+ return cf->value;
172
+ }
173
+ return Qnil;
174
+ #endif /* !HAVE_QSORT_BSEARCH */
175
+ }
176
+
177
+ void http_field(puma_parser* hp, const char *field, size_t flen,
178
+ const char *value, size_t vlen)
179
+ {
180
+ VALUE f = Qnil;
181
+ VALUE v;
182
+
183
+ VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
184
+ VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
185
+
186
+ f = find_common_field_value(field, flen);
187
+
188
+ if (f == Qnil) {
189
+ /*
190
+ * We got a strange header that we don't have a memoized value for.
191
+ * Fallback to creating a new string to use as a hash key.
192
+ */
193
+
194
+ size_t new_size = HTTP_PREFIX_LEN + flen;
195
+ assert(new_size < BUFFER_LEN);
196
+
197
+ memcpy(hp->buf, HTTP_PREFIX, HTTP_PREFIX_LEN);
198
+ memcpy(hp->buf + HTTP_PREFIX_LEN, field, flen);
199
+
200
+ f = rb_str_new(hp->buf, new_size);
201
+ }
202
+
203
+ while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
204
+
205
+ /* check for duplicate header */
206
+ v = rb_hash_aref(hp->request, f);
207
+
208
+ if (v == Qnil) {
209
+ v = rb_str_new(value, vlen);
210
+ rb_hash_aset(hp->request, f, v);
211
+ } else {
212
+ /* if duplicate header, normalize to comma-separated values */
213
+ rb_str_cat2(v, ", ");
214
+ rb_str_cat(v, value, vlen);
215
+ }
216
+ }
217
+
218
+ void request_method(puma_parser* hp, const char *at, size_t length)
219
+ {
220
+ VALUE val = Qnil;
221
+
222
+ val = rb_str_new(at, length);
223
+ rb_hash_aset(hp->request, global_request_method, val);
224
+ }
225
+
226
+ void request_uri(puma_parser* hp, const char *at, size_t length)
227
+ {
228
+ VALUE val = Qnil;
229
+
230
+ VALIDATE_MAX_LENGTH(length, REQUEST_URI);
231
+
232
+ val = rb_str_new(at, length);
233
+ rb_hash_aset(hp->request, global_request_uri, val);
234
+ }
235
+
236
+ void fragment(puma_parser* hp, const char *at, size_t length)
237
+ {
238
+ VALUE val = Qnil;
239
+
240
+ VALIDATE_MAX_LENGTH(length, FRAGMENT);
241
+
242
+ val = rb_str_new(at, length);
243
+ rb_hash_aset(hp->request, global_fragment, val);
244
+ }
245
+
246
+ void request_path(puma_parser* hp, const char *at, size_t length)
247
+ {
248
+ VALUE val = Qnil;
249
+
250
+ VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
251
+
252
+ val = rb_str_new(at, length);
253
+ rb_hash_aset(hp->request, global_request_path, val);
254
+ }
255
+
256
+ void query_string(puma_parser* hp, const char *at, size_t length)
257
+ {
258
+ VALUE val = Qnil;
259
+
260
+ VALIDATE_MAX_LENGTH(length, QUERY_STRING);
261
+
262
+ val = rb_str_new(at, length);
263
+ rb_hash_aset(hp->request, global_query_string, val);
264
+ }
265
+
266
+ void http_version(puma_parser* hp, const char *at, size_t length)
267
+ {
268
+ VALUE val = rb_str_new(at, length);
269
+ rb_hash_aset(hp->request, global_http_version, val);
270
+ }
271
+
272
+ /** Finalizes the request header to have a bunch of stuff that's
273
+ needed. */
274
+
275
+ void header_done(puma_parser* hp, const char *at, size_t length)
276
+ {
277
+ hp->body = rb_str_new(at, length);
278
+ }
279
+
280
+
281
+ void HttpParser_free(void *data) {
282
+ TRACE();
283
+
284
+ if(data) {
285
+ xfree(data);
286
+ }
287
+ }
288
+
289
+ void HttpParser_mark(puma_parser* hp) {
290
+ if(hp->request) rb_gc_mark(hp->request);
291
+ if(hp->body) rb_gc_mark(hp->body);
292
+ }
293
+
294
+ VALUE HttpParser_alloc(VALUE klass)
295
+ {
296
+ puma_parser *hp = ALLOC_N(puma_parser, 1);
297
+ TRACE();
298
+ hp->http_field = http_field;
299
+ hp->request_method = request_method;
300
+ hp->request_uri = request_uri;
301
+ hp->fragment = fragment;
302
+ hp->request_path = request_path;
303
+ hp->query_string = query_string;
304
+ hp->http_version = http_version;
305
+ hp->header_done = header_done;
306
+ hp->request = Qnil;
307
+
308
+ puma_parser_init(hp);
309
+
310
+ return Data_Wrap_Struct(klass, HttpParser_mark, HttpParser_free, hp);
311
+ }
312
+
313
+ /**
314
+ * call-seq:
315
+ * parser.new -> parser
316
+ *
317
+ * Creates a new parser.
318
+ */
319
+ VALUE HttpParser_init(VALUE self)
320
+ {
321
+ puma_parser *http = NULL;
322
+ DATA_GET(self, puma_parser, http);
323
+ puma_parser_init(http);
324
+
325
+ return self;
326
+ }
327
+
328
+
329
+ /**
330
+ * call-seq:
331
+ * parser.reset -> nil
332
+ *
333
+ * Resets the parser to it's initial state so that you can reuse it
334
+ * rather than making new ones.
335
+ */
336
+ VALUE HttpParser_reset(VALUE self)
337
+ {
338
+ puma_parser *http = NULL;
339
+ DATA_GET(self, puma_parser, http);
340
+ puma_parser_init(http);
341
+
342
+ return Qnil;
343
+ }
344
+
345
+
346
+ /**
347
+ * call-seq:
348
+ * parser.finish -> true/false
349
+ *
350
+ * Finishes a parser early which could put in a "good" or bad state.
351
+ * You should call reset after finish it or bad things will happen.
352
+ */
353
+ VALUE HttpParser_finish(VALUE self)
354
+ {
355
+ puma_parser *http = NULL;
356
+ DATA_GET(self, puma_parser, http);
357
+ puma_parser_finish(http);
358
+
359
+ return puma_parser_is_finished(http) ? Qtrue : Qfalse;
360
+ }
361
+
362
+
363
+ /**
364
+ * call-seq:
365
+ * parser.execute(req_hash, data, start) -> Integer
366
+ *
367
+ * Takes a Hash and a String of data, parses the String of data filling in the Hash
368
+ * returning an Integer to indicate how much of the data has been read. No matter
369
+ * what the return value, you should call HttpParser#finished? and HttpParser#error?
370
+ * to figure out if it's done parsing or there was an error.
371
+ *
372
+ * This function now throws an exception when there is a parsing error. This makes
373
+ * the logic for working with the parser much easier. You can still test for an
374
+ * error, but now you need to wrap the parser with an exception handling block.
375
+ *
376
+ * The third argument allows for parsing a partial request and then continuing
377
+ * the parsing from that position. It needs all of the original data as well
378
+ * so you have to append to the data buffer as you read.
379
+ */
380
+ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
381
+ {
382
+ puma_parser *http = NULL;
383
+ int from = 0;
384
+ char *dptr = NULL;
385
+ long dlen = 0;
386
+
387
+ DATA_GET(self, puma_parser, http);
388
+
389
+ from = FIX2INT(start);
390
+ dptr = rb_extract_chars(data, &dlen);
391
+
392
+ if(from >= dlen) {
393
+ rb_free_chars(dptr);
394
+ rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
395
+ } else {
396
+ http->request = req_hash;
397
+ puma_parser_execute(http, dptr, dlen, from);
398
+
399
+ rb_free_chars(dptr);
400
+ VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
401
+
402
+ if(puma_parser_has_error(http)) {
403
+ rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
404
+ } else {
405
+ return INT2FIX(puma_parser_nread(http));
406
+ }
407
+ }
408
+ }
409
+
410
+
411
+
412
+ /**
413
+ * call-seq:
414
+ * parser.error? -> true/false
415
+ *
416
+ * Tells you whether the parser is in an error state.
417
+ */
418
+ VALUE HttpParser_has_error(VALUE self)
419
+ {
420
+ puma_parser *http = NULL;
421
+ DATA_GET(self, puma_parser, http);
422
+
423
+ return puma_parser_has_error(http) ? Qtrue : Qfalse;
424
+ }
425
+
426
+
427
+ /**
428
+ * call-seq:
429
+ * parser.finished? -> true/false
430
+ *
431
+ * Tells you whether the parser is finished or not and in a good state.
432
+ */
433
+ VALUE HttpParser_is_finished(VALUE self)
434
+ {
435
+ puma_parser *http = NULL;
436
+ DATA_GET(self, puma_parser, http);
437
+
438
+ return puma_parser_is_finished(http) ? Qtrue : Qfalse;
439
+ }
440
+
441
+
442
+ /**
443
+ * call-seq:
444
+ * parser.nread -> Integer
445
+ *
446
+ * Returns the amount of data processed so far during this processing cycle. It is
447
+ * set to 0 on initialize or reset calls and is incremented each time execute is called.
448
+ */
449
+ VALUE HttpParser_nread(VALUE self)
450
+ {
451
+ puma_parser *http = NULL;
452
+ DATA_GET(self, puma_parser, http);
453
+
454
+ return INT2FIX(http->nread);
455
+ }
456
+
457
+ /**
458
+ * call-seq:
459
+ * parser.body -> nil or String
460
+ *
461
+ * If the request included a body, returns it.
462
+ */
463
+ VALUE HttpParser_body(VALUE self) {
464
+ puma_parser *http = NULL;
465
+ DATA_GET(self, puma_parser, http);
466
+
467
+ return http->body;
468
+ }
469
+
470
+ void Init_io_buffer(VALUE puma);
471
+ void Init_mini_ssl(VALUE mod);
472
+
473
+ void Init_puma_http11()
474
+ {
475
+
476
+ VALUE mPuma = rb_define_module("Puma");
477
+ VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
478
+
479
+ DEF_GLOBAL(request_method, "REQUEST_METHOD");
480
+ DEF_GLOBAL(request_uri, "REQUEST_URI");
481
+ DEF_GLOBAL(fragment, "FRAGMENT");
482
+ DEF_GLOBAL(query_string, "QUERY_STRING");
483
+ DEF_GLOBAL(http_version, "HTTP_VERSION");
484
+ DEF_GLOBAL(request_path, "REQUEST_PATH");
485
+
486
+ eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
487
+ rb_global_variable(&eHttpParserError);
488
+
489
+ rb_define_alloc_func(cHttpParser, HttpParser_alloc);
490
+ rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
491
+ rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
492
+ rb_define_method(cHttpParser, "finish", HttpParser_finish, 0);
493
+ rb_define_method(cHttpParser, "execute", HttpParser_execute, 3);
494
+ rb_define_method(cHttpParser, "error?", HttpParser_has_error, 0);
495
+ rb_define_method(cHttpParser, "finished?", HttpParser_is_finished, 0);
496
+ rb_define_method(cHttpParser, "nread", HttpParser_nread, 0);
497
+ rb_define_method(cHttpParser, "body", HttpParser_body, 0);
498
+ init_common_fields();
499
+
500
+ Init_io_buffer(mPuma);
501
+ Init_mini_ssl(mPuma);
502
+ }