From 788e4f4902992a9de25f995729b7de713003bcc2 Mon Sep 17 00:00:00 2001 From: zedshaw Date: Fri, 3 Feb 2006 05:42:08 +0000 Subject: * Moved various constants around. * Created the remaining feasible CGI variables people need. * Now create a REQUEST_URI which other CGI variables derive from. Implemented a simple DirHandler for browsing a directory and sending the files. git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@20 19e92222-5c0b-0410-8929-a290d50e31e9 --- examples/simpletest.rb | 1 + ext/http11/http11.c | 19 +-- ext/http11/http11_parser.c | 130 ++++++++++---------- ext/http11/http11_parser.h | 6 +- ext/http11/http11_parser.rl | 16 ++- lib/mongrel.rb | 290 ++++++++++++++++++++++++++++++++++++-------- 6 files changed, 330 insertions(+), 132 deletions(-) diff --git a/examples/simpletest.rb b/examples/simpletest.rb index 864c226..532e289 100644 --- a/examples/simpletest.rb +++ b/examples/simpletest.rb @@ -12,5 +12,6 @@ end h = Mongrel::HttpServer.new("0.0.0.0", "3000") h.register("/test", SimpleHandler.new) +h.register("/files", Mongrel::DirHandler.new(".")) h.run.join diff --git a/ext/http11/http11.c b/ext/http11/http11.c index c01798a..c2659c4 100644 --- a/ext/http11/http11.c +++ b/ext/http11/http11.c @@ -13,7 +13,7 @@ static int id_handler_map; static VALUE global_http_prefix; static VALUE global_request_method; -static VALUE global_path_info; +static VALUE global_request_uri; static VALUE global_query_string; static VALUE global_http_version; @@ -45,11 +45,11 @@ void request_method(void *data, const char *at, size_t length) rb_hash_aset(req, global_request_method, val); } -void path_info(void *data, const char *at, size_t length) +void request_uri(void *data, const char *at, size_t length) { VALUE req = (VALUE)data; VALUE val = rb_str_new(at, length); - rb_hash_aset(req, global_path_info, val); + rb_hash_aset(req, global_request_uri, val); } @@ -87,7 +87,7 @@ VALUE HttpParser_alloc(VALUE klass) TRACE(); hp->http_field = http_field; hp->request_method = request_method; - hp->path_info = path_info; + hp->request_uri = request_uri; hp->query_string = query_string; hp->http_version = http_version; @@ -279,8 +279,9 @@ VALUE URIClassifier_init(VALUE self) * * Registers the SampleHandler (one for all requests) with the "/someuri". * When URIClassifier::resolve is called with "/someuri" it'll return - * SampleHandler immediately. When "/someuri/pathhere" is called it'll - * find SomeHandler after a second search, and setup PATH_INFO="/pathhere". + * SampleHandler immediately. When called with "/someuri/iwant" it'll also + * return SomeHandler immediatly, with no additional searches, but it will + * return path info with "/iwant". * * You actually can reuse this class to register nearly anything and * quickly resolve it. This could be used for caching, fast mapping, etc. @@ -357,7 +358,7 @@ VALUE URIClassifier_unregister(VALUE self, VALUE uri) * It also means that it's very efficient to do this only taking as long as the URI has * characters. * - * It expects strings. Don't try other string-line stuff yet. + * It expects strings with no embedded '\0' characters. Don't try other string-line stuff yet. */ VALUE URIClassifier_resolve(VALUE self, VALUE uri) { @@ -401,8 +402,8 @@ void Init_http11() rb_global_variable(&global_http_prefix); global_request_method = rb_str_new2("REQUEST_METHOD"); rb_global_variable(&global_request_method); - global_path_info = rb_str_new2("PATH_INFO"); - rb_global_variable(&global_path_info); + global_request_uri = rb_str_new2("REQUEST_URI"); + rb_global_variable(&global_request_uri); global_query_string = rb_str_new2("QUERY_STRING"); rb_global_variable(&global_query_string); global_http_version = rb_str_new2("HTTP_VERSION"); diff --git a/ext/http11/http11_parser.c b/ext/http11/http11_parser.c index c256037..83d0431 100644 --- a/ext/http11/http11_parser.c +++ b/ext/http11/http11_parser.c @@ -1,4 +1,4 @@ -#line 1 "ext/http11/http11_parser.rl" +#line 1 "http11_parser.rl" #include "http11_parser.h" #include #include @@ -9,28 +9,28 @@ #define MARK(S,F) assert((F) - (S)->mark >= 0); (S)->mark = (F); /** machine **/ -#line 100 "ext/http11/http11_parser.rl" +#line 98 "http11_parser.rl" /** Data **/ -#line 18 "ext/http11/http11_parser.c" +#line 18 "http11_parser.c" static int http_parser_start = 0; static int http_parser_first_final = 56; static int http_parser_error = 1; -#line 104 "ext/http11/http11_parser.rl" +#line 102 "http11_parser.rl" int http_parser_init(http_parser *parser) { int cs = 0; -#line 30 "ext/http11/http11_parser.c" +#line 30 "http11_parser.c" { cs = http_parser_start; } -#line 108 "ext/http11/http11_parser.rl" +#line 106 "http11_parser.rl" parser->cs = cs; parser->body_start = NULL; parser->content_len = 0; @@ -50,7 +50,7 @@ size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len) pe = buffer+len; -#line 54 "ext/http11/http11_parser.c" +#line 54 "http11_parser.c" { p -= 1; if ( ++p == pe ) @@ -70,14 +70,14 @@ case 0: st1: goto _out1; tr13: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st2; st2: if ( ++p == pe ) goto _out2; case 2: -#line 81 "ext/http11/http11_parser.c" +#line 81 "http11_parser.c" if ( (*p) == 69 ) goto st3; goto st1; @@ -117,7 +117,7 @@ case 7: goto tr33; goto st1; tr33: -#line 29 "ext/http11/http11_parser.rl" +#line 29 "http11_parser.rl" { if(parser->request_method != NULL) parser->request_method(parser->data, parser->mark, p - parser->mark); @@ -127,7 +127,7 @@ st8: if ( ++p == pe ) goto _out8; case 8: -#line 131 "ext/http11/http11_parser.c" +#line 131 "http11_parser.c" switch( (*p) ) { case 42: goto tr27; case 43: goto tr28; @@ -144,26 +144,26 @@ case 8: goto tr28; goto st1; tr27: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st9; st9: if ( ++p == pe ) goto _out9; case 9: -#line 155 "ext/http11/http11_parser.c" +#line 155 "http11_parser.c" if ( (*p) == 32 ) goto tr34; goto st1; tr34: -#line 33 "ext/http11/http11_parser.rl" +#line 33 "http11_parser.rl" { - if(parser->path_info != NULL) - parser->path_info(parser->data, parser->mark, p - parser->mark); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, parser->mark, p - parser->mark); } goto st10; tr48: -#line 37 "ext/http11/http11_parser.rl" +#line 37 "http11_parser.rl" { if(parser->query_string != NULL) parser->query_string(parser->data, parser->mark, p - parser->mark); @@ -173,19 +173,19 @@ st10: if ( ++p == pe ) goto _out10; case 10: -#line 177 "ext/http11/http11_parser.c" +#line 177 "http11_parser.c" if ( (*p) == 72 ) goto tr11; goto st1; tr11: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st11; st11: if ( ++p == pe ) goto _out11; case 11: -#line 189 "ext/http11/http11_parser.c" +#line 189 "http11_parser.c" if ( (*p) == 84 ) goto st12; goto st1; @@ -243,7 +243,7 @@ case 18: goto st18; goto st1; tr37: -#line 42 "ext/http11/http11_parser.rl" +#line 42 "http11_parser.rl" { if(parser->http_version != NULL) parser->http_version(parser->data, parser->mark, p - parser->mark); @@ -253,7 +253,7 @@ st19: if ( ++p == pe ) goto _out19; case 19: -#line 257 "ext/http11/http11_parser.c" +#line 257 "http11_parser.c" if ( (*p) == 10 ) goto st20; goto st1; @@ -293,7 +293,7 @@ case 21: goto tr40; goto st1; tr40: -#line 46 "ext/http11/http11_parser.rl" +#line 46 "http11_parser.rl" { parser->body_start = p+1; goto _out56; } @@ -302,17 +302,17 @@ st56: if ( ++p == pe ) goto _out56; case 56: -#line 306 "ext/http11/http11_parser.c" +#line 306 "http11_parser.c" goto st1; tr36: -#line 16 "ext/http11/http11_parser.rl" +#line 16 "http11_parser.rl" { parser->field_start = p; } goto st22; st22: if ( ++p == pe ) goto _out22; case 22: -#line 316 "ext/http11/http11_parser.c" +#line 316 "http11_parser.c" switch( (*p) ) { case 33: goto st22; case 58: goto tr32; @@ -338,7 +338,7 @@ case 22: goto st22; goto st1; tr32: -#line 17 "ext/http11/http11_parser.rl" +#line 17 "http11_parser.rl" { parser->field_len = (p - parser->field_start); } @@ -347,24 +347,24 @@ st23: if ( ++p == pe ) goto _out23; case 23: -#line 351 "ext/http11/http11_parser.c" +#line 351 "http11_parser.c" if ( (*p) == 13 ) goto tr56; goto tr55; tr55: -#line 21 "ext/http11/http11_parser.rl" +#line 21 "http11_parser.rl" { MARK(parser, p); } goto st24; st24: if ( ++p == pe ) goto _out24; case 24: -#line 363 "ext/http11/http11_parser.c" +#line 363 "http11_parser.c" if ( (*p) == 13 ) goto tr51; goto st24; tr51: -#line 22 "ext/http11/http11_parser.rl" +#line 22 "http11_parser.rl" { if(parser->http_field != NULL) { parser->http_field(parser->data, @@ -374,9 +374,9 @@ tr51: } goto st25; tr56: -#line 21 "ext/http11/http11_parser.rl" +#line 21 "http11_parser.rl" { MARK(parser, p); } -#line 22 "ext/http11/http11_parser.rl" +#line 22 "http11_parser.rl" { if(parser->http_field != NULL) { parser->http_field(parser->data, @@ -389,7 +389,7 @@ st25: if ( ++p == pe ) goto _out25; case 25: -#line 393 "ext/http11/http11_parser.c" +#line 393 "http11_parser.c" switch( (*p) ) { case 10: goto st26; case 13: goto tr51; @@ -424,14 +424,14 @@ case 26: goto tr42; goto st24; tr42: -#line 16 "ext/http11/http11_parser.rl" +#line 16 "http11_parser.rl" { parser->field_start = p; } goto st27; st27: if ( ++p == pe ) goto _out27; case 27: -#line 435 "ext/http11/http11_parser.c" +#line 435 "http11_parser.c" switch( (*p) ) { case 13: goto tr51; case 33: goto st27; @@ -458,14 +458,14 @@ case 27: goto st27; goto st24; tr28: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st28; st28: if ( ++p == pe ) goto _out28; case 28: -#line 469 "ext/http11/http11_parser.c" +#line 469 "http11_parser.c" switch( (*p) ) { case 43: goto st28; case 58: goto st29; @@ -483,14 +483,14 @@ case 28: goto st28; goto st1; tr30: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st29; st29: if ( ++p == pe ) goto _out29; case 29: -#line 494 "ext/http11/http11_parser.c" +#line 494 "http11_parser.c" switch( (*p) ) { case 32: goto tr34; case 37: goto st30; @@ -531,14 +531,14 @@ case 31: goto st29; goto st1; tr29: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st32; st32: if ( ++p == pe ) goto _out32; case 32: -#line 542 "ext/http11/http11_parser.c" +#line 542 "http11_parser.c" switch( (*p) ) { case 32: goto tr34; case 37: goto st34; @@ -599,17 +599,17 @@ case 35: goto st33; goto st1; tr46: -#line 33 "ext/http11/http11_parser.rl" +#line 33 "http11_parser.rl" { - if(parser->path_info != NULL) - parser->path_info(parser->data, parser->mark, p - parser->mark); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, parser->mark, p - parser->mark); } goto st36; st36: if ( ++p == pe ) goto _out36; case 36: -#line 613 "ext/http11/http11_parser.c" +#line 613 "http11_parser.c" switch( (*p) ) { case 32: goto tr48; case 37: goto tr54; @@ -624,14 +624,14 @@ case 36: goto st1; goto tr53; tr53: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st37; st37: if ( ++p == pe ) goto _out37; case 37: -#line 635 "ext/http11/http11_parser.c" +#line 635 "http11_parser.c" switch( (*p) ) { case 32: goto tr48; case 37: goto st38; @@ -646,14 +646,14 @@ case 37: goto st1; goto st37; tr54: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st38; st38: if ( ++p == pe ) goto _out38; case 38: -#line 657 "ext/http11/http11_parser.c" +#line 657 "http11_parser.c" if ( (*p) < 65 ) { if ( 48 <= (*p) && (*p) <= 57 ) goto st39; @@ -677,14 +677,14 @@ case 39: goto st37; goto st1; tr14: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st40; st40: if ( ++p == pe ) goto _out40; case 40: -#line 688 "ext/http11/http11_parser.c" +#line 688 "http11_parser.c" if ( (*p) == 69 ) goto st41; goto st1; @@ -696,14 +696,14 @@ case 41: goto st7; goto st1; tr15: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st42; st42: if ( ++p == pe ) goto _out42; case 42: -#line 707 "ext/http11/http11_parser.c" +#line 707 "http11_parser.c" if ( (*p) == 69 ) goto st43; goto st1; @@ -722,14 +722,14 @@ case 44: goto st7; goto st1; tr16: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st45; st45: if ( ++p == pe ) goto _out45; case 45: -#line 733 "ext/http11/http11_parser.c" +#line 733 "http11_parser.c" if ( (*p) == 80 ) goto st46; goto st1; @@ -769,14 +769,14 @@ case 50: goto st7; goto st1; tr17: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st51; st51: if ( ++p == pe ) goto _out51; case 51: -#line 780 "ext/http11/http11_parser.c" +#line 780 "http11_parser.c" switch( (*p) ) { case 79: goto st52; case 85: goto st41; @@ -790,14 +790,14 @@ case 52: goto st41; goto st1; tr18: -#line 14 "ext/http11/http11_parser.rl" +#line 14 "http11_parser.rl" { MARK(parser, p); } goto st53; st53: if ( ++p == pe ) goto _out53; case 53: -#line 801 "ext/http11/http11_parser.c" +#line 801 "http11_parser.c" if ( (*p) == 82 ) goto st54; goto st1; @@ -875,15 +875,15 @@ case 55: _out: {} } -#line 127 "ext/http11/http11_parser.rl" +#line 125 "http11_parser.rl" parser->cs = cs; parser->nread = p - buffer; if(parser->body_start) { /* final \r\n combo encountered so stop right here */ -#line 886 "ext/http11/http11_parser.c" -#line 133 "ext/http11/http11_parser.rl" +#line 886 "http11_parser.c" +#line 131 "http11_parser.rl" parser->nread++; } @@ -895,8 +895,8 @@ int http_parser_finish(http_parser *parser) int cs = parser->cs; -#line 899 "ext/http11/http11_parser.c" -#line 144 "ext/http11/http11_parser.rl" +#line 899 "http11_parser.c" +#line 142 "http11_parser.rl" parser->cs = cs; diff --git a/ext/http11/http11_parser.h b/ext/http11/http11_parser.h index f4a205f..6ca9cef 100644 --- a/ext/http11/http11_parser.h +++ b/ext/http11/http11_parser.h @@ -3,6 +3,10 @@ #include +#if defined(_WIN32) +#include +#endif + typedef void (*element_cb)(void *data, const char *at, size_t length); typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen); @@ -19,7 +23,7 @@ typedef struct http_parser { field_cb http_field; element_cb request_method; - element_cb path_info; + element_cb request_uri; element_cb query_string; element_cb http_version; diff --git a/ext/http11/http11_parser.rl b/ext/http11/http11_parser.rl index 7642cb1..c86be7a 100644 --- a/ext/http11/http11_parser.rl +++ b/ext/http11/http11_parser.rl @@ -30,9 +30,9 @@ if(parser->request_method != NULL) parser->request_method(parser->data, parser->mark, p - parser->mark); } - action path_info { - if(parser->path_info != NULL) - parser->path_info(parser->data, parser->mark, p - parser->mark); + action request_uri { + if(parser->request_uri != NULL) + parser->request_uri(parser->data, parser->mark, p - parser->mark); } action query_string { if(parser->query_string != NULL) @@ -70,23 +70,21 @@ # URI schemes and absolute paths scheme = ( alpha | digit | "+" | "-" | "." )* ; - absolute_uri = (scheme ":" (uchar | reserved )*) >mark %path_info; + absolute_uri = (scheme ":" (uchar | reserved )*) >mark %request_uri; path = (pchar+ ( "/" pchar* )*) ; query = ( uchar | reserved )* >mark %query_string ; param = ( pchar | "/" )* ; params = (param ( ";" param )*) ; - rel_path = (path? (";" params)?) %path_info ("?" query)? ; + rel_path = (path? (";" params)?) %request_uri ("?" query)? ; absolute_path = ("/" rel_path) >mark ; - Request_URI = ("*" >mark %path_info | absolute_uri | absolute_path) ; + Request_URI = ("*" >mark %request_uri | absolute_uri | absolute_path) ; Method = ("OPTIONS"| "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "TRACE") >mark %request_method; http_number = (digit+ "." digit+) ; HTTP_Version = ("HTTP/" http_number) >mark %http_version ; Request_Line = (Method " " Request_URI " " HTTP_Version CRLF) ; - - field_name = (token - ":")+ >start_field %write_field; @@ -96,7 +94,7 @@ Request = Request_Line (message_header)* $0 ( CRLF $1 @done ); - main := Request; + main := Request; }%% /** Data **/ diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 4912b8a..4b59549 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -8,6 +8,9 @@ require 'stringio' # functionality to service web application requests fast as possible. module Mongrel + # Every standard HTTP code mapped to the appropriate message. These are + # used so frequently that they are placed directly in Mongrel for easy + # access rather than Mongrel::Const. HTTP_STATUS_CODES = { 100 => 'Continue', 101 => 'Switching Protocols', @@ -48,19 +51,89 @@ module Mongrel 505 => 'HTTP Version not supported' } + # Frequently used constants when constructing requests or responses. Many times + # the constant just refers to a string with the same contents. Using these constants + # gave about a 3% to 10% performance improvement over using the strings directly. + # Symbols did not really improve things much compared to constants. + # + # While Mongrel does try to emulate the CGI/1.2 protocol, it does not use the REMOTE_IDENT, + # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or + # too taxing on performance. + module Const + # This is the part of the path after the SCRIPT_NAME. URIClassifier will determine this. + PATH_INFO="PATH_INFO" + # This is the intial part that your handler is identified as by URIClassifier. + SCRIPT_NAME="SCRIPT_NAME" + # The original URI requested by the client. Passed to URIClassifier to build PATH_INFO and SCRIPT_NAME. + REQUEST_URI='REQUEST_URI' + + # Content length (also available as HTTP_CONTENT_LENGTH). + CONTENT_LENGTH='CONTENT_LENGTH' + + # Content length (also available as CONTENT_LENGTH). + HTTP_CONTENT_LENGTH='HTTP_CONTENT_LENGTH' + + # Content type (also available as HTTP_CONTENT_TYPE). + CONTENT_TYPE='CONTENT_TYPE' + + # Content type (also available as CONTENT_TYPE). + HTTP_CONTENT_TYPE='HTTP_CONTENT_TYPE' + + # Gateway interface key in the HttpRequest parameters. + GATEWAY_INTERFACE='GATEWAY_INTERFACE' + # We claim to support CGI/1.2. + GATEWAY_INTERFACE_VALUE='CGI/1.2' + + # Hosts remote IP address. Mongrel does not do DNS resolves since that slows + # processing down considerably. + REMOTE_ADDR='REMOTE_ADDR' + + # This is not given since Mongrel does not do DNS resolves. It is only here for + # completeness for the CGI standard. + REMOTE_HOST='REMOTE_HOST' + + # The name/host of our server as given by the HttpServer.new(host,port) call. + SERVER_NAME='SERVER_NAME' + + # The port of our server as given by the HttpServer.new(host,port) call. + SERVER_PORT='SERVER_PORT' + + # Official server protocol key in the HttpRequest parameters. + SERVER_PROTOCOL='SERVER_PROTOCOL' + # Mongrel claims to support HTTP/1.1. + SERVER_PROTOCOL_VALUE='HTTP/1.1' + + # The actual server software being used (it's Mongrel man). + SERVER_SOFTWARE='SERVER_SOFTWARE' + + # Current Mongrel version (used for SERVER_SOFTWARE and other response headers). + MONGREL_VERSION='Mongrel 0.2.2' + + # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff. + ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: #{MONGREL_VERSION}\r\n\r\nNOT FOUND" + + # A common header for indicating the server is too busy. Not used yet. + ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY" + + # The basic max request size we'll try to read. + CHUNK_SIZE=(16 * 1024) + + end + + # When a handler is found for a registered URI then this class is constructed # and passed to your HttpHandler::process method. You should assume that # *one* handler processes all requests. Included in the HttpReqeust is a # HttpRequest.params Hash that matches common CGI params, and a HttpRequest.body # which is a string containing the request body (raw for now). # - # Mongrel really only support small-ish request bodies right now since really + # Mongrel really only supports small-ish request bodies right now since really # huge ones have to be completely read off the wire and put into a string. # Later there will be several options for efficiently handling large file # uploads. class HttpRequest attr_reader :body, :params - + # You don't really call this. It's made for you. # Main thing it does is hook up the params, and store any remaining # body data into the HttpRequest.body attribute. @@ -68,13 +141,14 @@ module Mongrel @body = initial_body || "" @params = params @socket = socket - + # fix up the CGI requirements - params['CONTENT_LENGTH'] = params['HTTP_CONTENT_LENGTH'] || 0 + params[Const::CONTENT_LENGTH] = params[Const::HTTP_CONTENT_LENGTH] || 0 + params[Const::CONTENT_TYPE] ||= params[Const::HTTP_CONTENT_TYPE] # now, if the initial_body isn't long enough for the content length we have to fill it # TODO: adapt for big ass stuff by writing to a temp file - clen = params['HTTP_CONTENT_LENGTH'].to_i + clen = params[Const::HTTP_CONTENT_LENGTH].to_i if @body.length < clen @body << @socket.read(clen - @body.length) end @@ -82,6 +156,13 @@ module Mongrel end + # This class implements a simple way of constructing the HTTP headers dynamically + # via a Hash syntax. Think of it as a write-only Hash. Refer to HttpResponse for + # information on how this is used. + # + # One consequence of this write-only nature is that you can write multiple headers + # by just doing them twice (which is sometimes needed in HTTP), but that the normal + # semantics for Hash (where doing an insert replaces) is not there. class HeaderOut attr_reader :out @@ -89,6 +170,7 @@ module Mongrel @out = out end + # Simply writes "#{key}: #{value}" to an output buffer. def[]=(key,value) @out.write(key) @out.write(": ") @@ -97,7 +179,35 @@ module Mongrel end end - + # Writes and controls your response to the client using the HTTP/1.1 specification. + # You use it by simply doing: + # + # response.start(200) do |head,out| + # head['Content-Type'] = 'text/plain' + # out.write("hello\n") + # end + # + # The parameter to start is the response code--which Mongrel will translate for you + # based on HTTP_STATUS_CODES. The head parameter is how you write custom headers. + # The out parameter is where you write your body. The default status code for + # HttpResponse.start is 200 so the above example is redundant. + # + # As you can see, it's just like using a Hash and as you do this it writes the proper + # header to the output on the fly. You can even intermix specifying headers and + # writing content. The HttpResponse class with write the things in the proper order + # once the HttpResponse.block is ended. + # + # You may also work the HttpResponse object directly using the various attributes available + # for the raw socket, body, header, and status codes. If you do this you're on your own. + # A design decision was made to force the client to not pipeline requests. HTTP/1.1 + # pipelining really kills the performance due to how it has to be handled and how + # unclear the standard is. To fix this the HttpResponse gives a "Connection: close" + # header which forces the client to close right away. The bonus for this is that it + # gives a pretty nice speed boost to most clients since they can close their connection + # immediately. + # + # One additional caveat is that you don't have to specify the Content-length header + # as the HttpResponse will write this for you based on the out length. class HttpResponse attr_reader :socket attr_reader :body @@ -112,12 +222,25 @@ module Mongrel @header = HeaderOut.new(StringIO.new) end + # Receives a block passing it the header and body for you to work with. + # When the block is finished it writes everything you've done to + # the socket in the proper order. This lets you intermix header and + # body content as needed. def start(status=200) @status = status yield @header, @body finished end - + + # Primarily used in exception handling to reset the response output in order to write + # an alternative response. + def reset + @header.out.rewind + @body.rewind + end + + # This takes whatever has been done to header and body and then writes it in the + # proper format to make an HTTP/1.1 response. def finished @header.out.rewind @body.rewind @@ -136,29 +259,8 @@ module Mongrel # a response. Look at the HttpRequest and HttpResponse objects for how # to use them. class HttpHandler - attr_accessor :script_name - - def process(request, response) - end - end - - - # The server normally returns a 404 response if a URI is requested, but it - # also returns a lame empty message. This lets you do a 404 response - # with a custom message for special URIs. - class Error404Handler < HttpHandler - - # Sets the message to return. This is constructed once for the handler - # so it's pretty efficient. - def initialize(msg) - @response = HttpServer::ERROR_404_RESPONSE + msg - end - - # Just kicks back the standard 404 response with your special message. def process(request, response) - response.socket.write(@response) end - end @@ -183,16 +285,6 @@ module Mongrel class HttpServer attr_reader :acceptor - # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff. - ERROR_404_RESPONSE="HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Mongrel/0.2\r\n\r\nNOT FOUND" - ERROR_503_RESPONSE="HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY" - - # The basic max request size we'll try to read. - CHUNK_SIZE=(16 * 1024) - - PATH_INFO="PATH_INFO" - SCRIPT_NAME="SCRIPT_NAME" - # Creates a working server on host:port (strange things happen if port isn't a Number). # Use HttpServer::run to start the server. # @@ -210,8 +302,13 @@ module Mongrel # Future versions of Mongrel will make this more dynamic (hopefully). def initialize(host, port, num_processors=20) @socket = TCPServer.new(host, port) + @classifier = URIClassifier.new @req_queue = Queue.new + @host = host + @port = port + @num_procesors = num_processors + num_processors.times {|i| Thread.new do while client = @req_queue.deq process_client(client) @@ -223,30 +320,35 @@ module Mongrel # Does the majority of the IO processing. It has been written in Ruby using # about 7 different IO processing strategies and no matter how it's done - # the performance just does not improve. Ruby's use of select to implement - # threads means that it will most likely never improve, so the only remaining - # approach is to write all or some of this function in C. That will be the - # focus of future releases. + # the performance just does not improve. It is currently carefully constructed + # to make sure that it gets the best possible performance, but anyone who + # thinks they can make it faster is more than welcome to take a crack at it. def process_client(client) begin parser = HttpParser.new params = {} - data = client.readpartial(CHUNK_SIZE) + data = client.readpartial(Const::CHUNK_SIZE) while true nread = parser.execute(params, data) if parser.finished? - script_name, path_info, handler = @classifier.resolve(params[PATH_INFO]) + script_name, path_info, handler = @classifier.resolve(params[Const::REQUEST_URI]) if handler - params[PATH_INFO] = path_info - params[SCRIPT_NAME] = script_name + params[Const::PATH_INFO] = path_info + params[Const::SCRIPT_NAME] = script_name + params[Const::GATEWAY_INTERFACE]=Const::GATEWAY_INTERFACE_VALUE + params[Const::REMOTE_ADDR]=client.peeraddr + params[Const::SERVER_NAME]=@host + params[Const::SERVER_PORT]=@port + params[Const::SERVER_PROTOCOL]=Const::SERVER_PROTOCOL_VALUE + params[Const::SERVER_SOFTWARE]=Const::MONGREL_VERSION request = HttpRequest.new(params, data[nread ... data.length], client) response = HttpResponse.new(client) handler.process(request, response) else - client.write(ERROR_404_RESPONSE) + client.write(Const::ERROR_404_RESPONSE) end break @@ -254,7 +356,7 @@ module Mongrel # gotta stream and read again until we can get the parser to be character safe # TODO: make this more efficient since this means we're parsing a lot repeatedly parser.reset - data << client.readpartial(CHUNK_SIZE) + data << client.readpartial(Const::CHUNK_SIZE) end end rescue EOFError @@ -274,6 +376,7 @@ module Mongrel # Runs the thing. It returns the thread used so you can "join" it. You can also # access the HttpServer::acceptor attribute to get the thread later. def run + BasicSocket.do_not_reverse_lookup=true @acceptor = Thread.new do while true @req_queue << @socket.accept @@ -295,4 +398,95 @@ module Mongrel @classifier.unregister(uri) end end + + + # The server normally returns a 404 response if a URI is requested, but it + # also returns a lame empty message. This lets you do a 404 response + # with a custom message for special URIs. + class Error404Handler < HttpHandler + + # Sets the message to return. This is constructed once for the handler + # so it's pretty efficient. + def initialize(msg) + @response = HttpServer::ERROR_404_RESPONSE + msg + end + + # Just kicks back the standard 404 response with your special message. + def process(request, response) + response.socket.write(@response) + end + + end + + + # Serves the contents of a directory. You give it the path to the root + # where the files are located, and it tries to find the files based on + # the PATH_INFO inside the directory. If the requested path is a + # directory then it returns a simple directory listing. + # + # It does a simple protection against going outside it's root path by + # converting all paths to an absolute expanded path, and then making sure + # that the final expanded path includes the root path. If it doesn't + # than it simply gives a 404. + class DirHandler < HttpHandler + + def initialize(path, listing_allowed=true) + @path = File.expand_path(path) + @listing_allowed=listing_allowed + puts "DIR: #@path" + end + + def send_dir_listing(base, dir, response) + if @listing_allowed + response.start(200) do |head,out| + head['Content-Type'] = "text/html" + out << "Directory Listing" + Dir.entries(dir).each do |child| + out << "#{child}
" + end + out << "" + end + else + response.start(403) do |head,out| + out.write("Directory listings not allowed") + end + end + end + + + def send_file(req, response) + response.start(200) do |head,out| + open(req, "r") do |f| + out.write(f.read) + end + end + end + + + def process(request, response) + req = File.expand_path("." + request.params['PATH_INFO'], @path) + puts "FIND: #{req}" + if req.index(@path) != 0 or !File.exist? req + # not found, return a 404 + response.start(404) do |head,out| + out << "File not found" + end + else + begin + if File.directory? req + send_dir_listing(request.params["REQUEST_URI"],req, response) + else + send_file(req, response) + end + rescue => details + response.reset + response.start(403) do |head,out| + out << "Error accessing file" + end + STDERR.puts "ERROR: #{details}" + end + end + end + end + end -- cgit v1.2.3-24-ge0c7