puma 6.6.0 → 8.0.2
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.
- checksums.yaml +4 -4
- data/History.md +309 -5
- data/README.md +41 -42
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/deployment.md +58 -23
- data/docs/fork_worker.md +5 -5
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +11 -16
- data/docs/plugins.md +2 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +21 -21
- data/docs/stats.md +4 -3
- data/docs/systemd.md +4 -4
- data/ext/puma_http11/extconf.rb +2 -17
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/mini_ssl.c +18 -8
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +174 -102
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/ext/puma_http11/puma_http11.c +122 -118
- data/lib/puma/app/status.rb +10 -2
- data/lib/puma/binder.rb +12 -10
- data/lib/puma/cli.rb +4 -6
- data/lib/puma/client.rb +205 -131
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster/worker.rb +17 -17
- data/lib/puma/cluster/worker_handle.rb +38 -7
- data/lib/puma/cluster.rb +44 -30
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +173 -58
- data/lib/puma/const.rb +11 -11
- data/lib/puma/control_cli.rb +7 -3
- data/lib/puma/detect.rb +13 -0
- data/lib/puma/dsl.rb +225 -108
- data/lib/puma/error_logger.rb +3 -1
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/launcher/bundle_pruner.rb +3 -5
- data/lib/puma/launcher.rb +57 -53
- data/lib/puma/log_writer.rb +8 -2
- data/lib/puma/minissl.rb +0 -1
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -13
- data/lib/puma/{request.rb → response.rb} +56 -212
- data/lib/puma/runner.rb +9 -18
- data/lib/puma/server.rb +182 -97
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/single.rb +7 -4
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +170 -99
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +3 -3
- data/tools/Dockerfile +15 -5
- metadata +19 -7
- data/ext/puma_http11/ext_help.h +0 -15
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
package org.jruby.puma;
|
|
2
|
+
|
|
3
|
+
import org.jruby.util.ByteList;
|
|
4
|
+
|
|
5
|
+
import java.nio.charset.StandardCharsets;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A set of keys representing HTTP/1.1 header fields and CGI env keys.
|
|
9
|
+
*
|
|
10
|
+
* The set of header fields is derived from RFC 2616 with some additions
|
|
11
|
+
* common to mainstream browsers.
|
|
12
|
+
*
|
|
13
|
+
* This enum is used by {@link Http11 }to access pre-allocated strings for
|
|
14
|
+
* Puma's version of these keys and includes a perfect hash function to quickly
|
|
15
|
+
* find the key for a given range of bytes.
|
|
16
|
+
*/
|
|
17
|
+
public enum EnvKey {
|
|
18
|
+
ACCEPT,
|
|
19
|
+
ACCEPT_CHARSET,
|
|
20
|
+
ACCEPT_ENCODING,
|
|
21
|
+
ACCEPT_LANGUAGE,
|
|
22
|
+
ACCEPT_RANGES,
|
|
23
|
+
AGE,
|
|
24
|
+
ALLOW,
|
|
25
|
+
AUTHORIZATION,
|
|
26
|
+
CACHE_CONTROL,
|
|
27
|
+
CONNECTION,
|
|
28
|
+
CONTENT_ENCODING,
|
|
29
|
+
CONTENT_LANGUAGE,
|
|
30
|
+
CONTENT_LENGTH(true),
|
|
31
|
+
CONTENT_LOCATION,
|
|
32
|
+
CONTENT_MD5,
|
|
33
|
+
CONTENT_RANGE,
|
|
34
|
+
CONTENT_TYPE(true),
|
|
35
|
+
DATE,
|
|
36
|
+
ETAG,
|
|
37
|
+
EXPECT,
|
|
38
|
+
EXPIRES,
|
|
39
|
+
FRAGMENT(true),
|
|
40
|
+
FROM,
|
|
41
|
+
HOST,
|
|
42
|
+
IF_MATCH,
|
|
43
|
+
IF_MODIFIED_SINCE,
|
|
44
|
+
IF_NONE_MATCH,
|
|
45
|
+
IF_RANGE,
|
|
46
|
+
IF_UNMODIFIED_SINCE,
|
|
47
|
+
KEEP_ALIVE,
|
|
48
|
+
LAST_MODIFIED,
|
|
49
|
+
LOCATION,
|
|
50
|
+
MAX_FORWARDS,
|
|
51
|
+
PRAGMA,
|
|
52
|
+
PROXY_AUTHENTICATE,
|
|
53
|
+
PROXY_AUTHORIZATION,
|
|
54
|
+
QUERY_STRING(true),
|
|
55
|
+
RANGE,
|
|
56
|
+
REFERER,
|
|
57
|
+
REQUEST_METHOD(true),
|
|
58
|
+
REQUEST_PATH(true),
|
|
59
|
+
REQUEST_URI(true),
|
|
60
|
+
RETRY_AFTER,
|
|
61
|
+
SERVER,
|
|
62
|
+
SERVER_PROTOCOL(true),
|
|
63
|
+
TE,
|
|
64
|
+
TRAILER,
|
|
65
|
+
TRANSFER_ENCODING,
|
|
66
|
+
UPGRADE,
|
|
67
|
+
USER_AGENT,
|
|
68
|
+
VARY,
|
|
69
|
+
VIA,
|
|
70
|
+
X_FORWARDED_FOR,
|
|
71
|
+
X_REAL_IP,
|
|
72
|
+
WARNING,
|
|
73
|
+
WWW_AUTHENTICATE;
|
|
74
|
+
|
|
75
|
+
final ByteList httpName;
|
|
76
|
+
|
|
77
|
+
EnvKey() {
|
|
78
|
+
this(false);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
EnvKey(boolean raw) {
|
|
82
|
+
String httpName = raw ? name() : "HTTP_" + name();
|
|
83
|
+
this.httpName = new ByteList(ByteList.plain(httpName), false);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Using a perfect hash, find the EnvKey for a given field name, or null if none matches.
|
|
88
|
+
*
|
|
89
|
+
* Generated by the gperf tool as C and adapted to Java. Must be regenerated if the set of keys changes.
|
|
90
|
+
*
|
|
91
|
+
* @param buffer the buffer containing the field name
|
|
92
|
+
* @param start the starting offset of the field name
|
|
93
|
+
* @param len the length of the field name
|
|
94
|
+
* @return the matching EnvKey, or else null
|
|
95
|
+
*/
|
|
96
|
+
public static EnvKey keyForField(byte[] buffer, int start, int len) {
|
|
97
|
+
int key = hash(buffer, start, len);
|
|
98
|
+
|
|
99
|
+
if (key <= MAX_HASH_VALUE) {
|
|
100
|
+
EnvKey match = keyList[key];
|
|
101
|
+
|
|
102
|
+
if (match != null && equalsIgnoreCase(buffer, start, len, match.name())) {
|
|
103
|
+
return match;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static boolean equalsIgnoreCase(byte[] buf, int start, int len, String key) {
|
|
110
|
+
int slen = key.length();
|
|
111
|
+
if (len != slen) return false;
|
|
112
|
+
for (int i = 0; i < slen; i++) {
|
|
113
|
+
byte b1 = buf[start + i];
|
|
114
|
+
byte b2 = (byte) key.charAt(i);
|
|
115
|
+
if (Http11.snakeUpcase(b1) != b2) return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private static int hash(byte[] buffer, int start, int len) {
|
|
121
|
+
return len
|
|
122
|
+
+ asso_values[Byte.toUnsignedInt(Http11.snakeUpcase(buffer[start + len - 1]))]
|
|
123
|
+
+ asso_values[Byte.toUnsignedInt(Http11.snakeUpcase(buffer[start]))];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private static final int MAX_HASH_VALUE = 94;
|
|
127
|
+
|
|
128
|
+
private final static int[] asso_values = {
|
|
129
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
130
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
131
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
132
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
133
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
134
|
+
95, 95, 95, 10, 95, 95, 95, 95, 95, 95,
|
|
135
|
+
95, 95, 95, 95, 95, 5, 95, 15, 40, 0,
|
|
136
|
+
0, 10, 10, 15, 95, 0, 20, 5, 25, 95,
|
|
137
|
+
50, 40, 0, 40, 10, 35, 5, 35, 0, 10,
|
|
138
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
139
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
140
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
141
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
142
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
143
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
144
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
145
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
146
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
147
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
148
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
149
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
150
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
151
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
152
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
153
|
+
95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
|
|
154
|
+
95, 95, 95, 95, 95, 95
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
private static final EnvKey[] keyList = {
|
|
158
|
+
null, null, null, null, null,
|
|
159
|
+
EnvKey.RANGE,
|
|
160
|
+
null,
|
|
161
|
+
EnvKey.REFERER,
|
|
162
|
+
EnvKey.AGE,
|
|
163
|
+
EnvKey.FROM,
|
|
164
|
+
EnvKey.KEEP_ALIVE,
|
|
165
|
+
EnvKey.RETRY_AFTER,
|
|
166
|
+
EnvKey.TE,
|
|
167
|
+
EnvKey.VIA,
|
|
168
|
+
EnvKey.ETAG,
|
|
169
|
+
EnvKey.X_FORWARDED_FOR,
|
|
170
|
+
EnvKey.EXPECT,
|
|
171
|
+
EnvKey.TRAILER,
|
|
172
|
+
EnvKey.FRAGMENT,
|
|
173
|
+
EnvKey.VARY,
|
|
174
|
+
EnvKey.ACCEPT_LANGUAGE,
|
|
175
|
+
EnvKey.ACCEPT,
|
|
176
|
+
EnvKey.REQUEST_PATH,
|
|
177
|
+
EnvKey.IF_RANGE,
|
|
178
|
+
EnvKey.HOST,
|
|
179
|
+
null,
|
|
180
|
+
EnvKey.REQUEST_URI,
|
|
181
|
+
EnvKey.CONTENT_TYPE,
|
|
182
|
+
EnvKey.CONTENT_RANGE,
|
|
183
|
+
EnvKey.ACCEPT_CHARSET,
|
|
184
|
+
EnvKey.ACCEPT_ENCODING,
|
|
185
|
+
EnvKey.CONTENT_LANGUAGE,
|
|
186
|
+
EnvKey.IF_MODIFIED_SINCE,
|
|
187
|
+
EnvKey.IF_MATCH,
|
|
188
|
+
EnvKey.IF_UNMODIFIED_SINCE,
|
|
189
|
+
null,
|
|
190
|
+
EnvKey.CONTENT_MD5,
|
|
191
|
+
EnvKey.TRANSFER_ENCODING,
|
|
192
|
+
EnvKey.IF_NONE_MATCH,
|
|
193
|
+
EnvKey.CONTENT_LENGTH,
|
|
194
|
+
null,
|
|
195
|
+
EnvKey.CONTENT_ENCODING,
|
|
196
|
+
EnvKey.UPGRADE,
|
|
197
|
+
EnvKey.AUTHORIZATION,
|
|
198
|
+
EnvKey.DATE,
|
|
199
|
+
EnvKey.ALLOW,
|
|
200
|
+
EnvKey.SERVER,
|
|
201
|
+
EnvKey.EXPIRES,
|
|
202
|
+
EnvKey.CACHE_CONTROL,
|
|
203
|
+
null,
|
|
204
|
+
EnvKey.CONNECTION,
|
|
205
|
+
EnvKey.WWW_AUTHENTICATE,
|
|
206
|
+
EnvKey.WARNING,
|
|
207
|
+
EnvKey.LOCATION,
|
|
208
|
+
EnvKey.REQUEST_METHOD,
|
|
209
|
+
EnvKey.USER_AGENT,
|
|
210
|
+
EnvKey.CONTENT_LOCATION,
|
|
211
|
+
EnvKey.MAX_FORWARDS,
|
|
212
|
+
EnvKey.ACCEPT_RANGES,
|
|
213
|
+
EnvKey.X_REAL_IP,
|
|
214
|
+
null,
|
|
215
|
+
EnvKey.PRAGMA,
|
|
216
|
+
EnvKey.QUERY_STRING,
|
|
217
|
+
null, null, null, null, null,
|
|
218
|
+
EnvKey.PROXY_AUTHENTICATE,
|
|
219
|
+
null, null, null, null,
|
|
220
|
+
EnvKey.LAST_MODIFIED,
|
|
221
|
+
null,
|
|
222
|
+
EnvKey.SERVER_PROTOCOL,
|
|
223
|
+
null, null, null, null, null, null, null, null, null,
|
|
224
|
+
null, null, null, null, null, null, null, null, null,
|
|
225
|
+
EnvKey.PROXY_AUTHORIZATION
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
static {
|
|
229
|
+
// The perfect hash function must be regenerated if the list above changes
|
|
230
|
+
for (EnvKey key : values()) {
|
|
231
|
+
EnvKey found;
|
|
232
|
+
assert (found = keyForKey(key)) == key : key + " was not matched by perfect hash and found " + found;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Only used for verification of the perfect hash, at boot with asserts enabled.
|
|
237
|
+
private static EnvKey keyForKey(EnvKey key) {
|
|
238
|
+
byte[] bytes = key.name().getBytes(StandardCharsets.ISO_8859_1);
|
|
239
|
+
return keyList[hash(bytes, 0, bytes.length)];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -10,43 +10,22 @@ import org.jruby.RubyString;
|
|
|
10
10
|
|
|
11
11
|
import org.jruby.anno.JRubyMethod;
|
|
12
12
|
|
|
13
|
-
import org.jruby.runtime.
|
|
13
|
+
import org.jruby.runtime.ThreadContext;
|
|
14
14
|
import org.jruby.runtime.builtin.IRubyObject;
|
|
15
15
|
|
|
16
16
|
import org.jruby.exceptions.RaiseException;
|
|
17
17
|
|
|
18
18
|
import org.jruby.util.ByteList;
|
|
19
19
|
|
|
20
|
+
import java.util.Arrays;
|
|
21
|
+
|
|
22
|
+
import static java.util.Arrays.stream;
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
|
|
22
26
|
* @author <a href="mailto:headius@headius.com">Charles Oliver Nutter</a>
|
|
23
27
|
*/
|
|
24
28
|
public class Http11 extends RubyObject {
|
|
25
|
-
public final static int MAX_FIELD_NAME_LENGTH = 256;
|
|
26
|
-
public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length.";
|
|
27
|
-
public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024;
|
|
28
|
-
public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length.";
|
|
29
|
-
public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
|
|
30
|
-
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
|
|
31
|
-
public final static int MAX_FRAGMENT_LENGTH = 1024;
|
|
32
|
-
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
|
|
33
|
-
public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
|
|
34
|
-
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
|
|
35
|
-
public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
|
|
36
|
-
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the " + MAX_QUERY_STRING_LENGTH +" allowed length.";
|
|
37
|
-
public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
|
|
38
|
-
public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length.";
|
|
39
|
-
|
|
40
|
-
public static final ByteList CONTENT_TYPE_BYTELIST = new ByteList(ByteList.plain("CONTENT_TYPE"));
|
|
41
|
-
public static final ByteList CONTENT_LENGTH_BYTELIST = new ByteList(ByteList.plain("CONTENT_LENGTH"));
|
|
42
|
-
public static final ByteList HTTP_PREFIX_BYTELIST = new ByteList(ByteList.plain("HTTP_"));
|
|
43
|
-
public static final ByteList COMMA_SPACE_BYTELIST = new ByteList(ByteList.plain(", "));
|
|
44
|
-
public static final ByteList REQUEST_METHOD_BYTELIST = new ByteList(ByteList.plain("REQUEST_METHOD"));
|
|
45
|
-
public static final ByteList REQUEST_URI_BYTELIST = new ByteList(ByteList.plain("REQUEST_URI"));
|
|
46
|
-
public static final ByteList FRAGMENT_BYTELIST = new ByteList(ByteList.plain("FRAGMENT"));
|
|
47
|
-
public static final ByteList REQUEST_PATH_BYTELIST = new ByteList(ByteList.plain("REQUEST_PATH"));
|
|
48
|
-
public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING"));
|
|
49
|
-
public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL"));
|
|
50
29
|
|
|
51
30
|
public static String getEnvOrProperty(String name) {
|
|
52
31
|
String envValue = System.getenv(name);
|
|
@@ -69,29 +48,35 @@ public class Http11 extends RubyObject {
|
|
|
69
48
|
}
|
|
70
49
|
}
|
|
71
50
|
|
|
72
|
-
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
|
73
|
-
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
|
74
|
-
return new Http11(runtime, klass);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
51
|
public static void createHttp11(Ruby runtime) {
|
|
79
52
|
RubyModule mPuma = runtime.defineModule("Puma");
|
|
80
53
|
mPuma.defineClassUnder("HttpParserError",runtime.getClass("StandardError"),runtime.getClass("StandardError").getAllocator());
|
|
81
54
|
|
|
82
|
-
|
|
55
|
+
// Set up pre-allocated strings for HTTP/1.1 headers and CGI variables
|
|
56
|
+
RubyString[] envStrings =
|
|
57
|
+
stream(EnvKey.values())
|
|
58
|
+
.map((key) -> internRubyString(runtime, key))
|
|
59
|
+
.toArray(RubyString[]::new);
|
|
60
|
+
|
|
61
|
+
RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),(r, c) -> new Http11(r, c, envStrings));
|
|
83
62
|
cHttpParser.defineAnnotatedMethods(Http11.class);
|
|
84
63
|
}
|
|
85
64
|
|
|
86
|
-
private Ruby runtime
|
|
87
|
-
|
|
65
|
+
private static RubyString internRubyString(Ruby runtime, EnvKey key) {
|
|
66
|
+
return runtime.freezeAndDedupString(RubyString.newStringShared(runtime, key.httpName));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private final Ruby runtime;
|
|
70
|
+
private final Http11Parser hp;
|
|
71
|
+
final RubyString[] envStrings;
|
|
88
72
|
private RubyString body;
|
|
89
73
|
|
|
90
|
-
public Http11(Ruby runtime, RubyClass clazz) {
|
|
74
|
+
public Http11(Ruby runtime, RubyClass clazz, RubyString[] envStrings) {
|
|
91
75
|
super(runtime,clazz);
|
|
92
76
|
this.runtime = runtime;
|
|
93
77
|
this.hp = new Http11Parser();
|
|
94
|
-
this.
|
|
78
|
+
this.envStrings = envStrings;
|
|
79
|
+
this.hp.init();
|
|
95
80
|
}
|
|
96
81
|
|
|
97
82
|
public static void validateMaxLength(Ruby runtime, int len, int max, String msg) {
|
|
@@ -109,141 +94,228 @@ public class Http11 extends RubyObject {
|
|
|
109
94
|
return (RubyClass)runtime.getModule("Puma").getConstant("HttpParserError");
|
|
110
95
|
}
|
|
111
96
|
|
|
112
|
-
|
|
97
|
+
private static RubyString fstringForField(RubyString[] envStrings, byte[] buffer, int field, int flen) {
|
|
98
|
+
EnvKey key = EnvKey.keyForField(buffer, field, flen);
|
|
99
|
+
if (key != null) return envStrings[key.ordinal()];
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static boolean is_ows(int c) {
|
|
104
|
+
return c == ' ' || c == '\t';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public static final byte[] HTTP_PREFIX_BYTELIST = ByteList.plain("HTTP_");
|
|
108
|
+
private static final int HTTP_PREFIX_LENGTH = 5;
|
|
109
|
+
public static final byte[] COMMA_SPACE_BYTELIST = ByteList.plain(", ");
|
|
110
|
+
|
|
111
|
+
public static void http_field(Ruby runtime, RubyString[] envStrings, Http11Parser hp, int vlen) {
|
|
113
112
|
RubyString f;
|
|
114
113
|
IRubyObject v;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
ByteList b = new ByteList(buffer,field,flen);
|
|
119
|
-
for(int i = 0,j = b.length();i<j;i++) {
|
|
120
|
-
int bite = b.get(i) & 0xFF;
|
|
121
|
-
if(bite == '-') {
|
|
122
|
-
b.set(i, (byte)'_');
|
|
123
|
-
} else if(bite == '_') {
|
|
124
|
-
b.set(i, (byte)',');
|
|
125
|
-
} else {
|
|
126
|
-
b.set(i, (byte)Character.toUpperCase(bite));
|
|
127
|
-
}
|
|
128
|
-
}
|
|
114
|
+
int field_len = hp.field_len;
|
|
115
|
+
validateFieldNameLength(runtime, field_len);
|
|
116
|
+
validateFieldValueLength(runtime, vlen);
|
|
129
117
|
|
|
130
|
-
|
|
118
|
+
byte[] buffer = hp.buffer;
|
|
119
|
+
int field_start = hp.field_start;
|
|
120
|
+
f = fstringForField(envStrings, buffer, field_start, field_len);
|
|
131
121
|
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
} else {
|
|
135
|
-
f = RubyString.newStringShared(runtime, HTTP_PREFIX_BYTELIST);
|
|
136
|
-
f.cat(b);
|
|
122
|
+
if (f == null) {
|
|
123
|
+
f = newHttpHeader(runtime, buffer, field_start, field_len);
|
|
137
124
|
}
|
|
138
125
|
|
|
139
|
-
|
|
126
|
+
int mark = hp.mark;
|
|
127
|
+
while (vlen > 0) {
|
|
128
|
+
if (!is_ows(buffer[mark + vlen - 1])) break;
|
|
129
|
+
vlen--;
|
|
130
|
+
}
|
|
131
|
+
while (vlen > 0 && is_ows(buffer[mark])) {
|
|
132
|
+
vlen--;
|
|
133
|
+
mark++;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
RubyHash req = hp.data;
|
|
140
137
|
v = req.fastARef(f);
|
|
141
138
|
if (v == null || v.isNil()) {
|
|
142
|
-
req.fastASet(f, RubyString.
|
|
139
|
+
req.fastASet(f, RubyString.newStringShared(runtime, buffer, mark, vlen));
|
|
143
140
|
} else {
|
|
144
141
|
RubyString vs = v.convertToString();
|
|
145
142
|
vs.cat(COMMA_SPACE_BYTELIST);
|
|
146
|
-
vs.cat(
|
|
143
|
+
vs.cat(buffer, mark, vlen);
|
|
147
144
|
}
|
|
148
145
|
}
|
|
149
146
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
private static RubyString newHttpHeader(Ruby runtime, byte[] buffer, int field_start, int field_len) {
|
|
148
|
+
byte[] bytes = new byte[HTTP_PREFIX_LENGTH + field_len];
|
|
149
|
+
System.arraycopy(HTTP_PREFIX_BYTELIST, 0, bytes, 0, HTTP_PREFIX_LENGTH);
|
|
150
|
+
snakeCaseCopy(buffer, field_start, field_len, bytes, HTTP_PREFIX_LENGTH);
|
|
151
|
+
return RubyString.newStringNoCopy(runtime, bytes);
|
|
153
152
|
}
|
|
154
153
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
static void snakeCaseCopy(byte[] s, int soff, int slen, byte[] d, int doff) {
|
|
155
|
+
for (int i = 0; i < slen; i++) {
|
|
156
|
+
int si = soff + i;
|
|
157
|
+
byte sch = s[si];
|
|
158
|
+
int di = doff + i;
|
|
159
|
+
byte dch = snakeUpcase(sch);
|
|
160
|
+
d[di] = dch;
|
|
161
|
+
}
|
|
159
162
|
}
|
|
160
163
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
static byte snakeUpcase(byte ch) {
|
|
165
|
+
if (ch >= 'a' && ch <= 'z')
|
|
166
|
+
ch = (byte) (ch & ~0x20);
|
|
167
|
+
else if (ch == '_')
|
|
168
|
+
ch = ',';
|
|
169
|
+
else if (ch == '-')
|
|
170
|
+
ch = '_';
|
|
171
|
+
return ch;
|
|
165
172
|
}
|
|
166
173
|
|
|
167
|
-
public static void
|
|
168
|
-
|
|
169
|
-
RubyString val = RubyString.newString(runtime,new ByteList(buffer,at,length));
|
|
170
|
-
req.fastASet(RubyString.newStringShared(runtime, REQUEST_PATH_BYTELIST),val);
|
|
174
|
+
public static void request_method(Ruby runtime, RubyString[] envStrings, Http11Parser hp, int length) {
|
|
175
|
+
hp.data.fastASet(envStrings[EnvKey.REQUEST_METHOD.ordinal()], newValueString(runtime, hp, length));
|
|
171
176
|
}
|
|
172
177
|
|
|
173
|
-
public static void
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
public static void request_uri(Ruby runtime, RubyString[] envStrings, Http11Parser hp, int length) {
|
|
179
|
+
validateRequestURILength(runtime, length);
|
|
180
|
+
hp.data.fastASet(envStrings[EnvKey.REQUEST_URI.ordinal()], newValueString(runtime, hp, length));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public static void fragment(Ruby runtime, RubyString[] envStrings, Http11Parser hp, int length) {
|
|
184
|
+
validateFragmentLength(runtime, length);
|
|
185
|
+
hp.data.fastASet(envStrings[EnvKey.FRAGMENT.ordinal()], newValueString(runtime, hp, length));
|
|
177
186
|
}
|
|
178
187
|
|
|
179
|
-
public static void
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
public static void request_path(Ruby runtime, RubyString[] envStrings, Http11Parser hp, int length) {
|
|
189
|
+
validateRequestPathLength(runtime, length);
|
|
190
|
+
hp.data.fastASet(envStrings[EnvKey.REQUEST_PATH.ordinal()], newValueString(runtime, hp, length));
|
|
182
191
|
}
|
|
183
192
|
|
|
184
|
-
public void
|
|
185
|
-
|
|
193
|
+
public static void query_string(Ruby runtime, RubyString[] envStrings, Http11Parser hp, int length) {
|
|
194
|
+
validateQueryStringLength(runtime, length);
|
|
195
|
+
hp.data.fastASet(envStrings[EnvKey.QUERY_STRING.ordinal()], newQueryString(runtime, hp, length));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public static void server_protocol(Ruby runtime, RubyString[] envStrings, Http11Parser hp, int length) {
|
|
199
|
+
hp.data.fastASet(envStrings[EnvKey.SERVER_PROTOCOL.ordinal()], newValueString(runtime, hp, length));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public void header_done(Ruby runtime, Http11Parser hp, int at, int length) {
|
|
203
|
+
body = RubyString.newStringShared(runtime, hp.buffer, at, length);
|
|
186
204
|
}
|
|
187
205
|
|
|
188
206
|
@JRubyMethod
|
|
189
207
|
public IRubyObject initialize() {
|
|
190
|
-
this.hp.
|
|
208
|
+
this.hp.init();
|
|
191
209
|
return this;
|
|
192
210
|
}
|
|
193
211
|
|
|
194
212
|
@JRubyMethod
|
|
195
|
-
public IRubyObject reset() {
|
|
196
|
-
this.hp.
|
|
197
|
-
return
|
|
213
|
+
public IRubyObject reset(ThreadContext context) {
|
|
214
|
+
this.hp.init();
|
|
215
|
+
return context.nil;
|
|
198
216
|
}
|
|
199
217
|
|
|
200
218
|
@JRubyMethod
|
|
201
|
-
public IRubyObject finish() {
|
|
202
|
-
this.hp
|
|
203
|
-
|
|
219
|
+
public IRubyObject finish(ThreadContext context) {
|
|
220
|
+
Http11Parser hp = this.hp;
|
|
221
|
+
hp.finish();
|
|
222
|
+
return hp.is_finished() ? context.tru : context.fals;
|
|
204
223
|
}
|
|
205
224
|
|
|
206
225
|
@JRubyMethod
|
|
207
226
|
public IRubyObject execute(IRubyObject req_hash, IRubyObject data, IRubyObject start) {
|
|
227
|
+
Ruby runtime = this.runtime;
|
|
208
228
|
int from = RubyNumeric.fix2int(start);
|
|
209
|
-
|
|
229
|
+
RubyString dataString = (RubyString) data;
|
|
230
|
+
dataString.setByteListShared();
|
|
231
|
+
ByteList d = dataString.getByteList();
|
|
210
232
|
if(from >= d.length()) {
|
|
211
233
|
throw newHTTPParserError(runtime, "Requested start is after data buffer end.");
|
|
212
234
|
} else {
|
|
213
235
|
Http11Parser hp = this.hp;
|
|
214
|
-
Http11Parser.HttpParser parser = hp.parser;
|
|
215
236
|
|
|
216
|
-
|
|
237
|
+
hp.data = (RubyHash) req_hash;
|
|
217
238
|
|
|
218
239
|
hp.execute(runtime, this, d,from);
|
|
219
240
|
|
|
220
|
-
|
|
241
|
+
validateMaxHeaderLength(runtime, hp);
|
|
221
242
|
|
|
222
243
|
if(hp.has_error()) {
|
|
223
244
|
throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
|
|
224
245
|
} else {
|
|
225
|
-
return runtime.newFixnum(
|
|
246
|
+
return runtime.newFixnum(hp.nread);
|
|
226
247
|
}
|
|
227
248
|
}
|
|
228
249
|
}
|
|
229
250
|
|
|
230
251
|
@JRubyMethod(name = "error?")
|
|
231
|
-
public IRubyObject has_error() {
|
|
232
|
-
return this.hp.has_error() ?
|
|
252
|
+
public IRubyObject has_error(ThreadContext context) {
|
|
253
|
+
return this.hp.has_error() ? context.tru : context.fals;
|
|
233
254
|
}
|
|
234
255
|
|
|
235
256
|
@JRubyMethod(name = "finished?")
|
|
236
|
-
public IRubyObject is_finished() {
|
|
237
|
-
return this.hp.is_finished() ?
|
|
257
|
+
public IRubyObject is_finished(ThreadContext context) {
|
|
258
|
+
return this.hp.is_finished() ? context.tru : context.fals;
|
|
238
259
|
}
|
|
239
260
|
|
|
240
261
|
@JRubyMethod
|
|
241
262
|
public IRubyObject nread() {
|
|
242
|
-
return runtime.newFixnum(this.hp.
|
|
263
|
+
return runtime.newFixnum(this.hp.nread);
|
|
243
264
|
}
|
|
244
265
|
|
|
245
266
|
@JRubyMethod
|
|
246
267
|
public IRubyObject body() {
|
|
247
268
|
return body;
|
|
248
269
|
}
|
|
270
|
+
|
|
271
|
+
public final static int MAX_FIELD_NAME_LENGTH = 256;
|
|
272
|
+
public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length.";
|
|
273
|
+
public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024;
|
|
274
|
+
public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length.";
|
|
275
|
+
public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
|
|
276
|
+
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
|
|
277
|
+
public final static int MAX_FRAGMENT_LENGTH = 1024;
|
|
278
|
+
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element FRAGMENT is longer than the 1024 allowed length.";
|
|
279
|
+
public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
|
|
280
|
+
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
|
|
281
|
+
public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
|
|
282
|
+
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the " + MAX_QUERY_STRING_LENGTH +" allowed length.";
|
|
283
|
+
public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
|
|
284
|
+
public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length.";
|
|
285
|
+
|
|
286
|
+
private static void validateFieldNameLength(Ruby runtime, int field_len) {
|
|
287
|
+
validateMaxLength(runtime, field_len, MAX_FIELD_NAME_LENGTH, MAX_FIELD_NAME_LENGTH_ERR);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private static void validateFieldValueLength(Ruby runtime, int field_len) {
|
|
291
|
+
validateMaxLength(runtime, field_len, MAX_FIELD_VALUE_LENGTH, MAX_FIELD_VALUE_LENGTH_ERR);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private static void validateRequestURILength(Ruby runtime, int length) {
|
|
295
|
+
validateMaxLength(runtime, length, MAX_REQUEST_URI_LENGTH, MAX_REQUEST_URI_LENGTH_ERR);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private static void validateFragmentLength(Ruby runtime, int length) {
|
|
299
|
+
validateMaxLength(runtime, length, MAX_FRAGMENT_LENGTH, MAX_FRAGMENT_LENGTH_ERR);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private static void validateRequestPathLength(Ruby runtime, int length) {
|
|
303
|
+
validateMaxLength(runtime, length, MAX_REQUEST_PATH_LENGTH, MAX_REQUEST_PATH_LENGTH_ERR);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private static void validateQueryStringLength(Ruby runtime, int length) {
|
|
307
|
+
validateMaxLength(runtime, length, MAX_QUERY_STRING_LENGTH, MAX_QUERY_STRING_LENGTH_ERR);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private static RubyString newValueString(Ruby runtime, Http11Parser hp, int length) {
|
|
311
|
+
return RubyString.newStringShared(runtime, hp.buffer, hp.mark, length);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private static RubyString newQueryString(Ruby runtime, Http11Parser hp, int length) {
|
|
315
|
+
return RubyString.newStringShared(runtime, hp.buffer, hp.query_start, length);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private static void validateMaxHeaderLength(Ruby runtime, Http11Parser hp) {
|
|
319
|
+
validateMaxLength(runtime, hp.nread, MAX_HEADER_LENGTH, MAX_HEADER_LENGTH_ERR);
|
|
320
|
+
}
|
|
249
321
|
}// Http11
|