From 98a33bb30438c2bcbb10d48523afd9d491750a1e Mon Sep 17 00:00:00 2001 From: zedshaw Date: Wed, 8 Feb 2006 12:48:41 +0000 Subject: [PATCH] Fixed a bug in the Error404Handler. Created the first Rails runner script. git-svn-id: svn+ssh://rubyforge.org/var/svn/mongrel/trunk@21 19e92222-5c0b-0410-8929-a290d50e31e9 --- README | 12 +++- Rakefile | 2 +- examples/mongrel_rails.rb | 73 ++++++++++++++++++++++ examples/simpletest.rb | 22 +++++-- ext/http11/http11_parser.c | 122 ++++++++++++++++++------------------- lib/mongrel.rb | 58 +++++++++++++----- 6 files changed, 205 insertions(+), 84 deletions(-) create mode 100644 examples/mongrel_rails.rb diff --git a/README b/README index 050c3cfa..be4da9c0 100644 --- a/README +++ b/README @@ -11,7 +11,7 @@ scream without too many portability issues. == Status -The 0.2.1 release of Mongrel features an HTTP core server that is the fastest possible +The 0.2.2 release of Mongrel features an HTTP core server that is the fastest possible thing I could get without using something other than Ruby. It features a few bug fixes, but mostly just a change to the Mongrel::HttpResponse class to make it more feature complete. The remaining development will be spent getting Mongrel to work with @@ -28,6 +28,9 @@ page found at http://rubyforge.org/projects/mongrel/ thanks to Tom Copland. I'll be looking to automate management of this, but feel free to use rubyforge to post feature requests, bugs, and join the mailing list. +Finally, it now supports all CGI parameters that don't cause a performance hit, +and it has a Mongrel::DirHandler which can serve files out of a directory and +do (optional) directory listings. == Install @@ -40,7 +43,6 @@ who can build it for you. Finally, the source includes a setup.rb for those who hate RubyGems. - == Usage The examples/simpletest.rb file has the following code as the simplest @@ -59,6 +61,7 @@ example: h = Mongrel::HttpServer.new("0.0.0.0", "3000") h.register("/test", SimpleHandler.new) + h.register("/files", DirHandler.new(".")) h.run.join If you run this and access port 3000 with a browser it will say @@ -66,6 +69,11 @@ If you run this and access port 3000 with a browser it will say give a simple 404. Check out the Mongrel::Error404Handler for a basic way to give a more complex 404 message. +This also shows the DirHandler with directory listings. This is still +rough but it should work for basic hosting. *File extension to mime +type mapping is missing though.* + + == Speed The 0.2.1 release probably consists of the most effort I've ever put into diff --git a/Rakefile b/Rakefile index f084c889..3dddf717 100644 --- a/Rakefile +++ b/Rakefile @@ -27,4 +27,4 @@ setup_extension("http11", "http11") summary = "An experimental fast simple web server for Ruby." test_file = "test/test_ws.rb" -setup_gem("mongrel", "0.2.1", "Zed A. Shaw", summary, [], test_file) +setup_gem("mongrel", "0.2.2", "Zed A. Shaw", summary, [], test_file) diff --git a/examples/mongrel_rails.rb b/examples/mongrel_rails.rb new file mode 100644 index 00000000..ab5b740c --- /dev/null +++ b/examples/mongrel_rails.rb @@ -0,0 +1,73 @@ +require 'mongrel' +require 'cgi' +require 'config/environment' + +class CGIFixed < ::CGI + public :env_table + + def initialize(params, data, out, *args) + @env_table = params + @args = *args + @input = StringIO.new(data) + @out = out + super(*args) + end + + def args + @args + end + + def env_table + @env_table + end + + def stdinput + @input + end + + def stdoutput + @out + end +end + + +class RailsHandler < Mongrel::HttpHandler + def initialize + @guard = Mutex.new + end + + def process(request, response) + # not static, need to talk to rails + return if response.socket.closed? + + cgi = CGIFixed.new(request.params, request.body, response.socket) + begin + + @guard.synchronize do + # Rails is not thread safe so must be run entirely within synchronize + Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, response.body) + end + + response.send_status + response.send_body + rescue IOError + @log.error("received IOError #$! when handling client. Your web server doesn't like me.") + rescue Object => rails_error + @log.error("calling Dispatcher.dispatch", rails_error) + end + end +end + +if ARGV.length != 3 + STDERR.puts "usage: mongrel_rails.rb " + exit(1) +end + +h = Mongrel::HttpServer.new(ARGV[0], ARGV[1]) +h.register("/", Mongrel::DirHandler.new(ARGV[2])) +h.register("/app", RailsHandler.new) +h.run + +puts "Mongrel running on #{ARGV[0]}:#{ARGV[1]} with docroot #{ARGV[2]}" + +h.acceptor.join diff --git a/examples/simpletest.rb b/examples/simpletest.rb index 532e2892..893df96d 100644 --- a/examples/simpletest.rb +++ b/examples/simpletest.rb @@ -4,14 +4,24 @@ require 'yaml' class SimpleHandler < Mongrel::HttpHandler def process(request, response) response.start do |head,out| - head["Content-Type"] = "text/plain" - out.write("hello!\n") + head["Content-Type"] = "text/html" + out << "Your request:
" + out << "
#{request.params.to_yaml}
" + out << "View the files." end end end -h = Mongrel::HttpServer.new("0.0.0.0", "3000") -h.register("/test", SimpleHandler.new) -h.register("/files", Mongrel::DirHandler.new(".")) -h.run.join +if ARGV.length != 3 + STDERR.puts "usage: simpletest.rb " + exit(1) +end +h = Mongrel::HttpServer.new(ARGV[0], ARGV[1]) +h.register("/", SimpleHandler.new) +h.register("/files", Mongrel::DirHandler.new(ARGV[2])) +h.run + +puts "Mongrel running on #{ARGV[0]}:#{ARGV[1]} with docroot #{ARGV[2]}" + +h.acceptor.join diff --git a/ext/http11/http11_parser.c b/ext/http11/http11_parser.c index 83d0431c..9c1c01ba 100644 --- a/ext/http11/http11_parser.c +++ b/ext/http11/http11_parser.c @@ -1,4 +1,4 @@ -#line 1 "http11_parser.rl" +#line 1 "ext/http11/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 98 "http11_parser.rl" +#line 98 "ext/http11/http11_parser.rl" /** Data **/ -#line 18 "http11_parser.c" +#line 18 "ext/http11/http11_parser.c" static int http_parser_start = 0; static int http_parser_first_final = 56; static int http_parser_error = 1; -#line 102 "http11_parser.rl" +#line 102 "ext/http11/http11_parser.rl" int http_parser_init(http_parser *parser) { int cs = 0; -#line 30 "http11_parser.c" +#line 30 "ext/http11/http11_parser.c" { cs = http_parser_start; } -#line 106 "http11_parser.rl" +#line 106 "ext/http11/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 "http11_parser.c" +#line 54 "ext/http11/http11_parser.c" { p -= 1; if ( ++p == pe ) @@ -70,14 +70,14 @@ case 0: st1: goto _out1; tr13: -#line 14 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st2; st2: if ( ++p == pe ) goto _out2; case 2: -#line 81 "http11_parser.c" +#line 81 "ext/http11/http11_parser.c" if ( (*p) == 69 ) goto st3; goto st1; @@ -117,7 +117,7 @@ case 7: goto tr33; goto st1; tr33: -#line 29 "http11_parser.rl" +#line 29 "ext/http11/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 "http11_parser.c" +#line 131 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st9; st9: if ( ++p == pe ) goto _out9; case 9: -#line 155 "http11_parser.c" +#line 155 "ext/http11/http11_parser.c" if ( (*p) == 32 ) goto tr34; goto st1; tr34: -#line 33 "http11_parser.rl" +#line 33 "ext/http11/http11_parser.rl" { if(parser->request_uri != NULL) parser->request_uri(parser->data, parser->mark, p - parser->mark); } goto st10; tr48: -#line 37 "http11_parser.rl" +#line 37 "ext/http11/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 "http11_parser.c" +#line 177 "ext/http11/http11_parser.c" if ( (*p) == 72 ) goto tr11; goto st1; tr11: -#line 14 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st11; st11: if ( ++p == pe ) goto _out11; case 11: -#line 189 "http11_parser.c" +#line 189 "ext/http11/http11_parser.c" if ( (*p) == 84 ) goto st12; goto st1; @@ -243,7 +243,7 @@ case 18: goto st18; goto st1; tr37: -#line 42 "http11_parser.rl" +#line 42 "ext/http11/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 "http11_parser.c" +#line 257 "ext/http11/http11_parser.c" if ( (*p) == 10 ) goto st20; goto st1; @@ -293,7 +293,7 @@ case 21: goto tr40; goto st1; tr40: -#line 46 "http11_parser.rl" +#line 46 "ext/http11/http11_parser.rl" { parser->body_start = p+1; goto _out56; } @@ -302,17 +302,17 @@ st56: if ( ++p == pe ) goto _out56; case 56: -#line 306 "http11_parser.c" +#line 306 "ext/http11/http11_parser.c" goto st1; tr36: -#line 16 "http11_parser.rl" +#line 16 "ext/http11/http11_parser.rl" { parser->field_start = p; } goto st22; st22: if ( ++p == pe ) goto _out22; case 22: -#line 316 "http11_parser.c" +#line 316 "ext/http11/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 "http11_parser.rl" +#line 17 "ext/http11/http11_parser.rl" { parser->field_len = (p - parser->field_start); } @@ -347,24 +347,24 @@ st23: if ( ++p == pe ) goto _out23; case 23: -#line 351 "http11_parser.c" +#line 351 "ext/http11/http11_parser.c" if ( (*p) == 13 ) goto tr56; goto tr55; tr55: -#line 21 "http11_parser.rl" +#line 21 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st24; st24: if ( ++p == pe ) goto _out24; case 24: -#line 363 "http11_parser.c" +#line 363 "ext/http11/http11_parser.c" if ( (*p) == 13 ) goto tr51; goto st24; tr51: -#line 22 "http11_parser.rl" +#line 22 "ext/http11/http11_parser.rl" { if(parser->http_field != NULL) { parser->http_field(parser->data, @@ -374,9 +374,9 @@ tr51: } goto st25; tr56: -#line 21 "http11_parser.rl" +#line 21 "ext/http11/http11_parser.rl" { MARK(parser, p); } -#line 22 "http11_parser.rl" +#line 22 "ext/http11/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 "http11_parser.c" +#line 393 "ext/http11/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 "http11_parser.rl" +#line 16 "ext/http11/http11_parser.rl" { parser->field_start = p; } goto st27; st27: if ( ++p == pe ) goto _out27; case 27: -#line 435 "http11_parser.c" +#line 435 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st28; st28: if ( ++p == pe ) goto _out28; case 28: -#line 469 "http11_parser.c" +#line 469 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st29; st29: if ( ++p == pe ) goto _out29; case 29: -#line 494 "http11_parser.c" +#line 494 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st32; st32: if ( ++p == pe ) goto _out32; case 32: -#line 542 "http11_parser.c" +#line 542 "ext/http11/http11_parser.c" switch( (*p) ) { case 32: goto tr34; case 37: goto st34; @@ -599,7 +599,7 @@ case 35: goto st33; goto st1; tr46: -#line 33 "http11_parser.rl" +#line 33 "ext/http11/http11_parser.rl" { if(parser->request_uri != NULL) parser->request_uri(parser->data, parser->mark, p - parser->mark); @@ -609,7 +609,7 @@ st36: if ( ++p == pe ) goto _out36; case 36: -#line 613 "http11_parser.c" +#line 613 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st37; st37: if ( ++p == pe ) goto _out37; case 37: -#line 635 "http11_parser.c" +#line 635 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st38; st38: if ( ++p == pe ) goto _out38; case 38: -#line 657 "http11_parser.c" +#line 657 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st40; st40: if ( ++p == pe ) goto _out40; case 40: -#line 688 "http11_parser.c" +#line 688 "ext/http11/http11_parser.c" if ( (*p) == 69 ) goto st41; goto st1; @@ -696,14 +696,14 @@ case 41: goto st7; goto st1; tr15: -#line 14 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st42; st42: if ( ++p == pe ) goto _out42; case 42: -#line 707 "http11_parser.c" +#line 707 "ext/http11/http11_parser.c" if ( (*p) == 69 ) goto st43; goto st1; @@ -722,14 +722,14 @@ case 44: goto st7; goto st1; tr16: -#line 14 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st45; st45: if ( ++p == pe ) goto _out45; case 45: -#line 733 "http11_parser.c" +#line 733 "ext/http11/http11_parser.c" if ( (*p) == 80 ) goto st46; goto st1; @@ -769,14 +769,14 @@ case 50: goto st7; goto st1; tr17: -#line 14 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st51; st51: if ( ++p == pe ) goto _out51; case 51: -#line 780 "http11_parser.c" +#line 780 "ext/http11/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 "http11_parser.rl" +#line 14 "ext/http11/http11_parser.rl" { MARK(parser, p); } goto st53; st53: if ( ++p == pe ) goto _out53; case 53: -#line 801 "http11_parser.c" +#line 801 "ext/http11/http11_parser.c" if ( (*p) == 82 ) goto st54; goto st1; @@ -875,15 +875,15 @@ case 55: _out: {} } -#line 125 "http11_parser.rl" +#line 125 "ext/http11/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 "http11_parser.c" -#line 131 "http11_parser.rl" +#line 886 "ext/http11/http11_parser.c" +#line 131 "ext/http11/http11_parser.rl" parser->nread++; } @@ -895,8 +895,8 @@ int http_parser_finish(http_parser *parser) int cs = parser->cs; -#line 899 "http11_parser.c" -#line 142 "http11_parser.rl" +#line 899 "ext/http11/http11_parser.c" +#line 142 "ext/http11/http11_parser.rl" parser->cs = cs; diff --git a/lib/mongrel.rb b/lib/mongrel.rb index 4b59549c..1bc1c232 100644 --- a/lib/mongrel.rb +++ b/lib/mongrel.rb @@ -239,17 +239,29 @@ module Mongrel @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 + def send_status + @socket.write("HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status]}\r\nContent-Length: #{@body.length}\r\nConnection: close\r\n") + end + + def send_header @header.out.rewind + @socket.write(@header.out.read) + @socket.write("\r\n") + end + + def send_body @body.rewind # connection: close is also added to ensure that the client does not pipeline. - @socket.write("HTTP/1.1 #{@status} #{HTTP_STATUS_CODES[@status]}\r\nContent-Length: #{@body.length}\r\nConnection: close\r\n") - @socket.write(@header.out.read) - @socket.write("\r\n") @socket.write(@body.read) + 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 + send_status + send_header + send_body end end @@ -408,7 +420,7 @@ module Mongrel # 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 + @response = Const::ERROR_404_RESPONSE + msg end # Just kicks back the standard 404 response with your special message. @@ -429,20 +441,39 @@ module Mongrel # that the final expanded path includes the root path. If it doesn't # than it simply gives a 404. class DirHandler < HttpHandler + attr_reader :path + # You give it the path to the directory root and an (optional) def initialize(path, listing_allowed=true) @path = File.expand_path(path) @listing_allowed=listing_allowed - puts "DIR: #@path" + end + + # Checks if the given path can be served and returns the full path (or nil if not). + def can_serve(path_info) + req = File.expand_path(path_info, @path) + if req.index(@path) != 0 or !File.exist? req + return nil + else + return req + end end def send_dir_listing(base, dir, response) + base.chop! if base[-1] == "/"[-1] + 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}
" + next if child == "." + + if child == ".." + out << "Up to parent..
" + else + out << "#{child}
" + end end out << "" end @@ -464,9 +495,9 @@ module Mongrel 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 + path_info = request.params['PATH_INFO'] + req = can_serve path_info + if not req # not found, return a 404 response.start(404) do |head,out| out << "File not found" @@ -481,9 +512,8 @@ module Mongrel rescue => details response.reset response.start(403) do |head,out| - out << "Error accessing file" + out << "Error accessing file: #{details}" end - STDERR.puts "ERROR: #{details}" end end end