mongrel 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/README +10 -2
  2. data/Rakefile +1 -1
  3. data/doc/rdoc/classes/Mongrel.html +11 -1
  4. data/doc/rdoc/classes/Mongrel/Const.html +342 -0
  5. data/doc/rdoc/classes/Mongrel/DirHandler.html +201 -0
  6. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000008.html +20 -0
  7. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000009.html +31 -0
  8. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000010.html +22 -0
  9. data/doc/rdoc/classes/Mongrel/DirHandler.src/M000011.html +39 -0
  10. data/doc/rdoc/classes/Mongrel/Error404Handler.html +10 -10
  11. data/doc/rdoc/classes/Mongrel/Error404Handler.src/{M000023.html → M000028.html} +4 -4
  12. data/doc/rdoc/classes/Mongrel/Error404Handler.src/{M000024.html → M000029.html} +4 -4
  13. data/doc/rdoc/classes/Mongrel/HeaderOut.html +28 -10
  14. data/doc/rdoc/classes/Mongrel/HeaderOut.src/{M000013.html → M000017.html} +4 -4
  15. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000018.html +21 -0
  16. data/doc/rdoc/classes/Mongrel/HttpHandler.html +5 -18
  17. data/doc/rdoc/classes/Mongrel/HttpHandler.src/{M000019.html → M000023.html} +3 -3
  18. data/doc/rdoc/classes/Mongrel/HttpRequest.html +8 -8
  19. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000030.html +31 -0
  20. data/doc/rdoc/classes/Mongrel/HttpResponse.html +89 -15
  21. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000020.html → M000024.html} +7 -7
  22. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000021.html → M000025.html} +6 -6
  23. data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000026.html +19 -0
  24. data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000022.html → M000027.html} +11 -11
  25. data/doc/rdoc/classes/Mongrel/HttpServer.html +32 -76
  26. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000012.html +18 -5
  27. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000013.html +64 -0
  28. data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000010.html → M000014.html} +9 -8
  29. data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000011.html → M000015.html} +4 -4
  30. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000016.html +18 -0
  31. data/doc/rdoc/classes/Mongrel/URIClassifier.html +25 -23
  32. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000015.html → M000019.html} +0 -0
  33. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000016.html → M000020.html} +3 -2
  34. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000017.html → M000021.html} +0 -0
  35. data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000018.html → M000022.html} +1 -1
  36. data/doc/rdoc/created.rid +1 -1
  37. data/doc/rdoc/files/README.html +14 -2
  38. data/doc/rdoc/files/ext/http11/http11_c.html +1 -1
  39. data/doc/rdoc/files/lib/mongrel_rb.html +1 -1
  40. data/doc/rdoc/fr_class_index.html +2 -0
  41. data/doc/rdoc/fr_method_index.html +23 -18
  42. data/examples/simpletest.rb +2 -1
  43. data/ext/http11/http11.c +10 -9
  44. data/ext/http11/http11_parser.c +10 -10
  45. data/ext/http11/http11_parser.h +5 -1
  46. data/lib/#mongrel.rb# +493 -0
  47. data/lib/mongrel.rb +242 -48
  48. metadata +28 -19
  49. data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000014.html +0 -21
  50. data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000025.html +0 -30
  51. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000008.html +0 -26
  52. data/doc/rdoc/classes/Mongrel/HttpServer.src/M000009.html +0 -58
@@ -16,8 +16,9 @@
16
16
  *
17
17
  * Registers the SampleHandler (one for all requests) with the "/someuri".
18
18
  * When URIClassifier::resolve is called with "/someuri" it'll return
19
- * SampleHandler immediately. When "/someuri/pathhere" is called it'll
20
- * find SomeHandler after a second search, and setup PATH_INFO="/pathhere".
19
+ * SampleHandler immediately. When called with "/someuri/iwant" it'll also
20
+ * return SomeHandler immediatly, with no additional searches, but it will
21
+ * return path info with "/iwant".
21
22
  *
22
23
  * You actually can reuse this class to register nearly anything and
23
24
  * quickly resolve it. This could be used for caching, fast mapping, etc.
@@ -32,7 +32,7 @@
32
32
  * It also means that it's very efficient to do this only taking as long as the URI has
33
33
  * characters.
34
34
  *
35
- * It expects strings. Don't try other string-line stuff yet.
35
+ * It expects strings with no embedded '\0' characters. Don't try other string-line stuff yet.
36
36
  */
37
37
  VALUE URIClassifier_resolve(VALUE self, VALUE uri)
38
38
  {
@@ -1 +1 @@
1
- Thu Feb 02 01:57:10 EST 2006
1
+ Fri Feb 03 01:14:07 EST 2006
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Thu Feb 02 01:53:46 EST 2006</td>
59
+ <td>Fri Feb 03 01:13:51 EST 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -84,7 +84,7 @@ portability issues.
84
84
  </p>
85
85
  <h2>Status</h2>
86
86
  <p>
87
- The 0.2.1 release of <a href="../classes/Mongrel.html">Mongrel</a> features
87
+ The 0.2.2 release of <a href="../classes/Mongrel.html">Mongrel</a> features
88
88
  an HTTP core server that is the fastest possible thing I could get without
89
89
  using something other than Ruby. It features a few bug fixes, but mostly
90
90
  just a change to the <a
@@ -111,6 +111,12 @@ thanks to Tom Copland. I&#8217;ll be looking to automate management of
111
111
  this, but feel free to use rubyforge to post feature requests, bugs, and
112
112
  join the mailing list.
113
113
  </p>
114
+ <p>
115
+ Finally, it now supports all CGI parameters that don&#8217;t cause a
116
+ performance hit, and it has a <a
117
+ href="../classes/Mongrel/DirHandler.html">Mongrel::DirHandler</a> which can
118
+ serve files out of a directory and do (optional) directory listings.
119
+ </p>
114
120
  <h2>Install</h2>
115
121
  <p>
116
122
  It doesn&#8217;t explicitly require Camping, but if you want to run the
@@ -143,6 +149,7 @@ example:
143
149
 
144
150
  h = Mongrel::HttpServer.new(&quot;0.0.0.0&quot;, &quot;3000&quot;)
145
151
  h.register(&quot;/test&quot;, SimpleHandler.new)
152
+ h.register(&quot;/files&quot;, DirHandler.new(&quot;.&quot;))
146
153
  h.run.join
147
154
  </pre>
148
155
  <p>
@@ -152,6 +159,11 @@ If you run this and access port 3000 with a browser it will say
152
159
  href="../classes/Mongrel/Error404Handler.html">Mongrel::Error404Handler</a>
153
160
  for a basic way to give a more complex 404 message.
154
161
  </p>
162
+ <p>
163
+ This also shows the DirHandler with directory listings. This is still rough
164
+ but it should work for basic hosting. *File extension to mime type mapping
165
+ is missing though.*
166
+ </p>
155
167
  <h2>Speed</h2>
156
168
  <p>
157
169
  The 0.2.1 release probably consists of the most effort I&#8217;ve ever put
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Thu Feb 02 00:12:35 EST 2006</td>
59
+ <td>Fri Feb 03 00:55:27 EST 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Mon Jan 30 23:59:49 EST 2006</td>
59
+ <td>Fri Feb 03 00:55:27 EST 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -21,6 +21,8 @@
21
21
  <h1 class="section-bar">Classes</h1>
22
22
  <div id="index-entries">
23
23
  <a href="classes/Mongrel.html">Mongrel</a><br />
24
+ <a href="classes/Mongrel/Const.html">Mongrel::Const</a><br />
25
+ <a href="classes/Mongrel/DirHandler.html">Mongrel::DirHandler</a><br />
24
26
  <a href="classes/Mongrel/Error404Handler.html">Mongrel::Error404Handler</a><br />
25
27
  <a href="classes/Mongrel/HeaderOut.html">Mongrel::HeaderOut</a><br />
26
28
  <a href="classes/Mongrel/HttpHandler.html">Mongrel::HttpHandler</a><br />
@@ -20,31 +20,36 @@
20
20
  <div id="index">
21
21
  <h1 class="section-bar">Methods</h1>
22
22
  <div id="index-entries">
23
- <a href="classes/Mongrel/HeaderOut.html#M000014">[]= (Mongrel::HeaderOut)</a><br />
23
+ <a href="classes/Mongrel/HeaderOut.html#M000018">[]= (Mongrel::HeaderOut)</a><br />
24
24
  <a href="classes/Mongrel/HttpParser.html#M000005">error? (Mongrel::HttpParser)</a><br />
25
25
  <a href="classes/Mongrel/HttpParser.html#M000004">execute (Mongrel::HttpParser)</a><br />
26
26
  <a href="classes/Mongrel/HttpParser.html#M000003">finish (Mongrel::HttpParser)</a><br />
27
- <a href="classes/Mongrel/HttpResponse.html#M000022">finished (Mongrel::HttpResponse)</a><br />
27
+ <a href="classes/Mongrel/HttpResponse.html#M000027">finished (Mongrel::HttpResponse)</a><br />
28
28
  <a href="classes/Mongrel/HttpParser.html#M000006">finished? (Mongrel::HttpParser)</a><br />
29
- <a href="classes/Mongrel/Error404Handler.html#M000023">new (Mongrel::Error404Handler)</a><br />
30
- <a href="classes/Mongrel/HttpServer.html#M000008">new (Mongrel::HttpServer)</a><br />
31
- <a href="classes/Mongrel/HttpRequest.html#M000025">new (Mongrel::HttpRequest)</a><br />
32
- <a href="classes/Mongrel/HttpResponse.html#M000020">new (Mongrel::HttpResponse)</a><br />
33
- <a href="classes/Mongrel/URIClassifier.html#M000015">new (Mongrel::URIClassifier)</a><br />
34
29
  <a href="classes/Mongrel/HttpParser.html#M000001">new (Mongrel::HttpParser)</a><br />
35
- <a href="classes/Mongrel/HeaderOut.html#M000013">new (Mongrel::HeaderOut)</a><br />
30
+ <a href="classes/Mongrel/DirHandler.html#M000008">new (Mongrel::DirHandler)</a><br />
31
+ <a href="classes/Mongrel/HttpResponse.html#M000024">new (Mongrel::HttpResponse)</a><br />
32
+ <a href="classes/Mongrel/URIClassifier.html#M000019">new (Mongrel::URIClassifier)</a><br />
33
+ <a href="classes/Mongrel/Error404Handler.html#M000028">new (Mongrel::Error404Handler)</a><br />
34
+ <a href="classes/Mongrel/HttpServer.html#M000012">new (Mongrel::HttpServer)</a><br />
35
+ <a href="classes/Mongrel/HeaderOut.html#M000017">new (Mongrel::HeaderOut)</a><br />
36
+ <a href="classes/Mongrel/HttpRequest.html#M000030">new (Mongrel::HttpRequest)</a><br />
36
37
  <a href="classes/Mongrel/HttpParser.html#M000007">nread (Mongrel::HttpParser)</a><br />
37
- <a href="classes/Mongrel/Error404Handler.html#M000024">process (Mongrel::Error404Handler)</a><br />
38
- <a href="classes/Mongrel/HttpHandler.html#M000019">process (Mongrel::HttpHandler)</a><br />
39
- <a href="classes/Mongrel/HttpServer.html#M000009">process_client (Mongrel::HttpServer)</a><br />
40
- <a href="classes/Mongrel/HttpServer.html#M000011">register (Mongrel::HttpServer)</a><br />
41
- <a href="classes/Mongrel/URIClassifier.html#M000016">register (Mongrel::URIClassifier)</a><br />
38
+ <a href="classes/Mongrel/Error404Handler.html#M000029">process (Mongrel::Error404Handler)</a><br />
39
+ <a href="classes/Mongrel/DirHandler.html#M000011">process (Mongrel::DirHandler)</a><br />
40
+ <a href="classes/Mongrel/HttpHandler.html#M000023">process (Mongrel::HttpHandler)</a><br />
41
+ <a href="classes/Mongrel/HttpServer.html#M000013">process_client (Mongrel::HttpServer)</a><br />
42
+ <a href="classes/Mongrel/HttpServer.html#M000015">register (Mongrel::HttpServer)</a><br />
43
+ <a href="classes/Mongrel/URIClassifier.html#M000020">register (Mongrel::URIClassifier)</a><br />
44
+ <a href="classes/Mongrel/HttpResponse.html#M000026">reset (Mongrel::HttpResponse)</a><br />
42
45
  <a href="classes/Mongrel/HttpParser.html#M000002">reset (Mongrel::HttpParser)</a><br />
43
- <a href="classes/Mongrel/URIClassifier.html#M000018">resolve (Mongrel::URIClassifier)</a><br />
44
- <a href="classes/Mongrel/HttpServer.html#M000010">run (Mongrel::HttpServer)</a><br />
45
- <a href="classes/Mongrel/HttpResponse.html#M000021">start (Mongrel::HttpResponse)</a><br />
46
- <a href="classes/Mongrel/URIClassifier.html#M000017">unregister (Mongrel::URIClassifier)</a><br />
47
- <a href="classes/Mongrel/HttpServer.html#M000012">unregister (Mongrel::HttpServer)</a><br />
46
+ <a href="classes/Mongrel/URIClassifier.html#M000022">resolve (Mongrel::URIClassifier)</a><br />
47
+ <a href="classes/Mongrel/HttpServer.html#M000014">run (Mongrel::HttpServer)</a><br />
48
+ <a href="classes/Mongrel/DirHandler.html#M000009">send_dir_listing (Mongrel::DirHandler)</a><br />
49
+ <a href="classes/Mongrel/DirHandler.html#M000010">send_file (Mongrel::DirHandler)</a><br />
50
+ <a href="classes/Mongrel/HttpResponse.html#M000025">start (Mongrel::HttpResponse)</a><br />
51
+ <a href="classes/Mongrel/URIClassifier.html#M000021">unregister (Mongrel::URIClassifier)</a><br />
52
+ <a href="classes/Mongrel/HttpServer.html#M000016">unregister (Mongrel::HttpServer)</a><br />
48
53
  </div>
49
54
  </div>
50
55
  </body>
@@ -11,6 +11,7 @@ class SimpleHandler < Mongrel::HttpHandler
11
11
  end
12
12
 
13
13
  h = Mongrel::HttpServer.new("0.0.0.0", "3000")
14
- h.register("/test", SimpleHandler.new)
14
+ h.register("/test;mystuff;thosestuffs", SimpleHandler.new)
15
+ h.register("/files", Mongrel::DirHandler.new("."))
15
16
  h.run.join
16
17
 
@@ -13,7 +13,7 @@ static int id_handler_map;
13
13
 
14
14
  static VALUE global_http_prefix;
15
15
  static VALUE global_request_method;
16
- static VALUE global_path_info;
16
+ static VALUE global_request_uri;
17
17
  static VALUE global_query_string;
18
18
  static VALUE global_http_version;
19
19
 
@@ -45,11 +45,11 @@ void request_method(void *data, const char *at, size_t length)
45
45
  rb_hash_aset(req, global_request_method, val);
46
46
  }
