unicorn 0.90.0 → 0.91.0
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.
- 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
|