unicorn 0.9.2 → 0.90.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG +3 -0
  3. data/GNUmakefile +7 -7
  4. data/Manifest +20 -23
  5. data/README +16 -13
  6. data/TODO +5 -3
  7. data/bin/unicorn +1 -1
  8. data/bin/unicorn_rails +1 -1
  9. data/ext/unicorn_http/c_util.h +107 -0
  10. data/ext/unicorn_http/common_field_optimization.h +110 -0
  11. data/ext/unicorn_http/ext_help.h +41 -5
  12. data/ext/unicorn_http/extconf.rb +3 -1
  13. data/ext/unicorn_http/global_variables.h +93 -0
  14. data/ext/unicorn_http/unicorn_http.c +2123 -326
  15. data/ext/unicorn_http/unicorn_http.rl +488 -87
  16. data/ext/unicorn_http/unicorn_http_common.rl +12 -1
  17. data/lib/unicorn.rb +0 -2
  18. data/lib/unicorn/app/exec_cgi.rb +0 -1
  19. data/lib/unicorn/app/inetd.rb +2 -0
  20. data/lib/unicorn/app/old_rails/static.rb +0 -2
  21. data/lib/unicorn/const.rb +1 -5
  22. data/lib/unicorn/http_request.rb +13 -29
  23. data/lib/unicorn/http_response.rb +1 -1
  24. data/lib/unicorn/tee_input.rb +48 -46
  25. data/lib/unicorn/util.rb +3 -3
  26. data/test/benchmark/README +0 -5
  27. data/test/exec/test_exec.rb +4 -0
  28. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/.gitignore +0 -0
  29. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/Rakefile +0 -0
  30. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/application_controller.rb +0 -0
  31. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/controllers/foo_controller.rb +0 -0
  32. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/app/helpers/application_helper.rb +0 -0
  33. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/boot.rb +0 -0
  34. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/database.yml +0 -0
  35. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environment.rb +0 -0
  36. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/development.rb +0 -0
  37. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/environments/production.rb +0 -0
  38. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/config/routes.rb +0 -0
  39. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/db/.gitignore +0 -0
  40. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/404.html +0 -0
  41. data/test/rails/{app-2.3.2.1 → app-2.3.3.1}/public/500.html +0 -0
  42. data/test/unit/test_http_parser.rb +112 -47
  43. data/test/unit/test_http_parser_ng.rb +284 -0
  44. data/test/unit/test_request.rb +25 -7
  45. data/test/unit/test_response.rb +11 -0
  46. data/test/unit/test_server.rb +7 -2
  47. data/test/unit/test_signals.rb +2 -0
  48. data/test/unit/test_tee_input.rb +118 -2
  49. data/test/unit/test_upload.rb +1 -1
  50. data/test/unit/test_util.rb +5 -0
  51. data/unicorn.gemspec +6 -6
  52. metadata +33 -37
  53. data/ext/unicorn_http/unicorn_http.h +0 -1289
  54. data/lib/unicorn/chunked_reader.rb +0 -77
  55. data/lib/unicorn/trailer_parser.rb +0 -52
  56. data/test/benchmark/big_request.rb +0 -44
  57. data/test/benchmark/request.rb +0 -56
  58. data/test/benchmark/response.rb +0 -30
  59. data/test/unit/test_chunked_reader.rb +0 -123
  60. data/test/unit/test_trailer_parser.rb +0 -52
@@ -1,60 +1,135 @@
1
1
  /**
2
+ * Copyright (c) 2009 Eric Wong (all bugs are Eric's fault)
2
3
  * Copyright (c) 2005 Zed A. Shaw
3
4
  * You can redistribute it and/or modify it under the same terms as Ruby.
4
5
  */
5
- #ifndef unicorn_http_h
6
- #define unicorn_http_h
7
-
6
+ #include "ruby.h"
7
+ #include "ext_help.h"
8
+ #include <assert.h>
9
+ #include <string.h>
8
10
  #include <sys/types.h>
