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 CHANGED
@@ -3,13 +3,8 @@ TUNING
3
3
  PHILOSOPHY
4
4
  DESIGN
5
5
  CONTRIBUTORS
6
- COPYING
7
6
  LICENSE
8
7
  SIGNALS
9
8
  TODO
10
- bin/unicorn
11
- bin/unicorn_rails
12
9
  lib
13
- ext/**/*.c
14
- ext/**/*.rl
15
- ext/**/*.h
10
+ ext/unicorn_http/unicorn_http.c
data/CHANGELOG CHANGED
@@ -1,3 +1,4 @@
1
+ v0.91.0 - HTTP/0.9 support, multiline header support, small fixes
1
2
  v0.90.0 - switch chunking+trailer handling to Ragel, v0.8.4 fixes
2
3
  v0.9.2 - Ruby 1.9.2 preview1 compatibility
3
4
  v0.9.1 - FD_CLOEXEC portability fix (v0.8.2 port)
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 just like Mongrel:
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 -m README -t "$(shell sed -ne '1s/^= //p' README)"
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 (normalperson@yhbt.net)
2
- and contributors. You can redistribute it and/or modify it under either
3
- the terms of the GPL2 (see COPYING file) or the conditions below:
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
- software without restriction, provided that you duplicate all of the
7
- original copyright notices and associated disclaimers.
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
- you do at least ONE of the following:
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
- a) place your modifications in the Public Domain or otherwise make them
13
- Freely Available, such as by posting said modifications to Usenet or an
14
- equivalent medium, or by allowing the author to include your
15
- modifications in the software.
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
- b) use the modified software only within your corporation or
18
- organization.
19
+ b) use the modified software only within your corporation or
20
+ organization.
19
21
 
20
- c) rename any non-standard executables so the names do not conflict with
21
- standard executables, which must also be provided.
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
- d) make other distribution arrangements with the author.
25
+ d) make other distribution arrangements with the author.
24
26
 
25
- 3. You may distribute the software in object code or executable
26
- form, provided that you do at least ONE of the following:
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
- a) distribute the executables and library files of the software,
29
- together with instructions (in the manual page or equivalent) on where
30
- to get the original distribution.
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
- b) accompany the distribution with the machine-readable source of the
33
- software.
34
+ b) accompany the distribution with the machine-readable source of the
35
+ software.
34
36
 
35
- c) give non-standard executables non-standard names, with
36
- instructions on where to get the original software distribution.
37
+ c) give non-standard executables non-standard names, with
38
+ instructions on where to get the original software distribution.
37
39
 
38
- d) make other distribution arrangements with the author.
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
- software (possibly commercial). But some files in the distribution
42
- are not written by the author, so that they are not under this terms.
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
- output from the software do not automatically fall under the
46
- copyright of the software, but belong to whomever generated them,
47
- and may be sold commercially, and may be aggregated with this
48
- software.
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
- IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
52
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
53
- PURPOSE.
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, fast clients and nothing else
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
- rb_hash_aset(req, g_server_protocol, g_http_11);
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, NULL, NULL, hp);
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 -> parser
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 -> nil
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.headers(req, data) -> req or nil
404
- * parser.trailers(req, data) -> req or nil
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.filter_body(buf, data) -> nil/data
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::tmpdir+
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 = ( upper | digit | safe ){1,20} >mark %request_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
- message_header = field_name ":" " "* field_value :> CRLF;
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
- Request = Request_Line (message_header)* CRLF @header_done;
69
+ FullRequest = Request_Line (message_header)* CRLF @header_done;
70
+ SimpleRequest = GetOnly " " Request_URI ("#"Fragment){0,1} CRLF @header_done;
66
71
 
67
- main := Request;
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, 'a') { |fp| io.reopen(fp) } if 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.90.0".freeze
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
- # writes the rack_response to socket as an HTTP response
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
@@ -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
- $stdout.reopen("/dev/null", "a")
28
- $stderr.reopen("/dev/null", "a")
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
@@ -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
@@ -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
- # ref: http://thread.gmane.org/gmane.comp.lang.ruby.mongrel.devel/37/focus=45
139
- # (note we got 'pen' mixed up with 'pound' in that thread,
140
- # but the gist of it is still relevant: these nasty headers are irrelevant
141
- #
142
- # 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"
143
- # parser = HttpParser.new
144
- # req = {}
145
- # assert parser.execute(req, nasty_pound_header, 0)
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.1', req['SERVER_PROTOCOL']
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.1', req['SERVER_PROTOCOL']
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.1', req['SERVER_PROTOCOL']
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
@@ -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.90.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-08-16}
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.90.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-08-16 00:00:00 -07:00
12
+ date: 2009-09-04 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency