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.
- data/README +10 -2
- data/Rakefile +1 -1
- data/doc/rdoc/classes/Mongrel.html +11 -1
- data/doc/rdoc/classes/Mongrel/Const.html +342 -0
- data/doc/rdoc/classes/Mongrel/DirHandler.html +201 -0
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000008.html +20 -0
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000009.html +31 -0
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000010.html +22 -0
- data/doc/rdoc/classes/Mongrel/DirHandler.src/M000011.html +39 -0
- data/doc/rdoc/classes/Mongrel/Error404Handler.html +10 -10
- data/doc/rdoc/classes/Mongrel/Error404Handler.src/{M000023.html → M000028.html} +4 -4
- data/doc/rdoc/classes/Mongrel/Error404Handler.src/{M000024.html → M000029.html} +4 -4
- data/doc/rdoc/classes/Mongrel/HeaderOut.html +28 -10
- data/doc/rdoc/classes/Mongrel/HeaderOut.src/{M000013.html → M000017.html} +4 -4
- data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000018.html +21 -0
- data/doc/rdoc/classes/Mongrel/HttpHandler.html +5 -18
- data/doc/rdoc/classes/Mongrel/HttpHandler.src/{M000019.html → M000023.html} +3 -3
- data/doc/rdoc/classes/Mongrel/HttpRequest.html +8 -8
- data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000030.html +31 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.html +89 -15
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000020.html → M000024.html} +7 -7
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000021.html → M000025.html} +6 -6
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/M000026.html +19 -0
- data/doc/rdoc/classes/Mongrel/HttpResponse.src/{M000022.html → M000027.html} +11 -11
- data/doc/rdoc/classes/Mongrel/HttpServer.html +32 -76
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000012.html +18 -5
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000013.html +64 -0
- data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000010.html → M000014.html} +9 -8
- data/doc/rdoc/classes/Mongrel/HttpServer.src/{M000011.html → M000015.html} +4 -4
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000016.html +18 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.html +25 -23
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000015.html → M000019.html} +0 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000016.html → M000020.html} +3 -2
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000017.html → M000021.html} +0 -0
- data/doc/rdoc/classes/Mongrel/URIClassifier.src/{M000018.html → M000022.html} +1 -1
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/README.html +14 -2
- data/doc/rdoc/files/ext/http11/http11_c.html +1 -1
- data/doc/rdoc/files/lib/mongrel_rb.html +1 -1
- data/doc/rdoc/fr_class_index.html +2 -0
- data/doc/rdoc/fr_method_index.html +23 -18
- data/examples/simpletest.rb +2 -1
- data/ext/http11/http11.c +10 -9
- data/ext/http11/http11_parser.c +10 -10
- data/ext/http11/http11_parser.h +5 -1
- data/lib/#mongrel.rb# +493 -0
- data/lib/mongrel.rb +242 -48
- metadata +28 -19
- data/doc/rdoc/classes/Mongrel/HeaderOut.src/M000014.html +0 -21
- data/doc/rdoc/classes/Mongrel/HttpRequest.src/M000025.html +0 -30
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000008.html +0 -26
- data/doc/rdoc/classes/Mongrel/HttpServer.src/M000009.html +0 -58
File without changes
|
@@ -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/
|
20
|
-
*
|
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.
|
File without changes
|
@@ -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
|
{
|
data/doc/rdoc/created.rid
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
Fri Feb 03 01:14:07 EST 2006
|
data/doc/rdoc/files/README.html
CHANGED
@@ -56,7 +56,7 @@
|
|
56
56
|
</tr>
|
57
57
|
<tr class="top-aligned-row">
|
58
58
|
<td><strong>Last Update:</strong></td>
|
59
|
-
<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.
|
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’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’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’t explicitly require Camping, but if you want to run the
|
@@ -143,6 +149,7 @@ example:
|
|
143
149
|
|
144
150
|
h = Mongrel::HttpServer.new("0.0.0.0", "3000")
|
145
151
|
h.register("/test", SimpleHandler.new)
|
152
|
+
h.register("/files", DirHandler.new("."))
|
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’ve ever put
|
@@ -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#
|
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#
|
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/
|
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#
|
38
|
-
<a href="classes/Mongrel/
|
39
|
-
<a href="classes/Mongrel/
|
40
|
-
<a href="classes/Mongrel/HttpServer.html#
|
41
|
-
<a href="classes/Mongrel/
|
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#
|
44
|
-
<a href="classes/Mongrel/HttpServer.html#
|
45
|
-
<a href="classes/Mongrel/
|
46
|
-
<a href="classes/Mongrel/
|
47
|
-
<a href="classes/Mongrel/
|
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>
|
data/examples/simpletest.rb
CHANGED
@@ -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
|
|
data/ext/http11/http11.c
CHANGED
@@ -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
|
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
|
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,
|
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->
|
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/
|
283
|
-
*
|
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
|
-
|
405
|
-
rb_global_variable(&
|
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");
|
data/ext/http11/http11_parser.c
CHANGED
@@ -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
|
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
|
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
|
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->
|
162
|
-
parser->
|
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->
|
605
|
-
parser->
|
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
|
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
|
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
|
899
|
+
#line 142 "ext/http11/http11_parser.rl"
|
900
900
|
|
901
901
|
parser->cs = cs;
|
902
902
|
|
data/ext/http11/http11_parser.h
CHANGED
@@ -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
|
26
|
+
element_cb request_uri;
|
23
27
|
element_cb query_string;
|
24
28
|
element_cb http_version;
|
25
29
|
|
data/lib/#mongrel.rb#
ADDED
@@ -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
|