11
+ #include "common_field_optimization.h"
12
+ #include "global_variables.h"
13
+ #include "c_util.h"
14
+
15
+ #define UH_FL_CHUNKED 0x1
16
+ #define UH_FL_HASBODY 0x2
17
+ #define UH_FL_INBODY 0x4
18
+ #define UH_FL_HASTRAILER 0x8
19
+ #define UH_FL_INTRAILER 0x10
20
+ #define UH_FL_INCHUNK 0x20
21
+ #define UH_FL_KAMETHOD 0x40
22
+ #define UH_FL_KAVERSION 0x80
9
23
 
10
- static void http_field(void *data, const char *field,
11
- size_t flen, const char *value, size_t vlen);
12
- static void request_method(void *data, const char *at, size_t length);
13
- static void scheme(void *data, const char *at, size_t length);
14
- static void host(void *data, const char *at, size_t length);
15
- static void request_uri(void *data, const char *at, size_t length);
16
- static void fragment(void *data, const char *at, size_t length);
17
- static void request_path(void *data, const char *at, size_t length);
18
- static void query_string(void *data, const char *at, size_t length);
19
- static void http_version(void *data, const char *at, size_t length);
20
- static void header_done(void *data, const char *at, size_t length);
21
-
22
- typedef struct http_parser {
23
- int cs;
24
- size_t body_start;
25
- size_t nread;
24
+ #define UH_FL_KEEPALIVE (UH_FL_KAMETHOD | UH_FL_KAVERSION)
25
+
26
+ struct http_parser {
27
+ int cs; /* Ragel internal state */
28
+ unsigned int flags;
26
29
  size_t mark;
27
- size_t field_start;
28
- size_t field_len;
29
- size_t query_start;
30
+ union { /* these 3 fields don't nest */
31
+ size_t field;
32
+ size_t query;
33
+ size_t offset;
34
+ } start;
35
+ union {
36
+ size_t field_len; /* only used during header processing */
37
+ size_t dest_offset; /* only used during body processing */
38
+ } s;
39
+ union {
40
+ off_t content;
41
+ off_t chunk;
42
+ } len;
43
+ };
30
44
 
31
- void *data;
32
- } http_parser;
45
+ static void finalize_header(VALUE req);
33
46
 
34
- static int http_parser_has_error(http_parser *parser);
35
- static int http_parser_is_finished(http_parser *parser);
47
+ #define REMAINING (unsigned long)(pe - p)
48
+ #define LEN(AT, FPC) (FPC - buffer - hp->AT)
49
+ #define MARK(M,FPC) (hp->M = (FPC) - buffer)
50
+ #define PTR_TO(F) (buffer + hp->F)
51
+ #define STR_NEW(M,FPC) rb_str_new(PTR_TO(M), LEN(M, FPC))
36
52
 