47
47
 
48
- void path_info(void *data, const char *at, size_t length)
48
+ void request_uri(void *data, const char *at, size_t length)
49
49
  {
50
50
  VALUE req = (VALUE)data;
51
51
  VALUE val = rb_str_new(at, length);
52
- rb_hash_aset(req, global_path_info, val);
52
+ rb_hash_aset(req, global_request_uri, val);
53
53
  }
54
54
 
55
55
 
@@ -87,7 +87,7 @@ VALUE HttpParser_alloc(VALUE klass)
87
87
  TRACE();
88
88
  hp->http_field = http_field;
89
89
  hp->request_method = request_method;
90
- hp->path_info = path_info;
90
+ hp->request_uri = request_uri;
91
91
  hp->query_string = query_string;
92
92
  hp->http_version = http_version;
93
93
 
@@ -279,8 +279,9 @@ VALUE URIClassifier_init(VALUE self)
279
279
  *
280
280
  * Registers the SampleHandler (one for all requests) with the "/someuri".
281
281
  * When URIClassifier::resolve is called with "/someuri" it'll return
282
- * SampleHandler immediately. When "/someuri/pathhere" is called it'll
283
- * find SomeHandler after a second search, and setup PATH_INFO="/pathhere".
282
+ * SampleHandler immediately. When called with "/someuri/iwant" it'll also
283
+ * return SomeHandler immediatly, with no additional searches, but it will
284
+ * return path info with "/iwant".
284
285
  *
