mongrel 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|