37
- /*
38
- * capitalizes all lower-case ASCII characters,
39
- * converts dashes to underscores.
40
- */
41
- static void snake_upcase_char(char *c)
53
+ static void
54
+ request_method(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
55
+ {
56
+ VALUE v;
57
+
58
+ if (CONST_MEM_EQ("GET", ptr, len)) {
59
+ hp->flags |= UH_FL_KAMETHOD;
60
+ v = g_GET;
61
+ } else if (CONST_MEM_EQ("HEAD", ptr, len)) {
62
+ hp->flags |= UH_FL_KAMETHOD;
63
+ v = g_HEAD;
64
+ } else {
65
+ v = rb_str_new(ptr, len);
66
+ }
67
+ rb_hash_aset(req, g_request_method, v);
68
+ }
69
+
70
+ static void
71
+ http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
42
72
  {
43
- if (*c >= 'a' && *c <= 'z')
44
- *c &= ~0x20;
45
- else if (*c == '-')
46
- *c = '_';
73
+ VALUE v;
74
+
75
+ if (CONST_MEM_EQ("HTTP/1.1", ptr, len)) {
76
+ hp->flags |= UH_FL_KAVERSION;
77
+ v = g_http_11;
78
+ } else {
79
+ v = rb_str_new(ptr, len);
80
+ }
81
+ rb_hash_aset(req, g_http_version, v);
47
82
  }
48
83
 
49
- static void downcase_char(char *c)
84
+ static void invalid_if_trailer(int flags)
50
85
  {
51
- if (*c >= 'A' && *c <= 'Z')
52
- *c |= 0x20;
86
+ if (flags & UH_FL_INTRAILER)
87
+ rb_raise(eHttpParserError, "invalid Trailer");
53
88
  }
54
89
 
55
- #define LEN(AT, FPC) (FPC - buffer - parser->AT)
56
- #define MARK(M,FPC) (parser->M = (FPC) - buffer)
57
- #define PTR_TO(F) (buffer + parser->F)
90
+ static void write_value(VALUE req, struct http_parser *hp,
91
+ const char *buffer, const char *p)
92
+ {
93
+ VALUE f = find_common_field(PTR_TO(start.field), hp->s.field_len);
94
+ VALUE v;
95
+ VALUE e;
96
+
97
+ VALIDATE_MAX_LENGTH(LEN(mark, p), FIELD_VALUE);
98
+ v = STR_NEW(mark, p);
99
+ if (f == Qnil) {
100
+ VALIDATE_MAX_LENGTH(hp->s.field_len, FIELD_NAME);
101
+ f = uncommon_field(PTR_TO(start.field), hp->s.field_len);
102
+ } else if (f == g_http_connection) {
103
+ if (hp->flags & UH_FL_KAMETHOD) {
104
+ if (STR_CSTR_CASE_EQ(v, "keep-alive"))
105
+ hp->flags |= UH_FL_KAVERSION;
106
+ else if (STR_CSTR_CASE_EQ(v, "close"))
107
+ hp->flags &= ~UH_FL_KEEPALIVE;
108
+ }
109
+ } else if (f == g_content_length) {
110
+ hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v));
111
+ if (hp->len.content < 0)
112
+ rb_raise(eHttpParserError, "invalid Content-Length");
113
+ hp->flags |= UH_FL_HASBODY;
114
+ invalid_if_trailer(hp->flags);
115
+ } else if (f == g_http_transfer_encoding) {
116
+ if (STR_CSTR_CASE_EQ(v, "chunked"))
117
+ hp->flags |= UH_FL_CHUNKED | UH_FL_HASBODY;
118
+ invalid_if_trailer(hp->flags);
119
+ } else if (f == g_http_trailer) {
120
+ hp->flags |= UH_FL_HASTRAILER;
121
+ invalid_if_trailer(hp->flags);
122
+ }
123
+
124
+ e = rb_hash_aref(req, f);
125
+ if (e == Qnil) {
126
+ rb_hash_aset(req, f, v);
127
+ } else if (f != g_http_host) {
128
+ /* full URLs in REQUEST_URI take precedence for the Host: header */
129
+ rb_str_buf_cat(e, ",", 1);
130
+ rb_str_buf_append(e, v);
131
+ }
132
+ }
58
133
 
59
134
  /** Machine **/
60
135
 
@@ -63,96 +138,422 @@ static void downcase_char(char *c)
63
138
 
64
139
  action mark {MARK(mark, fpc); }
65
140
 
66
- action start_field { MARK(field_start, fpc); }
141
+ action start_field { MARK(start.field, fpc); }
67
142
  action snake_upcase_field { snake_upcase_char((char *)fpc); }
68
143
  action downcase_char { downcase_char((char *)fpc); }
