unicorn 0.90.0 → 0.91.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -6
- data/CHANGELOG +1 -0
- data/DESIGN +2 -2
- data/GNUmakefile +5 -3
- data/LICENSE +41 -39
- data/README +4 -4
- data/examples/echo.ru +1 -1
- data/ext/unicorn_http/global_variables.h +4 -6
- data/ext/unicorn_http/unicorn_http.rl +118 -17
- data/ext/unicorn_http/unicorn_http_common.rl +9 -4
- data/lib/unicorn.rb +2 -3
- data/lib/unicorn/const.rb +1 -1
- data/lib/unicorn/http_response.rb +8 -3
- data/lib/unicorn/launcher.rb +2 -2
- data/lib/unicorn/tee_input.rb +9 -12
- data/local.mk.sample +1 -1
- data/test/exec/test_exec.rb +20 -0
- data/test/unit/test_http_parser.rb +80 -11
- data/test/unit/test_http_parser_ng.rb +22 -0
- data/test/unit/test_server.rb +10 -0
- data/unicorn.gemspec +2 -2
- metadata +2 -2
data/.document
CHANGED
data/CHANGELOG
CHANGED
data/DESIGN
CHANGED
@@ -11,8 +11,8 @@
|
|
11
11
|
only non-Ruby part and there are no plans to add any more
|
12
12
|
non-Ruby components.
|
13
13
|
|
14
|
-
* All HTTP protocol parsing and I/O is done
|
15
|
-
1. read/parse HTTP request in full
|
14
|
+
* All HTTP protocol parsing and I/O is done much like Mongrel:
|
15
|
+
1. read/parse HTTP request headers in full
|
16
16
|
2. call Rack application
|
17
17
|
3. write HTTP response back to the client
|
18
18
|
|
data/GNUmakefile
CHANGED
@@ -128,9 +128,11 @@ Manifest:
|
|
128
128
|
cmp $@+ $@ || mv $@+ $@
|
129
129
|
$(RM) -f $@+
|
130
130
|
|
131
|
-
# using rdoc 2.4.1
|
132
|
-
doc: .document
|
133
|
-
rdoc -Na -
|
131
|
+
# using rdoc 2.4.1+
|
132
|
+
doc: .document $(ext)/unicorn_http.c
|
133
|
+
rdoc -Na -t "$(shell sed -ne '1s/^= //p' README)"
|
134
|
+
install -m644 COPYING doc/COPYING
|
135
|
+
cd doc && ln README.html tmp.html && mv tmp.html index.html
|
134
136
|
|
135
137
|
rails_git_url = git://github.com/rails/rails.git
|
136
138
|
rails_git := vendor/rails.git
|
data/LICENSE
CHANGED
@@ -1,53 +1,55 @@
|
|
1
|
-
Unicorn is copyrighted free software by Eric Wong
|
2
|
-
and contributors. You can redistribute it
|
3
|
-
|
1
|
+
Unicorn is copyrighted free software by Eric Wong
|
2
|
+
(mailto:normalperson@yhbt.net) and contributors. You can redistribute it
|
3
|
+
and/or modify it under either the terms of the
|
4
|
+
{GPL2}[http://www.gnu.org/licenses/gpl-2.0.txt] (see link:COPYING) or
|
5
|
+
the conditions below:
|
4
6
|
|
5
|
-
1. You may make and give away verbatim copies of the source form of the
|
6
|
-
|
7
|
-
|
7
|
+
1. You may make and give away verbatim copies of the source form of the
|
8
|
+
software without restriction, provided that you duplicate all of the
|
9
|
+
original copyright notices and associated disclaimers.
|
8
10
|
|
9
|
-
2. You may modify your copy of the software in any way, provided that
|
10
|
-
|
11
|
+
2. You may modify your copy of the software in any way, provided that
|
12
|
+
you do at least ONE of the following:
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
a) place your modifications in the Public Domain or otherwise make them
|
15
|
+
Freely Available, such as by posting said modifications to Usenet or an
|
16
|
+
equivalent medium, or by allowing the author to include your
|
17
|
+
modifications in the software.
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
b) use the modified software only within your corporation or
|
20
|
+
organization.
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
+
c) rename any non-standard executables so the names do not conflict with
|
23
|
+
standard executables, which must also be provided.
|
22
24
|
|
23
|
-
|
25
|
+
d) make other distribution arrangements with the author.
|
24
26
|
|
25
|
-
3. You may distribute the software in object code or executable
|
26
|
-
|
27
|
+
3. You may distribute the software in object code or executable
|
28
|
+
form, provided that you do at least ONE of the following:
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
a) distribute the executables and library files of the software,
|
31
|
+
together with instructions (in the manual page or equivalent) on where
|
32
|
+
to get the original distribution.
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
+
b) accompany the distribution with the machine-readable source of the
|
35
|
+
software.
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
+
c) give non-standard executables non-standard names, with
|
38
|
+
instructions on where to get the original software distribution.
|
37
39
|
|
38
|
-
|
40
|
+
d) make other distribution arrangements with the author.
|
39
41
|
|
40
|
-
4. You may modify and include the part of the software into any other
|
41
|
-
|
42
|
-
|
42
|
+
4. You may modify and include the part of the software into any other
|
43
|
+
software (possibly commercial). But some files in the distribution
|
44
|
+
are not written by the author, so that they are not under this terms.
|
43
45
|
|
44
|
-
5. The scripts and library files supplied as input to or produced as
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
5. The scripts and library files supplied as input to or produced as
|
47
|
+
output from the software do not automatically fall under the
|
48
|
+
copyright of the software, but belong to whomever generated them,
|
49
|
+
and may be sold commercially, and may be aggregated with this
|
50
|
+
software.
|
49
51
|
|
50
|
-
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
53
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
54
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
55
|
+
PURPOSE.
|
data/README
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
= Unicorn: Rack HTTP server for Unix
|
1
|
+
= Unicorn: Rack HTTP server for Unix and fast clients
|
2
2
|
|
3
3
|
== Features
|
4
4
|
|
5
5
|
* Designed for Rack, Unix, fast clients, and ease-of-debugging. We
|
6
6
|
cut out everything that is better supported by the operating system,
|
7
|
-
nginx or Rack.
|
7
|
+
{nginx}[http://nginx.net/] or {Rack}[http://rack.rubyforge.org/].
|
8
8
|
|
9
9
|
* Compatible with both Ruby 1.8 and 1.9, Rubinius support is planned.
|
10
10
|
|
@@ -60,7 +60,7 @@
|
|
60
60
|
== License
|
61
61
|
|
62
62
|
Unicorn is copyright 2009 Eric Wong and contributors.
|
63
|
-
It is based on Mongrel and carries the same license
|
63
|
+
It is based on Mongrel and carries the same license.
|
64
64
|
|
65
65
|
Mongrel is copyright 2007 Zed A. Shaw and contributors. It is licensed
|
66
66
|
under the Ruby license and the GPL2. See the included LICENSE file for
|
@@ -162,7 +162,7 @@ requests) go to the mailing list/newsgroup. Patches must be sent inline
|
|
162
162
|
to post on the mailing list. No top posting. Address replies +To:+ (or
|
163
163
|
+Cc:+) the original sender and +Cc:+ the mailing list.
|
164
164
|
|
165
|
-
* email: mongrel-unicorn@rubyforge.org
|
165
|
+
* email: mailto:mongrel-unicorn@rubyforge.org
|
166
166
|
* nntp: nntp://news.gmane.org/gmane.comp.lang.ruby.unicorn.general
|
167
167
|
* archives: http://rubyforge.org/pipermail/mongrel-unicorn/
|
168
168
|
* subscribe: http://rubyforge.org/mailman/listinfo/mongrel-unicorn/
|
data/examples/echo.ru
CHANGED
@@ -21,7 +21,7 @@ end
|
|
21
21
|
|
22
22
|
use Rack::Chunked
|
23
23
|
run lambda { |env|
|
24
|
-
/\A100-continue\z/ =~ env['HTTP_EXPECT'] and return [100, {}, []]
|
24
|
+
/\A100-continue\z/i =~ env['HTTP_EXPECT'] and return [100, {}, []]
|
25
25
|
[ 200, { 'Content-Type' => 'application/octet-stream' },
|
26
26
|
EchoBody.new(env['rack.input']) ]
|
27
27
|
}
|
@@ -25,6 +25,8 @@ static VALUE g_port_80;
|
|
25
25
|
static VALUE g_port_443;
|
26
26
|
static VALUE g_localhost;
|
27
27
|
static VALUE g_http;
|
28
|
+
static VALUE g_http_09;
|
29
|
+
static VALUE g_http_10;
|
28
30
|
static VALUE g_http_11;
|
29
31
|
static VALUE g_GET;
|
30
32
|
static VALUE g_HEAD;
|
@@ -61,8 +63,6 @@ DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
|
61
63
|
|
62
64
|
void init_globals(void)
|
63
65
|
{
|
64
|
-
mUnicorn = rb_define_module("Unicorn");
|
65
|
-
|
66
66
|
DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
|
67
67
|
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
68
68
|
DEF_GLOBAL(request_uri, "REQUEST_URI");
|
@@ -80,12 +80,10 @@ void init_globals(void)
|
|
80
80
|
DEF_GLOBAL(localhost, "localhost");
|
81
81
|
DEF_GLOBAL(http, "http");
|
82
82
|
DEF_GLOBAL(http_11, "HTTP/1.1");
|
83
|
+
DEF_GLOBAL(http_10, "HTTP/1.0");
|
84
|
+
DEF_GLOBAL(http_09, "HTTP/0.9");
|
83
85
|
DEF_GLOBAL(GET, "GET");
|
84
86
|
DEF_GLOBAL(HEAD, "HEAD");
|
85
|
-
|
86
|
-
eHttpParserError =
|
87
|
-
rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
|
88
|
-
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
89
87
|
}
|
90
88
|
|
91
89
|
#undef DEF_GLOBAL
|
@@ -20,6 +20,7 @@
|
|
20
20
|
#define UH_FL_INCHUNK 0x20
|
21
21
|
#define UH_FL_KAMETHOD 0x40
|
22
22
|
#define UH_FL_KAVERSION 0x80
|
23
|
+
#define UH_FL_HASHEADER 0x100
|
23
24
|
|
24
25
|
#define UH_FL_KEEPALIVE (UH_FL_KAMETHOD | UH_FL_KAVERSION)
|
25
26
|
|
@@ -36,13 +37,14 @@ struct http_parser {
|
|
36
37
|
size_t field_len; /* only used during header processing */
|
37
38
|
size_t dest_offset; /* only used during body processing */
|
38
39
|
} s;
|
40
|
+
VALUE cont;
|
39
41
|
union {
|
40
42
|
off_t content;
|
41
43
|
off_t chunk;
|
42
44
|
} len;
|
43
45
|
};
|
44
46
|
|
45
|
-
static void finalize_header(VALUE req);
|
47
|
+
static void finalize_header(struct http_parser *hp, VALUE req);
|
46
48
|
|
47
49
|
#define REMAINING (unsigned long)(pe - p)
|
48
50
|
#define LEN(AT, FPC) (FPC - buffer - hp->AT)
|
@@ -72,12 +74,17 @@ http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len)
|
|
72
74
|
{
|
73
75
|
VALUE v;
|
74
76
|
|
77
|
+
hp->flags |= UH_FL_HASHEADER;
|
78
|
+
|
75
79
|
if (CONST_MEM_EQ("HTTP/1.1", ptr, len)) {
|
76
80
|
hp->flags |= UH_FL_KAVERSION;
|
77
81
|
v = g_http_11;
|
82
|
+
} else if (CONST_MEM_EQ("HTTP/1.0", ptr, len)) {
|
83
|
+
v = g_http_10;
|
78
84
|
} else {
|
79
85
|
v = rb_str_new(ptr, len);
|
80
86
|
}
|
87
|
+
rb_hash_aset(req, g_server_protocol, v);
|
81
88
|
rb_hash_aset(req, g_http_version, v);
|
82
89
|
}
|
83
90
|
|
@@ -87,6 +94,31 @@ static void invalid_if_trailer(int flags)
|
|
87
94
|
rb_raise(eHttpParserError, "invalid Trailer");
|
88
95
|
}
|
89
96
|
|
97
|
+
static void write_cont_value(struct http_parser *hp,
|
98
|
+
const char *buffer, const char *p)
|
99
|
+
{
|
100
|
+
char *vptr;
|
101
|
+
|
102
|
+
if (!hp->cont)
|
103
|
+
rb_raise(eHttpParserError, "invalid continuation line");
|
104
|
+
|
105
|
+
assert(hp->mark > 0);
|
106
|
+
|
107
|
+
if (LEN(mark, p) == 0)
|
108
|
+
return;
|
109
|
+
|
110
|
+
if (RSTRING_LEN(hp->cont) > 0)
|
111
|
+
--hp->mark;
|
112
|
+
|
113
|
+
vptr = (char *)PTR_TO(mark);
|
114
|
+
|
115
|
+
if (RSTRING_LEN(hp->cont) > 0) {
|
116
|
+
assert(' ' == *vptr || '\t' == *vptr);
|
117
|
+
*vptr = ' ';
|
118
|
+
}
|
119
|
+
rb_str_buf_cat(hp->cont, vptr, LEN(mark, p));
|
120
|
+
}
|
121
|
+
|
90
122
|
static void write_value(VALUE req, struct http_parser *hp,
|
91
123
|
const char *buffer, const char *p)
|
92
124
|
{
|
@@ -123,11 +155,11 @@ static void write_value(VALUE req, struct http_parser *hp,
|
|
123
155
|
|
124
156
|
e = rb_hash_aref(req, f);
|
125
157
|
if (e == Qnil) {
|
126
|
-
rb_hash_aset(req, f, v);
|
158
|
+
hp->cont = rb_hash_aset(req, f, v);
|
127
159
|
} else if (f != g_http_host) {
|
128
160
|
/* full URLs in REQUEST_URI take precedence for the Host: header */
|
129
161
|
rb_str_buf_cat(e, ",", 1);
|
130
|
-
rb_str_buf_append(e, v);
|
162
|
+
hp->cont = rb_str_buf_append(e, v);
|
131
163
|
}
|
132
164
|
}
|
133
165
|
|
@@ -144,6 +176,7 @@ static void write_value(VALUE req, struct http_parser *hp,
|
|
144
176
|
action write_field { hp->s.field_len = LEN(start.field, fpc); }
|
145
177
|
action start_value { MARK(mark, fpc); }
|
146
178
|
action write_value { write_value(req, hp, buffer, fpc); }
|
179
|
+
action write_cont_value { write_cont_value(hp, buffer, fpc); }
|
147
180
|
action request_method {
|
148
181
|
request_method(hp, req, PTR_TO(mark), LEN(mark, fpc));
|
149
182
|
}
|
@@ -196,7 +229,7 @@ static void write_value(VALUE req, struct http_parser *hp,
|
|
196
229
|
rb_raise(eHttpParserError, "invalid chunk size");
|
197
230
|
}
|
198
231
|
action header_done {
|
199
|
-
finalize_header(req);
|
232
|
+
finalize_header(hp, req);
|
200
233
|
|
201
234
|
cs = http_parser_first_final;
|
202
235
|
if (hp->flags & UH_FL_HASBODY) {
|
@@ -301,7 +334,7 @@ static struct http_parser *data_get(VALUE self)
|
|
301
334
|
return hp;
|
302
335
|
}
|
303
336
|
|
304
|
-
static void finalize_header(VALUE req)
|
337
|
+
static void finalize_header(struct http_parser *hp, VALUE req)
|
305
338
|
{
|
306
339
|
VALUE temp = rb_hash_aref(req, g_rack_url_scheme);
|
307
340
|
VALUE server_name = g_localhost;
|
@@ -335,23 +368,32 @@ static void finalize_header(VALUE req)
|
|
335
368
|
}
|
336
369
|
rb_hash_aset(req, g_server_name, server_name);
|
337
370
|
rb_hash_aset(req, g_server_port, server_port);
|
338
|
-
|
371
|
+
if (!(hp->flags & UH_FL_HASHEADER))
|
372
|
+
rb_hash_aset(req, g_server_protocol, g_http_09);
|
339
373
|
|
340
374
|
/* rack requires QUERY_STRING */
|
341
375
|
if (rb_hash_aref(req, g_query_string) == Qnil)
|
342
376
|
rb_hash_aset(req, g_query_string, rb_str_new(NULL, 0));
|
343
377
|
}
|
344
378
|
|
379
|
+
static void hp_mark(void *ptr)
|
380
|
+
{
|
381
|
+
struct http_parser *hp = ptr;
|
382
|
+
|
383
|
+
if (hp->cont)
|
384
|
+
rb_gc_mark(hp->cont);
|
385
|
+
}
|
386
|
+
|
345
387
|
static VALUE HttpParser_alloc(VALUE klass)
|
346
388
|
{
|
347
389
|
struct http_parser *hp;
|
348
|
-
return Data_Make_Struct(klass, struct http_parser,
|
390
|
+
return Data_Make_Struct(klass, struct http_parser, hp_mark, NULL, hp);
|
349
391
|
}
|
350
392
|
|
351
393
|
|
352
394
|
/**
|
353
395
|
* call-seq:
|
354
|
-
* parser.new
|
396
|
+
* parser.new => parser
|
355
397
|
*
|
356
398
|
* Creates a new parser.
|
357
399
|
*/
|
@@ -362,10 +404,9 @@ static VALUE HttpParser_init(VALUE self)
|
|
362
404
|
return self;
|
363
405
|
}
|
364
406
|
|
365
|
-
|
366
407
|
/**
|
367
408
|
* call-seq:
|
368
|
-
* parser.reset
|
409
|
+
* parser.reset => nil
|
369
410
|
*
|
370
411
|
* Resets the parser to it's initial state so that you can reuse it
|
371
412
|
* rather than making new ones.
|
@@ -384,6 +425,8 @@ static void advance_str(VALUE str, off_t nr)
|
|
384
425
|
if (len == 0)
|
385
426
|
return;
|
386
427
|
|
428
|
+
rb_str_modify(str);
|
429
|
+
|
387
430
|
assert(nr <= len);
|
388
431
|
len -= nr;
|
389
432
|
if (len > 0) /* unlikely, len is usually 0 */
|
@@ -391,6 +434,18 @@ static void advance_str(VALUE str, off_t nr)
|
|
391
434
|
rb_str_set_len(str, len);
|
392
435
|
}
|
393
436
|
|
437
|
+
/**
|
438
|
+
* call-seq:
|
439
|
+
* parser.content_length => nil or Integer
|
440
|
+
*
|
441
|
+
* Returns the number of bytes left to run through HttpParser#filter_body.
|
442
|
+
* This will initially be the value of the "Content-Length" HTTP header
|
443
|
+
* after header parsing is complete and will decrease in value as
|
444
|
+
* HttpParser#filter_body is called for each chunk. This should return
|
445
|
+
* zero for requests with no body.
|
446
|
+
*
|
447
|
+
* This will return nil on "Transfer-Encoding: chunked" requests.
|
448
|
+
*/
|
394
449
|
static VALUE HttpParser_content_length(VALUE self)
|
395
450
|
{
|
396
451
|
struct http_parser *hp = data_get(self);
|
@@ -399,16 +454,24 @@ static VALUE HttpParser_content_length(VALUE self)
|
|
399
454
|
}
|
400
455
|
|
401
456
|
/**
|
457
|
+
* Document-method: trailers
|
402
458
|
* call-seq:
|
403
|
-
* parser.
|
404
|
-
*
|
459
|
+
* parser.trailers(req, data) => req or nil
|
460
|
+
*
|
461
|
+
* This is an alias for HttpParser#headers
|
462
|
+
*/
|
463
|
+
|
464
|
+
/**
|
465
|
+
* Document-method: headers
|
466
|
+
* call-seq:
|
467
|
+
* parser.headers(req, data) => req or nil
|
405
468
|
*
|
406
469
|
* Takes a Hash and a String of data, parses the String of data filling
|
407
470
|
* in the Hash returning the Hash if parsing is finished, nil otherwise
|
408
471
|
* When returning the req Hash, it may modify data to point to where
|
409
|
-
* body processing should begin
|
472
|
+
* body processing should begin.
|
410
473
|
*
|
411
|
-
* Raises HttpParserError if there are parsing errors
|
474
|
+
* Raises HttpParserError if there are parsing errors.
|
412
475
|
*/
|
413
476
|
static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data)
|
414
477
|
{
|
@@ -437,6 +500,13 @@ static int chunked_eof(struct http_parser *hp)
|
|
437
500
|
(hp->flags & UH_FL_INTRAILER));
|
438
501
|
}
|
439
502
|
|
503
|
+
/**
|
504
|
+
* call-seq:
|
505
|
+
* parser.body_eof? => true or false
|
506
|
+
*
|
507
|
+
* Detects if we're done filtering the body or not. This can be used
|
508
|
+
* to detect when to stop calling HttpParser#filter_body.
|
509
|
+
*/
|
440
510
|
static VALUE HttpParser_body_eof(VALUE self)
|
441
511
|
{
|
442
512
|
struct http_parser *hp = data_get(self);
|
@@ -447,6 +517,17 @@ static VALUE HttpParser_body_eof(VALUE self)
|
|
447
517
|
return hp->len.content == 0 ? Qtrue : Qfalse;
|
448
518
|
}
|
449
519
|
|
520
|
+
/**
|
521
|
+
* call-seq:
|
522
|
+
* parser.keepalive? => true or false
|
523
|
+
*
|
524
|
+
* This should be used to detect if a request can really handle
|
525
|
+
* keepalives and pipelining. Currently, the rules are:
|
526
|
+
*
|
527
|
+
* 1. MUST be a GET or HEAD request
|
528
|
+
* 2. MUST be HTTP/1.1 +or+ HTTP/1.0 with "Connection: keep-alive"
|
529
|
+
* 3. MUST NOT have "Connection: close" set
|
530
|
+
*/
|
450
531
|
static VALUE HttpParser_keepalive(VALUE self)
|
451
532
|
{
|
452
533
|
struct http_parser *hp = data_get(self);
|
@@ -456,7 +537,22 @@ static VALUE HttpParser_keepalive(VALUE self)
|
|
456
537
|
|
457
538
|
/**
|
458
539
|
* call-seq:
|
459
|
-
* parser.
|
540
|
+
* parser.headers? => true or false
|
541
|
+
*
|
542
|
+
* This should be used to detect if a request has headers (and if
|
543
|
+
* the response will have headers as well). HTTP/0.9 requests
|
544
|
+
* should return false, all subsequent HTTP versions will return true
|
545
|
+
*/
|
546
|
+
static VALUE HttpParser_has_headers(VALUE self)
|
547
|
+
{
|
548
|
+
struct http_parser *hp = data_get(self);
|
549
|
+
|
550
|
+
return (hp->flags & UH_FL_HASHEADER) ? Qtrue : Qfalse;
|
551
|
+
}
|
552
|
+
|
553
|
+
/**
|
554
|
+
* call-seq:
|
555
|
+
* parser.filter_body(buf, data) => nil/data
|
460
556
|
*
|
461
557
|
* Takes a String of +data+, will modify data if dechunking is done.
|
462
558
|
* Returns +nil+ if there is more data left to process. Returns
|
@@ -464,7 +560,7 @@ static VALUE HttpParser_keepalive(VALUE self)
|
|
464
560
|
* it may modify +data+ so the start of the string points to where
|
465
561
|
* the body ended so that trailer processing can begin.
|
466
562
|
*
|
467
|
-
* Raises HttpParserError if there are dechunking errors
|
563
|
+
* Raises HttpParserError if there are dechunking errors.
|
468
564
|
* Basically this is a glorified memcpy(3) that copies +data+
|
469
565
|
* into +buf+ while filtering it through the dechunker.
|
470
566
|
*/
|
@@ -523,6 +619,10 @@ end_of_body:
|
|
523
619
|
|
524
620
|
void Init_unicorn_http(void)
|
525
621
|
{
|
622
|
+
mUnicorn = rb_define_module("Unicorn");
|
623
|
+
eHttpParserError =
|
624
|
+
rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
|
625
|
+
cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
|
526
626
|
init_globals();
|
527
627
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
528
628
|
rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
|
@@ -533,6 +633,7 @@ void Init_unicorn_http(void)
|
|
533
633
|
rb_define_method(cHttpParser, "content_length", HttpParser_content_length, 0);
|
534
634
|
rb_define_method(cHttpParser, "body_eof?", HttpParser_body_eof, 0);
|
535
635
|
rb_define_method(cHttpParser, "keepalive?", HttpParser_keepalive, 0);
|
636
|
+
rb_define_method(cHttpParser, "headers?", HttpParser_has_headers, 0);
|
536
637
|
|
537
638
|
/*
|
538
639
|
* The maximum size a single chunk when using chunked transfer encoding.
|
@@ -545,7 +646,7 @@ void Init_unicorn_http(void)
|
|
545
646
|
/*
|
546
647
|
* The maximum size of the body as specified by Content-Length.
|
547
648
|
* This is only a theoretical maximum, the actual limit is subject
|
548
|
-
* to the limits of the file system used for +Dir
|
649
|
+
* to the limits of the file system used for +Dir.tmpdir+.
|
549
650
|
*/
|
550
651
|
rb_define_const(cHttpParser, "LENGTH_MAX", OFFT2NUM(UH_OFF_T_MAX));
|
551
652
|
|
@@ -19,6 +19,7 @@
|
|
19
19
|
uchar = (unreserved | escape | sorta_safe);
|
20
20
|
pchar = (uchar | ":" | "@" | "&" | "=" | "+");
|
21
21
|
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
22
|
+
lws = (" " | "\t");
|
22
23
|
|
23
24
|
# elements
|
24
25
|
token = (ascii -- (CTL | tspecials));
|
@@ -39,7 +40,8 @@
|
|
39
40
|
|
40
41
|
Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI;
|
41
42
|
Fragment = ( uchar | reserved )* >mark %fragment;
|
42
|
-
Method = (
|
43
|
+
Method = (token){1,20} >mark %request_method;
|
44
|
+
GetOnly = "GET" >mark %request_method;
|
43
45
|
|
44
46
|
http_number = ( digit+ "." digit+ ) ;
|
45
47
|
HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
|
@@ -49,7 +51,9 @@
|
|
49
51
|
|
50
52
|
field_value = any* >start_value %write_value;
|
51
53
|
|
52
|
-
|
54
|
+
value_cont = lws+ any* >start_value %write_cont_value;
|
55
|
+
|
56
|
+
message_header = ((field_name ":" " "* field_value)|value_cont) :> CRLF;
|
53
57
|
chunk_ext_val = token*;
|
54
58
|
chunk_ext_name = token*;
|
55
59
|
chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
|
@@ -62,8 +66,9 @@
|
|
62
66
|
ChunkedBody := chunk* last_chunk @end_chunked_body;
|
63
67
|
Trailers := (message_header)* CRLF @end_trailers;
|
64
68
|
|
65
|
-
|
69
|
+
FullRequest = Request_Line (message_header)* CRLF @header_done;
|
70
|
+
SimpleRequest = GetOnly " " Request_URI ("#"Fragment){0,1} CRLF @header_done;
|
66
71
|
|
67
|
-
main :=
|
72
|
+
main := FullRequest | SimpleRequest;
|
68
73
|
|
69
74
|
}%%
|
data/lib/unicorn.rb
CHANGED
@@ -447,8 +447,7 @@ module Unicorn
|
|
447
447
|
env.delete(Const::HTTP_EXPECT)
|
448
448
|
response = app.call(env)
|
449
449
|
end
|
450
|
-
|
451
|
-
HttpResponse.write(client, response)
|
450
|
+
HttpResponse.write(client, response, HttpRequest::PARSER.headers?)
|
452
451
|
# if we get any error, try to write something back to the client
|
453
452
|
# assuming we haven't closed the socket, but don't get hung up
|
454
453
|
# if the socket is already closed or broken. We'll always ensure
|
@@ -631,7 +630,7 @@ module Unicorn
|
|
631
630
|
end
|
632
631
|
|
633
632
|
def redirect_io(io, path)
|
634
|
-
File.open(path, '
|
633
|
+
File.open(path, 'ab') { |fp| io.reopen(fp) } if path
|
635
634
|
io.sync = true
|
636
635
|
end
|
637
636
|
|
data/lib/unicorn/const.rb
CHANGED
@@ -5,7 +5,7 @@ module Unicorn
|
|
5
5
|
# gave about a 3% to 10% performance improvement over using the strings directly.
|
6
6
|
# Symbols did not really improve things much compared to constants.
|
7
7
|
module Const
|
8
|
-
UNICORN_VERSION="0.
|
8
|
+
UNICORN_VERSION="0.91.0".freeze
|
9
9
|
|
10
10
|
DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
|
11
11
|
DEFAULT_PORT = "8080".freeze # default TCP listen port
|
@@ -33,9 +33,7 @@ module Unicorn
|
|
33
33
|
SKIP = { 'connection' => true, 'date' => true, 'status' => true }.freeze
|
34
34
|
OUT = [] # :nodoc
|
35
35
|
|
36
|
-
|
37
|
-
def self.write(socket, rack_response)
|
38
|
-
status, headers, body = rack_response
|
36
|
+
def self.write_header(socket, status, headers)
|
39
37
|
status = CODES[status.to_i] || status
|
40
38
|
OUT.clear
|
41
39
|
|
@@ -59,6 +57,13 @@ module Unicorn
|
|
59
57
|
"Status: #{status}\r\n" \
|
60
58
|
"Connection: close\r\n" \
|
61
59
|
"#{OUT.join(Z)}\r\n")
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
# writes the rack_response to socket as an HTTP response
|
64
|
+
def self.write(socket, rack_response, have_header = true)
|
65
|
+
status, headers, body = rack_response
|
66
|
+
write_header(socket, status, headers) if have_header
|
62
67
|
body.each { |chunk| socket.write(chunk) }
|
63
68
|
socket.close # flushes and uncorks the socket immediately
|
64
69
|
ensure
|
data/lib/unicorn/launcher.rb
CHANGED
@@ -24,8 +24,8 @@ class Unicorn::Launcher
|
|
24
24
|
exit if fork
|
25
25
|
|
26
26
|
# $stderr/$stderr can/will be redirected separately in the Unicorn config
|
27
|
-
|
28
|
-
|
27
|
+
Unicorn::Configurator::DEFAULTS[:stderr_path] = "/dev/null"
|
28
|
+
Unicorn::Configurator::DEFAULTS[:stdout_path] = "/dev/null"
|
29
29
|
end
|
30
30
|
$stdin.sync = $stdout.sync = $stderr.sync = true
|
31
31
|
end
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -1,16 +1,13 @@
|
|
1
|
-
# Copyright (c) 2009 Eric Wong
|
2
|
-
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
|
-
|
4
|
-
# acts like tee(1) on an input input to provide a input-like stream
|
5
|
-
# while providing rewindable semantics through a File/StringIO
|
6
|
-
# backing store. On the first pass, the input is only read on demand
|
7
|
-
# so your Rack application can use input notification (upload progress
|
8
|
-
# and like). This should fully conform to the Rack::InputWrapper
|
9
|
-
# specification on the public API. This class is intended to be a
|
10
|
-
# strict interpretation of Rack::InputWrapper functionality and will
|
11
|
-
# not support any deviations from it.
|
12
|
-
|
13
1
|
module Unicorn
|
2
|
+
|
3
|
+
# acts like tee(1) on an input input to provide a input-like stream
|
4
|
+
# while providing rewindable semantics through a File/StringIO
|
5
|
+
# backing store. On the first pass, the input is only read on demand
|
6
|
+
# so your Rack application can use input notification (upload progress
|
7
|
+
# and like). This should fully conform to the Rack::InputWrapper
|
8
|
+
# specification on the public API. This class is intended to be a
|
9
|
+
# strict interpretation of Rack::InputWrapper functionality and will
|
10
|
+
# not support any deviations from it.
|
14
11
|
class TeeInput < Struct.new(:socket, :req, :parser, :buf)
|
15
12
|
|
16
13
|
def initialize(*args)
|
data/local.mk.sample
CHANGED
@@ -38,7 +38,7 @@ publish_doc:
|
|
38
38
|
# Create gzip variants of the same timestamp as the original so nginx
|
39
39
|
# "gzip_static on" can serve the gzipped versions directly.
|
40
40
|
doc_gz: suf := html js css
|
41
|
-
doc_gz: docs = $(shell find doc/ -regex '^.*\.\(html\|js\|css\)$$')
|
41
|
+
doc_gz: docs = $(shell find doc/ -regex '^.*\.\(html\|js\|css\)$$') doc/COPYING
|
42
42
|
doc_gz:
|
43
43
|
for i in $(docs); do \
|
44
44
|
gzip --rsyncable < $$i > $$i.gz; touch -r $$i $$i.gz; done
|
data/test/exec/test_exec.rb
CHANGED
@@ -604,6 +604,26 @@ end
|
|
604
604
|
reexec_usr2_quit_test(new_pid, pid_file)
|
605
605
|
end
|
606
606
|
|
607
|
+
def test_daemonize_redirect_fail
|
608
|
+
pid_file = "#{@tmpdir}/test.pid"
|
609
|
+
log = Tempfile.new('unicorn_test_log')
|
610
|
+
ucfg = Tempfile.new('unicorn_test_config')
|
611
|
+
ucfg.syswrite("pid #{pid_file}\"\n")
|
612
|
+
err = Tempfile.new('stderr')
|
613
|
+
out = Tempfile.new('stdout ')
|
614
|
+
|
615
|
+
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
616
|
+
pid = xfork do
|
617
|
+
$stderr.reopen(err.path, "a")
|
618
|
+
$stdout.reopen(out.path, "a")
|
619
|
+
exec($unicorn_bin, "-D", "-l#{@addr}:#{@port}", "-c#{ucfg.path}")
|
620
|
+
end
|
621
|
+
pid, status = Process.waitpid2(pid)
|
622
|
+
assert status.success?, "original process exited successfully"
|
623
|
+
sleep 1 # can't waitpid on a daemonized process :<
|
624
|
+
assert err.stat.size > 0
|
625
|
+
end
|
626
|
+
|
607
627
|
def test_reexec_fd_leak
|
608
628
|
unless RUBY_PLATFORM =~ /linux/ # Solaris may work, too, but I forget...
|
609
629
|
warn "FD leak test only works on Linux at the moment"
|
@@ -134,15 +134,84 @@ class HttpParserTest < Test::Unit::TestCase
|
|
134
134
|
assert_equal req, parser.headers(req, should_be_good)
|
135
135
|
assert_equal '', should_be_good
|
136
136
|
assert parser.keepalive?
|
137
|
+
end
|
138
|
+
|
139
|
+
# legacy test case from Mongrel that we never supported before...
|
140
|
+
# I still consider Pound irrelevant, unfortunately stupid clients that
|
141
|
+
# send extremely big headers do exist and they've managed to find Unicorn...
|
142
|
+
def test_nasty_pound_header
|
143
|
+
parser = HttpParser.new
|
144
|
+
nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
|
145
|
+
req = {}
|
146
|
+
buf = nasty_pound_header.dup
|
147
|
+
|
148
|
+
assert nasty_pound_header =~ /(-----BEGIN .*--END CERTIFICATE-----)/m
|
149
|
+
expect = $1.dup
|
150
|
+
expect.gsub!(/\r\n\t/, ' ')
|
151
|
+
assert_equal req, parser.headers(req, buf)
|
152
|
+
assert_equal '', buf
|
153
|
+
assert_equal expect, req['HTTP_X_SSL_BULLSHIT']
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_continuation_eats_leading_spaces
|
157
|
+
parser = HttpParser.new
|
158
|
+
header = "GET / HTTP/1.1\r\n" \
|
159
|
+
"X-ASDF: \r\n" \
|
160
|
+
"\t\r\n" \
|
161
|
+
" \r\n" \
|
162
|
+
" ASDF\r\n\r\n"
|
163
|
+
req = {}
|
164
|
+
assert_equal req, parser.headers(req, header)
|
165
|
+
assert_equal '', header
|
166
|
+
assert_equal 'ASDF', req['HTTP_X_ASDF']
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_continuation_eats_scattered_leading_spaces
|
170
|
+
parser = HttpParser.new
|
171
|
+
header = "GET / HTTP/1.1\r\n" \
|
172
|
+
"X-ASDF: hi\r\n" \
|
173
|
+
" y\r\n" \
|
174
|
+
"\t\r\n" \
|
175
|
+
" x\r\n" \
|
176
|
+
" ASDF\r\n\r\n"
|
177
|
+
req = {}
|
178
|
+
assert_equal req, parser.headers(req, header)
|
179
|
+
assert_equal '', header
|
180
|
+
assert_equal 'hi y x ASDF', req['HTTP_X_ASDF']
|
181
|
+
end
|
182
|
+
|
183
|
+
# this may seem to be testing more of an implementation detail, but
|
184
|
+
# it also helps ensure we're safe in the presence of multiple parsers
|
185
|
+
# in case we ever go multithreaded/evented...
|
186
|
+
def test_resumable_continuations
|
187
|
+
nr = 1000
|
188
|
+
req = {}
|
189
|
+
header = "GET / HTTP/1.1\r\n" \
|
190
|
+
"X-ASDF: \r\n" \
|
191
|
+
" hello\r\n"
|
192
|
+
tmp = []
|
193
|
+
nr.times { |i|
|
194
|
+
parser = HttpParser.new
|
195
|
+
assert parser.headers(req, "#{header} #{i}\r\n").nil?
|
196
|
+
asdf = req['HTTP_X_ASDF']
|
197
|
+
assert_equal "hello #{i}", asdf
|
198
|
+
tmp << [ parser, asdf ]
|
199
|
+
req.clear
|
200
|
+
}
|
201
|
+
tmp.each_with_index { |(parser, asdf), i|
|
202
|
+
assert_equal req, parser.headers(req, "#{header} #{i}\r\n .\r\n\r\n")
|
203
|
+
assert_equal "hello #{i} .", asdf
|
204
|
+
}
|
205
|
+
end
|
137
206
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
207
|
+
def test_invalid_continuation
|
208
|
+
parser = HttpParser.new
|
209
|
+
header = "GET / HTTP/1.1\r\n" \
|
210
|
+
" y\r\n" \
|
211
|
+
"Host: hello\r\n" \
|
212
|
+
"\r\n"
|
213
|
+
req = {}
|
214
|
+
assert_raises(HttpParserError) { parser.headers(req, header) }
|
146
215
|
end
|
147
216
|
|
148
217
|
def test_parse_ie6_urls
|
@@ -191,7 +260,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
191
260
|
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
192
261
|
assert_nil parser.headers(req, http << "\r")
|
193
262
|
assert_equal req, parser.headers(req, http << "\n")
|
194
|
-
assert_equal 'HTTP/1.
|
263
|
+
assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
|
195
264
|
assert_nil req['FRAGMENT']
|
196
265
|
assert_equal '', req['QUERY_STRING']
|
197
266
|
assert_equal "", http
|
@@ -298,7 +367,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
298
367
|
assert_equal '/', req['REQUEST_URI']
|
299
368
|
assert_equal 'PUT', req['REQUEST_METHOD']
|
300
369
|
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
301
|
-
assert_equal 'HTTP/1.
|
370
|
+
assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
|
302
371
|
assert_equal "abcde", http
|
303
372
|
assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
|
304
373
|
end
|
@@ -312,7 +381,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
312
381
|
assert_equal '/l', req['REQUEST_URI']
|
313
382
|
assert_equal 'PUT', req['REQUEST_METHOD']
|
314
383
|
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
315
|
-
assert_equal 'HTTP/1.
|
384
|
+
assert_equal 'HTTP/1.0', req['SERVER_PROTOCOL']
|
316
385
|
assert_equal "", http
|
317
386
|
assert ! parser.keepalive? # TODO: read HTTP/1.2 when it's final
|
318
387
|
end
|
@@ -21,6 +21,7 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
21
21
|
assert_equal '123', req['CONTENT_LENGTH']
|
22
22
|
assert_equal 0, str.size
|
23
23
|
assert ! @parser.keepalive?
|
24
|
+
assert @parser.headers?
|
24
25
|
end
|
25
26
|
|
26
27
|
def test_identity_oneshot_header
|
@@ -281,4 +282,25 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
281
282
|
assert ! @parser.keepalive?
|
282
283
|
end
|
283
284
|
|
285
|
+
def test_parse_simple_request
|
286
|
+
parser = HttpParser.new
|
287
|
+
req = {}
|
288
|
+
http = "GET /read-rfc1945-if-you-dont-believe-me\r\n"
|
289
|
+
assert_equal req, parser.headers(req, http)
|
290
|
+
assert_equal '', http
|
291
|
+
expect = {
|
292
|
+
"SERVER_NAME"=>"localhost",
|
293
|
+
"rack.url_scheme"=>"http",
|
294
|
+
"REQUEST_PATH"=>"/read-rfc1945-if-you-dont-believe-me",
|
295
|
+
"PATH_INFO"=>"/read-rfc1945-if-you-dont-believe-me",
|
296
|
+
"REQUEST_URI"=>"/read-rfc1945-if-you-dont-believe-me",
|
297
|
+
"SERVER_PORT"=>"80",
|
298
|
+
"SERVER_PROTOCOL"=>"HTTP/0.9",
|
299
|
+
"REQUEST_METHOD"=>"GET",
|
300
|
+
"QUERY_STRING"=>""
|
301
|
+
}
|
302
|
+
assert_equal expect, req
|
303
|
+
assert ! parser.headers?
|
304
|
+
end
|
305
|
+
|
284
306
|
end
|
data/test/unit/test_server.rb
CHANGED
@@ -148,6 +148,16 @@ class WebServerTest < Test::Unit::TestCase
|
|
148
148
|
assert_nothing_raised { sock.close }
|
149
149
|
end
|
150
150
|
|
151
|
+
def test_http_0_9
|
152
|
+
sock = nil
|
153
|
+
assert_nothing_raised do
|
154
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
155
|
+
sock.syswrite("GET /hello\r\n")
|
156
|
+
end
|
157
|
+
assert_match 'hello!\n', sock.sysread(4096)
|
158
|
+
assert_nothing_raised { sock.close }
|
159
|
+
end
|
160
|
+
|
151
161
|
def test_header_is_too_long
|
152
162
|
redirect_test_io do
|
153
163
|
long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
|
data/unicorn.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{unicorn}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.91.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Eric Wong"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-09-04}
|
10
10
|
s.description = %q{Rack HTTP server for Unix, fast clients and nothing else}
|
11
11
|
s.email = %q{normalperson@yhbt.net}
|
12
12
|
s.executables = ["unicorn", "unicorn_rails"]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.91.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Wong
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-09-04 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|