1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00

* 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
This commit is contained in:
zedshaw 2006-02-03 05:42:08 +00:00
parent 1b9b3bcb73
commit 788e4f4902
6 changed files with 330 additions and 132 deletions

View file

@ -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

View file

@ -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");

View file

@ -1,4 +1,4 @@
#line 1 "ext/http11/http11_parser.rl"
#line 1 "http11_parser.rl"
#include "http11_parser.h"
#include <stdio.h>
#include <assert.h>
@ -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;

View file

@ -3,6 +3,10 @@
#include <sys/types.h>
#if defined(_WIN32)
#include <stddef.h>
#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;

View file

@ -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 **/

View file

@ -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,32 +259,11 @@ 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
# This is the main driver of Mongrel, while the Mognrel::HttpParser and Mongrel::URIClassifier
# make up the majority of how the server functions. It's a very simple class that just
# has a thread accepting connections and a simple HttpServer.process_client function
@ -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 << "<html><head><title>Directory Listing</title></head><body>"
Dir.entries(dir).each do |child|
out << "<a href=\"#{base}/#{child}\">#{child}</a><br/>"
end
out << "</body></html>"
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