69
- action write_field {
70
- parser->field_len = LEN(field_start, fpc);
71
- }
72
-
144
+ action write_field { hp->s.field_len = LEN(start.field, fpc); }
73
145
  action start_value { MARK(mark, fpc); }
74
- action write_value {
75
- http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
76
- }
146
+ action write_value { write_value(req, hp, buffer, fpc); }
77
147
  action request_method {
78
- request_method(parser->data, PTR_TO(mark), LEN(mark, fpc));
148
+ request_method(hp, req, PTR_TO(mark), LEN(mark, fpc));
149
+ }
150
+ action scheme {
151
+ rb_hash_aset(req, g_rack_url_scheme, STR_NEW(mark, fpc));
152
+ }
153
+ action host {
154
+ rb_hash_aset(req, g_http_host, STR_NEW(mark, fpc));
79
155
  }
80
- action scheme { scheme(parser->data, PTR_TO(mark), LEN(mark, fpc)); }
81
- action host { host(parser->data, PTR_TO(mark), LEN(mark, fpc)); }
82
156
  action request_uri {
83
- request_uri(parser->data, PTR_TO(mark), LEN(mark, fpc));
157
+ size_t len = LEN(mark, fpc);
158
+ VALUE str;
159
+
160
+ VALIDATE_MAX_LENGTH(len, REQUEST_URI);
161
+ str = rb_hash_aset(req, g_request_uri, STR_NEW(mark, fpc));
162
+ /*
163
+ * "OPTIONS * HTTP/1.1\r\n" is a valid request, but we can't have '*'
164
+ * in REQUEST_PATH or PATH_INFO or else Rack::Lint will complain
165
+ */
166
+ if (STR_CSTR_EQ(str, "*")) {
167
+ str = rb_str_new(NULL, 0);
168
+ rb_hash_aset(req, g_path_info, str);
169
+ rb_hash_aset(req, g_request_path, str);
170
+ }
84
171
  }
85
172
  action fragment {
86
- fragment(parser->data, PTR_TO(mark), LEN(mark, fpc));
173
+ VALIDATE_MAX_LENGTH(LEN(mark, fpc), FRAGMENT);
174
+ rb_hash_aset(req, g_fragment, STR_NEW(mark, fpc));
87
175
  }
88
-
89
- action start_query {MARK(query_start, fpc); }
176
+ action start_query {MARK(start.query, fpc); }
90
177
  action query_string {
91
- query_string(parser->data, PTR_TO(query_start), LEN(query_start, fpc));
178
+ VALIDATE_MAX_LENGTH(LEN(start.query, fpc), QUERY_STRING);
179
+ rb_hash_aset(req, g_query_string, STR_NEW(start.query, fpc));
92
180
  }