285
286
  * You actually can reuse this class to register nearly anything and
286
287
  * quickly resolve it. This could be used for caching, fast mapping, etc.
@@ -357,7 +358,7 @@ VALUE URIClassifier_unregister(VALUE self, VALUE uri)
357
358
  * It also means that it's very efficient to do this only taking as long as the URI has
358
359
  * characters.
359
360
  *
360
- * It expects strings. Don't try other string-line stuff yet.
361
+ * It expects strings with no embedded '\0' characters. Don't try other string-line stuff yet.
361
362
  */
362
363
  VALUE URIClassifier_resolve(VALUE self, VALUE uri)
363
364
  {
@@ -401,8 +402,8 @@ void Init_http11()
401
402
  rb_global_variable(&global_http_prefix);
402
403
  global_request_method = rb_str_new2("REQUEST_METHOD");
403
404
  rb_global_variable(&global_request_method);
404
- global_path_info = rb_str_new2("PATH_INFO");
405
- rb_global_variable(&global_path_info);
405
+ global_request_uri = rb_str_new2("REQUEST_URI");
406
+ rb_global_variable(&global_request_uri);
406
407
  global_query_string = rb_str_new2("QUERY_STRING");
407
408
  rb_global_variable(&global_query_string);
408
409
  global_http_version = rb_str_new2("HTTP_VERSION");
@@ -9,7 +9,7 @@
9
9
  #define MARK(S,F) assert((F) - (S)->mark >= 0); (S)->mark = (F);
10
10
 
11
11
  /** machine **/
12
- #line 100 "ext/http11/http11_parser.rl"
12
+ #line 98 "ext/http11/http11_parser.rl"
13
13
 
14
14
 
15
15
  /** Data **/
@@ -21,7 +21,7 @@ static int http_parser_first_final = 56;
21
21
 
22
22
  static int http_parser_error = 1;
23
23
 
24
- #line 104 "ext/http11/http11_parser.rl"
24
+ #line 102 "ext/http11/http11_parser.rl"
25
25
 
26
26
  int http_parser_init(http_parser *parser) {
27
27
  int cs = 0;
@@ -30,7 +30,7 @@ int http_parser_init(http_parser *parser) {
30
30
  {
31
31
  cs = http_parser_start;
32
32
  }
33
- #line 108 "ext/http11/http11_parser.rl"
33
+ #line 106 "ext/http11/http11_parser.rl"
34
34
  parser->cs = cs;
35
35
  parser->body_start = NULL;
36
36
  parser->content_len = 0;
@@ -158,8 +158,8 @@ case 9:
158
158
  tr34:
159
159
  #line 33 "ext/http11/http11_parser.rl"
160
160
  {
161
- if(parser->path_info != NULL)
162
- parser->path_info(parser->data, parser->mark, p - parser->mark);
161
+ if(parser->request_uri != NULL)
162
+ parser->request_uri(parser->data, parser->mark, p - parser->mark);
163
163
  }
164
164
  goto st10;
165
165
  tr48:
@@ -601,8 +601,8 @@ case 35:
601
601
  tr46:
602
602
  #line 33 "ext/http11/http11_parser.rl"
603
603
  {
604
- if(parser->path_info != NULL)
605
- parser->path_info(parser->data, parser->mark, p - parser->mark);
604
+ if(parser->request_uri != NULL)
605
+ parser->request_uri(parser->data, parser->mark, p - parser->mark);
606
606
  }
607
607
  goto st36;
608
608
  st36:
@@ -875,7 +875,7 @@ case 55:
875
875
 
876
876
  _out: {}
877
877
  }
878
- #line 127 "ext/http11/http11_parser.rl"
878
+ #line 125 "ext/http11/http11_parser.rl"
879
879
 
880
880
  parser->cs = cs;
881
881
  parser->nread = p - buffer;
@@ -883,7 +883,7 @@ case 55:
883
883
  /* final \r\n combo encountered so stop right here */
884
884
 
885
885
  #line 886 "ext/http11/http11_parser.c"
886
- #line 133 "ext/http11/http11_parser.rl"
886
+ #line 131 "ext/http11/http11_parser.rl"
887
887
  parser->nread++;
888
888
  }
889
889
 
@@ -896,7 +896,7 @@ int http_parser_finish(http_parser *parser)
896
896
 
897
897
 
898
898
  #line 899 "ext/http11/http11_parser.c"
899
- #line 144 "ext/http11/http11_parser.rl"
899
+ #line 142 "ext/http11/http11_parser.rl"
900
900
 
901
901
  parser->cs = cs;
902
902
 
@@ -3,6 +3,10 @@
3
3
 
4
4
  #include <sys/types.h>
5
5
 
6
+ #if defined(_WIN32)
7
+ #include <stddef.h>
8
+ #endif
9
+
6
10
  typedef void (*element_cb)(void *data, const char *at, size_t length);
7
11
  typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
8
12
 
@@ -19,7 +23,7 @@ typedef struct http_parser {
19
23
 
20
24
  field_cb http_field;
21
25
  element_cb request_method;
22
- element_cb path_info;
26
+ element_cb request_uri;
23
27
  element_cb query_string;
24
28
  element_cb http_version;
25
29
 
@@ -0,0 +1,493 @@
1
+ require 'socket'
2
+ require 'http11'
3
+ require 'thread'
4
+ require 'stringio'
5
+
6
+ # Mongrel module containing all of the classes (include C extensions) for running
7
+ # a Mongrel web server. It contains a minimalist HTTP server with just enough
8
+ # functionality to service web application requests fast as possible.
9
+ module Mongrel
10
+
11
+ # Every standard HTTP code mapped to the appropriate message. These are
12
+ # used so frequently that they are placed directly in Mongrel for easy
13
+ # access rather than Mongrel::Const.
14
+ HTTP_STATUS_CODES = {
15
+ 100 => 'Continue',
16
+ 101 => 'Switching Protocols',
17
+ 200 => 'OK',
18
+ 201 => 'Created',
19
+ 202 => 'Accepted',
20
+ 203 => 'Non-Authoritative Information',
21
+ 204 => 'No Content',
22
+ 205 => 'Reset Content',
23
+ 206 => 'Partial Content',
24
+ 300 => 'Multiple Choices',
25
+ 301 => 'Moved Permanently',
26
+ 302 => 'Moved Temporarily',
27
+ 303 => 'See Other',
28
+ 304 => 'Not Modified',
29
+ 305 => 'Use Proxy',
30
+ 400 => 'Bad Request',
31
+ 401 => 'Unauthorized',
32
+ 402 => 'Payment Required',
33
+ 403 => 'Forbidden',
34
+ 404 => 'Not Found',
35
+ 405 => 'Method Not Allowed',
36
+ 406 => 'Not Acceptable',
37
+ 407 => 'Proxy Authentication Required',
38
+ 408 => 'Request Time-out',
39
+ 409 => 'Conflict',
40
+ 410 => 'Gone',
41
+ 411 => 'Length Required',
42
+ 412 => 'Precondition Failed',
43
+ 413 => 'Request Entity Too Large',
44
+ 414 => 'Request-URI Too Large',
45
+ 415 => 'Unsupported Media Type',
46
+ 500 => 'Internal Server Error',
47
+ 501 => 'Not Implemented',
48
+ 502 => 'Bad Gateway',
49
+ 503 => 'Service Unavailable',
50
+ 504 => 'Gateway Time-out',
51
+ 505 => 'HTTP Version not supported'
52
+ }
53
+
54
+ # Frequently used constants when constructing requests or responses. Many times
55
+ # the constant just refers to a string with the same contents. Using these constants
56
+ # gave about a 3% to 10% performance improvement over using the strings directly.
57
+ # Symbols did not really improve things much compared to constants.
58
+ #
59
+ # While Mongrel does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT,
60
+ # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
61
+ # too taxing on performance.
62
+ module Const
63
+ # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this.
64
+ PATH_INFO="PATH_INFO"
65
+ # This is the intial part that your handler is identified as by URIClassifier.
66
+ SCRIPT_NAME="SCRIPT_NAME"
67
+ # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME.
68
+ REQUEST_URI='REQUEST_URI'
69
+
70
+ # Content length (also available as HTTP_CONTENT_LENGTH).
71
+ CONTENT_LENGTH='CONTENT_LENGTH'
72
+
73
+ # Content length (also available as CONTENT_LENGTH).
74
+ HTTP_CONTENT_LENGTH='HTTP_CONTENT_LENGTH'
75
+
76
+ # Content type (also available as HTTP_CONTENT_TYPE).
77
+ CONTENT_TYPE='CONTENT_TYPE'
78
+
79
+ # Content type (also available as CONTENT_TYPE).
80
+ HTTP_CONTENT_TYPE='HTTP_CONTENT_TYPE'
81
+
82
+ # Gateway interface key in the HttpRequest parameters.
83
+ GATEWAY_INTERFACE='GATEWAY_INTERFACE'
84
+ # We claim to support CGI/1.2.
85
+ GATEWAY_INTERFACE_VALUE='CGI/1.2'
86
+
87
+ # Hosts remote IP address. Mongrel does not do DNS resolves since that slows
88
+ # processing down considerably.
89
+ REMOTE_ADDR='REMOTE_ADDR'
90
+
91
+ # This is not given since Mongrel does not do DNS resolves. It is only here for
92
+ # completeness for the CGI standard.
93
+ REMOTE_HOST='REMOTE_HOST'
94
+
95
+ # The name/host of our server as given by the HttpServer.new(host,port) call.
96
+ SERVER_NAME='SERVER_NAME'
97
+
98
+ # The port of our server as given by the HttpServer.new(host,port) call.
99
+ SERVER_PORT='SERVER_PORT'
100
+
101
+ # Official server protocol key in the HttpRequest parameters.
102
+ SERVER_PROTOCOL='SERVER_PROTOCOL'
103
+ # Mongrel claims to support HTTP/1.1.
104
+ SERVER_PROTOCOL_VALUE='HTTP/1.1'
105
+
106
+ # The actual server software being used (it's Mongrel man).
107
+ SERVER_SOFTWARE='SERVER_SOFTWARE'
108
+
109
+ # Current Mongrel version (used for SERVER_SOFTWARE and other response headers).
110
+ MONGREL_VERSION='Mongrel 0.2.2'
111
+
112
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
113
+ ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND"
114
+
115
+ # A common header for indicating the server is too busy. Not used yet.
116
+ ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY"
117
+
118
+ # The basic max request size we'll try to read.
119
+ CHUNK_SIZE=(16 * 1024)
120
+
121
+ end
122
+
123
+
124
+ # When a handler is found for a registered URI then this class is constructed
125
+ # and passed to your HttpHandler::process method. You should assume that
126
+ # *one* handler processes all requests. Included in the HttpReqeust is a
127
+ # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body
128
+ # which is a string containing the request body (raw for now).
129
+ #
130
+ # Mongrel really only supports small-ish request bodies right now since really
131
+ # huge ones have to be completely read off the wire and put into a string.
132
+ # Later there will be several options for efficiently handling large file
133
+ # uploads.
134
+ class HttpRequest
135
+ attr_reader :body, :params
136
+
137
+ # You don't really call this. It's made for you.
138
+ # Main thing it does is hook up the params, and store any remaining
139
+ # body data into the HttpRequest.body attribute.
140
+ def initialize(params, initial_body, socket)
141
+ @body = initial_body || ""
142
+ @params = params
143
+ @socket = socket
144
+
145
+ # fix up the CGI requirements
146
+ params[Const::CONTENT_LENGTH] = params[Const::HTTP_CONTENT_LENGTH] || 0
147
+ params[Const::CONTENT_TYPE] ||= params[Const::HTTP_CONTENT_TYPE]
148
+
149
+ # now, if the initial_body isn't long enough for the content length we have to fill it
150
+ # TODO: adapt for big ass stuff by writing to a temp file
151
+ clen = params[Const::HTTP_CONTENT_LENGTH].to_i
152
+ if @body.length < clen
153
+ @body << @socket.read(clen - @body.length)
154
+ end
155
+ end
156
+ end
157
+
158
+
159
+ # This class implements a simple way of constructing the HTTP headers dynamically
160
+ # via a Hash syntax. Think of it as a write-only Hash. Refer to HttpResponse for
161
+ # information on how this is used.
162
+ #
163
+ # One consequence of this write-only nature is that you can write multiple headers
164
+ # by just doing them twice (which is sometimes needed in HTTP), but that the normal
165
+ # semantics for Hash (where doing an insert replaces) is not there.
166
+ class HeaderOut
167
+ attr_reader :out
168
+
169
+ def initialize(out)
170
+ @out = out
171
+ end
172
+
173
+ # Simply writes "#{key}: #{value}" to an output buffer.
174
+ def[]=(key,value)
175
+ @out.write(key)
176
+ @out.write(": ")
177
+ @out.write(value)
178
+ @out.write("\r\n")
179
+ end
180
+ end
181
+
182
+ # Writes and controls your response to the client using the HTTP/1.1 specification.
183
+ # You use it by simply doing:
184
+ #
185
+ # response.start(200) do |head,out|
186
+ # head['Content-Type'] = 'text/plain'
187
+ # out.write("hello\n")
188
+ # end
189
+ #
190
+ # The parameter to start is the response code--which Mongrel will translate for you
191
+ # based on HTTP_STATUS_CODES. The head parameter is how you write custom headers.
192
+ # The out parameter is where you write your body. The default status code for
193
+ # HttpResponse.start is 200 so the above example is redundant.
194
+ #
195
+ # As you can see, it's just like using a Hash and as you do this it writes the proper
196
+ # header to the output on the fly. You can even intermix specifying headers and
197
+ # writing content. The HttpResponse class with write the things in the proper order
198
+ # once the HttpResponse.block is ended.
199
+ #
200
+ # You may also work the HttpResponse object directly using the various attributes available
201
+ # for the raw socket, body, header, and status codes. If you do this you're on your own.
202
+ # A design decision was made to force the client to not pipeline requests. HTTP/1.1
203
+ # pipelining really kills the performance due to how it has to be handled and how
204
+ # unclear the standard is. To fix this the HttpResponse gives a "Connection: close"
205
+ # header which forces the client to close right away. The bonus for this is that it
206
+ # gives a pretty nice speed boost to most clients since they can close their connection
207
+ # immediately.
208
+ #
209
+ # One additional caveat is that you don't have to specify the Content-length header
210
+ # as the HttpResponse will write this for you based on the out length.
211
+ class HttpResponse
212
+ attr_reader :socket
213
+ attr_reader :body
214
+ attr_reader :header
215
+ attr_reader :status
216
+ attr_writer :status
217
+
218
+ def initialize(socket)
219
+ @socket = socket
220
+ @body = StringIO.new
221
+ @status = 404
222
+ @header = HeaderOut.new(StringIO.new)
223
+ end
224
+
225
+ # Receives a block passing it the header and body for you to work with.
226
+ # When the block is finished it writes everything you've done to
227
+ # the socket in the proper order. This lets you intermix header and
228
+ # body content as needed.
229
+ def start(status=200)
230
+ @status = status
231
+ yield @header, @body
232
+ finished
233
+ end
234
+
235
+ # Primarily used in exception handling to reset the response output in order to write
236
+ # an alternative response.
237
+ def reset
238
+ @header.out.rewind
239
+ @body.rewind
240
+ end
241
+
242
+ # This takes whatever has been done to header and body and then writes it in the
243
+ # proper format to make an HTTP/1.1 response.
244
+ def finished
245
+ @header.out.rewind
246
+ @body.rewind
247
+
248
+ # connection: close is also added to ensure that the client does not pipeline.
249
+ @socket.write("HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status]}\r\nContent-Length: #{@body.length}\r\nConnection: close\r\n")
250
+ @socket.write(@header.out.read)
251
+ @socket.write("\r\n")
252
+ @socket.write(@body.read)
253
+ end
254
+ end
255
+
256
+
257
+ # You implement your application handler with this. It's very light giving
258
+ # just the minimum necessary for you to handle a request and shoot back
259
+ # a response. Look at the HttpRequest and HttpResponse objects for how
260
+ # to use them.
261
+ class HttpHandler
262
+ def process(request, response)
263
+ end
264
+ end
265
+
266
+
267
+ # This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier
268
+ # make up the majority of how the server functions. It's a very simple class that just
269
+ # has a thread accepting connections and a simple HttpServer.process_client function
270
+ # to do the heavy lifting with the IO and Ruby.
271
+ #
272
+ # You use it by doing the following:
273
+ #
274
+ # server = HttpServer.new("0.0.0.0", 3000)
275
+ # server.register("/stuff", MyNifterHandler.new)
276
+ # server.run.join
277
+ #
278
+ # The last line can be just server.run if you don't want to join the thread used.
279
+ # If you don't though Ruby will mysteriously just exit on you.
280
+ #
281
+ # Ruby's thread implementation is "interesting" to say the least. Experiments with
282
+ # *many* different types of IO processing simply cannot make a dent in it. Future
283
+ # releases of Mongrel will find other creative ways to make threads faster, but don't
284
+ # hold your breath until Ruby 1.9 is actually finally useful.
285
+ class HttpServer
286
+ attr_reader :acceptor
287
+
288
+ # Creates a working server on host:port (strange things happen if port isn't a Number).
289
+ # Use HttpServer::run to start the server.
290
+ #
291
+ # The num_processors variable has varying affects on how requests are processed. You'd
292
+ # think adding more processing threads (processors) would make the server faster, but
293
+ # that's just not true. There's actually an effect of how Ruby does threads such that
294
+ # the more processors waiting on the request queue, the slower the system is to handle
295
+ # each request. But, the lower the number of processors the fewer concurrent responses
296
+ # the server can make.
297
+ #
298
+ # 20 is the default number of processors and is based on experimentation on a few
299
+ # systems. If you find that you overload Mongrel too much
300
+ # try changing it higher. If you find that responses are way too slow
301
+ # try lowering it (after you've tuned your stuff of course).
302
+ # Future versions of Mongrel will make this more dynamic (hopefully).
303
+ def initialize(host, port, num_processors=20)
304
+ @socket = TCPServer.new(host, port)
305
+
306
+ @classifier = URIClassifier.new
307
+ @req_queue = Queue.new
308
+ @host = host
309
+ @port = port
310
+ @num_procesors = num_processors
311
+
312
+ num_processors.times {|i| Thread.new do
313
+ while client = @req_queue.deq
314
+ process_client(client)
315
+ end
316
+ end
317
+ }
318
+ end
319
+
320
+
321
+ # Does the majority of the IO processing. It has been written in Ruby using
322
+ # about 7 different IO processing strategies and no matter how it's done
323
+ # the performance just does not improve. It is currently carefully constructed
324
+ # to make sure that it gets the best possible performance, but anyone who
325
+ # thinks they can make it faster is more than welcome to take a crack at it.
326
+ def process_client(client)
327
+ begin
328
+ parser = HttpParser.new
329
+ params = {}
330
+ data = client.readpartial(Const::CHUNK_SIZE)
331
+
332
+ while true
333
+ nread = parser.execute(params, data)
334
+ if parser.finished?
335
+ script_name, path_info, handler = @classifier.resolve(params[Const::REQUEST_URI])
336
+
337
+ if handler
338
+ params[Const::PATH_INFO] = path_info
339
+ params[Const::SCRIPT_NAME] = script_name
340
+ params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE
341
+ params[Const::REMOTE_ADDR]=client.peeraddr
342
+ params[Const::SERVER_NAME]=@host
343
+ params[Const::SERVER_PORT]=@port
344
+ params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE
345
+ params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION
346
+
347
+ request = HttpRequest.new(params, data[nread ... data.length], client)
348
+ response = HttpResponse.new(client)
349
+ handler.process(request, response)
350
+ else
351
+ client.write(Const::ERROR_404_RESPONSE)
352
+ end
353
+
354
+ break
355
+ else
356
+ # gotta stream and read again until we can get the parser to be character safe
357
+ # TODO: make this more efficient since this means we're parsing a lot repeatedly
358
+ parser.reset
359
+ data << client.readpartial(Const::CHUNK_SIZE)
360
+ end
361
+ end
362
+ rescue EOFError
363
+ # ignored
364
+ rescue Errno::ECONNRESET
365
+ # ignored
366
+ rescue Errno::EPIPE
367
+ # ignored
368
+ rescue => details
369
+ STDERR.puts "ERROR(#{details.class}): #{details}"
370
+ STDERR.puts details.backtrace.join("\n")
371
+ ensure
372
+ client.close
373
+ end
374
+ end
375
+
376
+ # Runs the thing. It returns the thread used so you can "join" it. You can also
377
+ # access the HttpServer::acceptor attribute to get the thread later.
378
+ def run
379
+ BasicSocket.do_not_reverse_lookup=true
380
+ @acceptor = Thread.new do
381
+ while true
382
+ @req_queue << @socket.accept
383
+ end
384
+ end
385
+ end
386
+
387
+
388
+ # Simply registers a handler with the internal URIClassifier. When the URI is
389
+ # found in the prefix of a request then your handler's HttpHandler::process method
390
+ # is called. See Mongrel::URIClassifier#register for more information.
391
+ def register(uri, handler)
392
+ @classifier.register(uri, handler)
393
+ end
394
+
395
+ # Removes any handler registered at the given URI. See Mongrel::URIClassifier#unregister
396
+ # for more information.
397
+ def unregister(uri)
398
+ @classifier.unregister(uri)
399
+ end
400
+ end
401
+
402
+
403
+ # The server normally returns a 404 response if a URI is requested, but it
404
+ # also returns a lame empty message. This lets you do a 404 response
405
+ # with a custom message for special URIs.
406
+ class Error404Handler < HttpHandler
407
+
408
+ # Sets the message to return. This is constructed once for the handler
409
+ # so it's pretty efficient.
410
+ def initialize(msg)
411
+ @response = HttpServer::ERROR_404_RESPONSE + msg
412
+ end
413
+
414
+ # Just kicks back the standard 404 response with your special message.
415
+ def process(request, response)
416
+ response.socket.write(@response)
417
+ end
418
+
419
+ end
420
+
421
+
422
+ # Serves the contents of a directory. You give it the path to the root
423
+ # where the files are located, and it tries to find the files based on
424
+ # the PATH_INFO inside the directory. If the requested path is a
425
+ # directory then it returns a simple directory listing.
426
+ #
427
+ # It does a simple protection against going outside it's root path by
428
+ # converting all paths to an absolute expanded path, and then making sure
429
+ # that the final expanded path includes the root path. If it doesn't
430
+ # than it simply gives a 404.
431
+ class DirHandler < HttpHandler
432
+
433
+ # You give it the path to the directory root and an (optional)
434
+ def initialize(path, listing_allowed=true)
435
+ @path = File.expand_path(path)
436
+ @listing_allowed=listing_allowed
437
+ puts "DIR: #@path"
438
+ end
439
+
440
+ def send_dir_listing(base, dir, response)
441
+ if @listing_allowed
442
+ response.start(200) do |head,out|
443
+ head['Content-Type'] = "text/html"
444
+ out << "<html><head><title>Directory Listing</title></head><body>"
445
+ Dir.entries(dir).each do |child|
446
+ out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
447
+ end
448
+ out << "</body></html>"
449
+ end
450
+ else
451
+ response.start(403) do |head,out|
452
+ out.write("Directory listings not allowed")
453
+ end
454
+ end
455
+ end
456
+
457
+
458
+ def send_file(req, response)
459
+ response.start(200) do |head,out|
460
+ open(req, "r") do |f|
461
+ out.write(f.read)
462
+ end
463
+ end
464
+ end
465
+
466
+
467
+ def process(request, response)
468
+ req = File.expand_path("." + request.params['PATH_INFO'], @path)
469
+ puts "FIND: #{req}"
470
+ if req.index(@path) != 0 or !File.exist? req
471
+ # not found, return a 404
472
+ response.start(404) do |head,out|
473
+ out << "File not found"
474
+ end
475
+ else
476
+ begin
477
+ if File.directory? req
478
+ send_dir_listing(request.params["REQUEST_URI"],req, response)
479
+ else
480
+ send_file(req, response)
481
+ end
482
+ rescue => details
483
+ response.reset
484
+ response.start(403) do |head,out|
485
+ out << "Error accessing file"
486
+ end
487
+ STDERR.puts "ERROR: #{details}"
488
+ end
489
+ end
490
+ end
491
+ end
492
+
493
+ end