181
+ action http_version { http_version(hp, req, PTR_TO(mark), LEN(mark, fpc)); }
182
+ action request_path {
183
+ VALUE val;
184
+ size_t len = LEN(mark, fpc);
93
185
 
94
- action http_version {
95
- http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
186
+ VALIDATE_MAX_LENGTH(len, REQUEST_PATH);
187
+ val = rb_hash_aset(req, g_request_path, STR_NEW(mark, fpc));
188
+
189
+ /* rack says PATH_INFO must start with "/" or be empty */
190
+ if (!STR_CSTR_EQ(val, "*"))
191
+ rb_hash_aset(req, g_path_info, val);
192
+ }
193
+ action add_to_chunk_size {
194
+ hp->len.chunk = step_incr(hp->len.chunk, fc, 16);
195
+ if (hp->len.chunk < 0)
196
+ rb_raise(eHttpParserError, "invalid chunk size");
96
197
  }
198
+ action header_done {
199
+ finalize_header(req);
97
200
 
98
- action request_path {
99
- request_path(parser->data, PTR_TO(mark), LEN(mark,fpc));
201
+ cs = http_parser_first_final;
202
+ if (hp->flags & UH_FL_HASBODY) {
203
+ hp->flags |= UH_FL_INBODY;
204
+ if (hp->flags & UH_FL_CHUNKED)
205
+ cs = http_parser_en_ChunkedBody;
206
+ } else {
207
+ assert(!(hp->flags & UH_FL_CHUNKED));
208
+ }
209
+ /*
210
+ * go back to Ruby so we can call the Rack application, we'll reenter
211
+ * the parser iff the body needs to be processed.
212
+ */
213
+ goto post_exec;
100
214
  }
101
215
 
102
- action done {
103
- parser->body_start = fpc - buffer + 1;
104
- header_done(parser->data, fpc + 1, pe - fpc - 1);
105
- fbreak;
216
+ action end_trailers {
217
+ cs = http_parser_first_final;
218
+ goto post_exec;
106
219
  }
107
220
 
221
+ action end_chunked_body {
222
+ if (hp->flags & UH_FL_HASTRAILER) {
223
+ hp->flags |= UH_FL_INTRAILER;
224
+ cs = http_parser_en_Trailers;
225
+ } else {
226
+ cs = http_parser_first_final;
227
+ }
228
+ ++p;
229
+ goto post_exec;
230
+ }
231
+
232
+ action skip_chunk_data {
233
+ skip_chunk_data_hack: {
234
+ size_t nr = MIN(hp->len.chunk, REMAINING);
235
+ memcpy(RSTRING_PTR(req) + hp->s.dest_offset, fpc, nr);
236
+ hp->s.dest_offset += nr;
237
+ hp->len.chunk -= nr;
238
+ p += nr;
239
+ assert(hp->len.chunk >= 0);
240
+ if (hp->len.chunk > REMAINING) {
241
+ hp->flags |= UH_FL_INCHUNK;
242
+ goto post_exec;
243
+ } else {
244
+ fhold;
245
+ fgoto chunk_end;
246
+ }
247
+ }}
248
+
108
249
  include unicorn_http_common "unicorn_http_common.rl";
109
250
  }%%
110
251
 
111
252
  /** Data **/
112
253
  %% write data;
113
254
 
114
- static void http_parser_init(http_parser *parser) {
255
+ static void http_parser_init(struct http_parser *hp)
256
+ {
115
257
  int cs = 0;
116
- memset(parser, 0, sizeof(*parser));
258
+ memset(hp, 0, sizeof(struct http_parser));
117
259
  %% write init;
118
- parser->cs = cs;
260
+ hp->cs = cs;
119
261
  }
120
262
 
121
263
  /** exec **/
122
- static void http_parser_execute(
123
- http_parser *parser, const char *buffer, size_t len)
264
+ static void http_parser_execute(struct http_parser *hp,
265
+ VALUE req, const char *buffer, size_t len)
124
266
  {
125
267
  const char *p, *pe;
126
- int cs = parser->cs;
127
- size_t off = parser->nread;
268
+ int cs = hp->cs;
269
+ size_t off = hp->start.offset;
270
+
271
+ if (cs == http_parser_first_final)
272
+ return;
128
273
 
129
274
  assert(off <= len && "offset past end of buffer");
130
275
 
131
276
  p = buffer+off;
132
277
  pe = buffer+len;
133
278
 
134
- assert(*pe == '\0' && "pointer does not end on NUL");
135
279
  assert(pe - p == len - off && "pointers aren't same distance");
136
280
 
281
+ if (hp->flags & UH_FL_INCHUNK) {
282
+ hp->flags &= ~(UH_FL_INCHUNK);
283
+ goto skip_chunk_data_hack;
284
+ }
137
285
  %% write exec;
138
-
139
- if (!http_parser_has_error(parser))
140
- parser->cs = cs;
141
- parser->nread += p - (buffer + off);
286
+ post_exec: /* "_out:" also goes here */
287
+ if (hp->cs != http_parser_error)
288
+ hp->cs = cs;
289
+ hp->start.offset = p - buffer;
142
290
 
143
291
  assert(p <= pe && "buffer overflow after parsing execute");
144
- assert(parser->nread <= len && "nread longer than length");
145
- assert(parser->body_start <= len && "body starts after buffer end");
146
- assert(parser->mark < len && "mark is after buffer end");
147
- assert(parser->field_len <= len && "field has length longer than whole buffer");
148
- assert(parser->field_start < len && "field starts after buffer end");
292
+ assert(hp->start.offset <= len && "start.offset longer than length");
293
+ }
294
+
295
+ static struct http_parser *data_get(VALUE self)
296
+ {
297
+ struct http_parser *hp;
298
+
299
+ Data_Get_Struct(self, struct http_parser, hp);
300
+ assert(hp);
301
+ return hp;
302
+ }
303
+
304
+ static void finalize_header(VALUE req)
305
+ {
306
+ VALUE temp = rb_hash_aref(req, g_rack_url_scheme);
307
+ VALUE server_name = g_localhost;
308
+ VALUE server_port = g_port_80;
309
+
310
+ /* set rack.url_scheme to "https" or "http", no others are allowed by Rack */
311
+ if (temp == Qnil) {
312
+ temp = rb_hash_aref(req, g_http_x_forwarded_proto);
313
+ if (temp != Qnil && STR_CSTR_EQ(temp, "https"))
314
+ server_port = g_port_443;
315
+ else
316
+ temp = g_http;
317
+ rb_hash_aset(req, g_rack_url_scheme, temp);
318
+ } else if (STR_CSTR_EQ(temp, "https")) {
319
+ server_port = g_port_443;
320
+ }
321
+
322
+ /* parse and set the SERVER_NAME and SERVER_PORT variables */
323
+ temp = rb_hash_aref(req, g_http_host);
324
+ if (temp != Qnil) {
325
+ char *colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
326
+ if (colon) {
327
+ long port_start = colon - RSTRING_PTR(temp) + 1;
328
+
329
+ server_name = rb_str_substr(temp, 0, colon - RSTRING_PTR(temp));
330
+ if ((RSTRING_LEN(temp) - port_start) > 0)
331
+ server_port = rb_str_substr(temp, port_start, RSTRING_LEN(temp));
332
+ } else {
333
+ server_name = temp;
334
+ }
335
+ }
336
+ rb_hash_aset(req, g_server_name, server_name);
337
+ rb_hash_aset(req, g_server_port, server_port);
338
+ rb_hash_aset(req, g_server_protocol, g_http_11);
339
+
340
+ /* rack requires QUERY_STRING */
341
+ if (rb_hash_aref(req, g_query_string) == Qnil)
342
+ rb_hash_aset(req, g_query_string, rb_str_new(NULL, 0));
343
+ }
344
+
345
+ static VALUE HttpParser_alloc(VALUE klass)
346
+ {
347
+ struct http_parser *hp;
348
+ return Data_Make_Struct(klass, struct http_parser, NULL, NULL, hp);
349
+ }
350
+
351
+
352
+ /**
353
+ * call-seq:
354
+ * parser.new -> parser
355
+ *
356
+ * Creates a new parser.
357
+ */
358
+ static VALUE HttpParser_init(VALUE self)
359
+ {
360
+ http_parser_init(data_get(self));
361
+
362
+ return self;
363
+ }
364
+
365
+
366
+ /**
367
+ * call-seq:
368
+ * parser.reset -> nil
369
+ *
370
+ * Resets the parser to it's initial state so that you can reuse it
371
+ * rather than making new ones.
372
+ */
373
+ static VALUE HttpParser_reset(VALUE self)
374
+ {
375
+ http_parser_init(data_get(self));
376
+
377
+ return Qnil;
378
+ }
379
+
380
+ static void advance_str(VALUE str, off_t nr)
381
+ {
382
+ long len = RSTRING_LEN(str);
383
+
384
+ if (len == 0)
385
+ return;
386
+
387
+ assert(nr <= len);
388
+ len -= nr;
389
+ if (len > 0) /* unlikely, len is usually 0 */
390
+ memmove(RSTRING_PTR(str), RSTRING_PTR(str) + nr, len);
391
+ rb_str_set_len(str, len);
392
+ }
393
+
394
+ static VALUE HttpParser_content_length(VALUE self)
395
+ {
396
+ struct http_parser *hp = data_get(self);
397
+
398
+ return (hp->flags & UH_FL_CHUNKED) ? Qnil : OFFT2NUM(hp->len.content);
399
+ }
400
+
401
+ /**
402
+ * call-seq:
403
+ * parser.headers(req, data) -> req or nil
404
+ * parser.trailers(req, data) -> req or nil
405
+ *
406
+ * Takes a Hash and a String of data, parses the String of data filling
407
+ * in the Hash returning the Hash if parsing is finished, nil otherwise
408
+ * When returning the req Hash, it may modify data to point to where
409
+ * body processing should begin
410
+ *
411
+ * Raises HttpParserError if there are parsing errors
412
+ */
413
+ static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data)
414
+ {
415
+ struct http_parser *hp = data_get(self);
416
+
417
+ http_parser_execute(hp, req, RSTRING_PTR(data), RSTRING_LEN(data));
418
+ VALIDATE_MAX_LENGTH(hp->start.offset, HEADER);
419
+
420
+ if (hp->cs == http_parser_first_final ||
421
+ hp->cs == http_parser_en_ChunkedBody) {
422
+ advance_str(data, hp->start.offset + 1);
423
+ hp->start.offset = 0;
424
+
425
+ return req;
426
+ }
427
+
428
+ if (hp->cs == http_parser_error)
429
+ rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
430
+
431
+ return Qnil;
432
+ }
433
+
434
+ static int chunked_eof(struct http_parser *hp)
435
+ {
436
+ return ((hp->cs == http_parser_first_final) ||
437
+ (hp->flags & UH_FL_INTRAILER));
438
+ }
439
+
440
+ static VALUE HttpParser_body_eof(VALUE self)
441
+ {
442
+ struct http_parser *hp = data_get(self);
443
+
444
+ if (hp->flags & UH_FL_CHUNKED)
445
+ return chunked_eof(hp) ? Qtrue : Qfalse;
446
+
447
+ return hp->len.content == 0 ? Qtrue : Qfalse;
149
448
  }
150
449
 
151
- static int http_parser_has_error(http_parser *parser) {
152
- return parser->cs == http_parser_error;
450
+ static VALUE HttpParser_keepalive(VALUE self)
451
+ {
452
+ struct http_parser *hp = data_get(self);
453
+
454
+ return (hp->flags & UH_FL_KEEPALIVE) == UH_FL_KEEPALIVE ? Qtrue : Qfalse;
455
+ }
456
+
457
+ /**
458
+ * call-seq:
459
+ * parser.filter_body(buf, data) -> nil/data
460
+ *
461
+ * Takes a String of +data+, will modify data if dechunking is done.
462
+ * Returns +nil+ if there is more data left to process. Returns
463
+ * +data+ if body processing is complete. When returning +data+,
464
+ * it may modify +data+ so the start of the string points to where
465
+ * the body ended so that trailer processing can begin.
466
+ *
467
+ * Raises HttpParserError if there are dechunking errors
468
+ * Basically this is a glorified memcpy(3) that copies +data+
469
+ * into +buf+ while filtering it through the dechunker.
470
+ */
471
+ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data)
472
+ {
473
+ struct http_parser *hp = data_get(self);
474
+ char *dptr = RSTRING_PTR(data);
475
+ long dlen = RSTRING_LEN(data);
476
+
477
+ StringValue(buf);
478
+ rb_str_resize(buf, dlen); /* we can never copy more than dlen bytes */
479
+ OBJ_TAINT(buf); /* keep weirdo $SAFE users happy */
480
+
481
+ if (hp->flags & UH_FL_CHUNKED) {
482
+ if (chunked_eof(hp))
483
+ goto end_of_body;
484
+
485
+ hp->s.dest_offset = 0;
486
+ http_parser_execute(hp, buf, dptr, dlen);
487
+ if (hp->cs == http_parser_error)
488
+ rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
489
+
490
+ assert(hp->s.dest_offset <= hp->start.offset);
491
+ advance_str(data, hp->start.offset);
492
+ rb_str_set_len(buf, hp->s.dest_offset);
493
+
494
+ if (RSTRING_LEN(buf) == 0 && chunked_eof(hp)) {
495
+ assert(hp->len.chunk == 0);
496
+ } else {
497
+ data = Qnil;
498
+ }
499
+ } else {
500
+ /* no need to enter the Ragel machine for unchunked transfers */
501
+ assert(hp->len.content >= 0);
502
+ if (hp->len.content > 0) {
503
+ long nr = MIN(dlen, hp->len.content);
504
+
505
+ memcpy(RSTRING_PTR(buf), dptr, nr);
506
+ hp->len.content -= nr;
507
+ if (hp->len.content == 0)
508
+ hp->cs = http_parser_first_final;
509
+ advance_str(data, nr);
510
+ rb_str_set_len(buf, nr);
511
+ data = Qnil;
512
+ }
513
+ }
514
+ end_of_body:
515
+ hp->start.offset = 0; /* for trailer parsing */
516
+ return data;
153
517
  }
154
518
 
155
- static int http_parser_is_finished(http_parser *parser) {
156
- return parser->cs == http_parser_first_final;
519
+ #define SET_GLOBAL(var,str) do { \
520
+ var = find_common_field(str, sizeof(str) - 1); \
521
+ assert(var != Qnil); \
522
+ } while (0)
523
+
524
+ void Init_unicorn_http(void)
525
+ {
526
+ init_globals();
527
+ rb_define_alloc_func(cHttpParser, HttpParser_alloc);
528
+ rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
529
+ rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
530
+ rb_define_method(cHttpParser, "headers", HttpParser_headers, 2);
531
+ rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2);
532
+ rb_define_method(cHttpParser, "trailers", HttpParser_headers, 2);
533
+ rb_define_method(cHttpParser, "content_length", HttpParser_content_length, 0);
534
+ rb_define_method(cHttpParser, "body_eof?", HttpParser_body_eof, 0);
535
+ rb_define_method(cHttpParser, "keepalive?", HttpParser_keepalive, 0);
536
+
537
+ /*
538
+ * The maximum size a single chunk when using chunked transfer encoding.
539
+ * This is only a theoretical maximum used to detect errors in clients,
540
+ * it is highly unlikely to encounter clients that send more than
541
+ * several kilobytes at once.
542
+ */
543
+ rb_define_const(cHttpParser, "CHUNK_MAX", OFFT2NUM(UH_OFF_T_MAX));
544
+
545
+ /*
546
+ * The maximum size of the body as specified by Content-Length.
547
+ * This is only a theoretical maximum, the actual limit is subject
548
+ * to the limits of the file system used for +Dir::tmpdir+
549
+ */
550
+ rb_define_const(cHttpParser, "LENGTH_MAX", OFFT2NUM(UH_OFF_T_MAX));
551
+
552
+ init_common_fields();
553
+ SET_GLOBAL(g_http_host, "HOST");
554
+ SET_GLOBAL(g_http_trailer, "TRAILER");
555
+ SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
556
+ SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
557
+ SET_GLOBAL(g_http_connection, "CONNECTION");
157
558
  }
158
- #endif /* unicorn_http_h */
559
+ #undef SET_GLOBAL