[3975] ControlTower/trunk
Revision: 3975 http://trac.macosforge.org/projects/ruby/changeset/3975 Author: lsansonetti@apple.com Date: 2010-04-29 13:51:15 -0700 (Thu, 29 Apr 2010) Log Message: ----------- initial revision of ControlTower project Added Paths: ----------- ControlTower/trunk/README ControlTower/trunk/Rakefile ControlTower/trunk/bin/ ControlTower/trunk/bin/control_tower ControlTower/trunk/ext/ ControlTower/trunk/ext/CTParser/ ControlTower/trunk/ext/CTParser/CTParser.h ControlTower/trunk/ext/CTParser/CTParser.m ControlTower/trunk/ext/CTParser/extconf.rb ControlTower/trunk/ext/CTParser/http11_parser.c ControlTower/trunk/ext/CTParser/http11_parser.h ControlTower/trunk/lib/ ControlTower/trunk/lib/control_tower/ ControlTower/trunk/lib/control_tower/rack_socket.rb ControlTower/trunk/lib/control_tower/server.rb ControlTower/trunk/lib/control_tower.rb ControlTower/trunk/lib/rack/ ControlTower/trunk/lib/rack/handler/ ControlTower/trunk/lib/rack/handler/control_tower.rb ControlTower/trunk/spec/ ControlTower/trunk/spec/ctparser_spec.rb Added: ControlTower/trunk/README =================================================================== --- ControlTower/trunk/README (rev 0) +++ ControlTower/trunk/README 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,9 @@ +Control Tower + +Copyright (c) 2009-2010, Apple Inc +Author: Joshua Ballanco + +SYNOPSIS +Control Tower is a Web application server for Rack-based Ruby applications. It +is (or will be) composed of three major components: a networking layer, an HTTP +parser, and a Rack interface. Added: ControlTower/trunk/Rakefile =================================================================== --- ControlTower/trunk/Rakefile (rev 0) +++ ControlTower/trunk/Rakefile 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,80 @@ +require 'rubygems' +require 'rake/gempackagetask' + +CT_VERSION = '0.1' + +GEM_SPEC = Gem::Specification.new do |spec| + spec.platform = Gem::Platform.local + spec.name = 'control_tower' + spec.summary = "A Rack-based HTTP server for MacRuby" + spec.description = <<-DESCRIPTION + Control Tower is a Rack-based HTTP server designed to work with MacRuby. It can + be used by calling to its Rack::Handler class, or by running the control_tower + executable with a Rackup configuration file (see the control tower help for more + details). + DESCRIPTION + spec.version = CT_VERSION + spec.add_runtime_dependency 'rack', '>= 1.0.1' + spec.files = %w( + lib/control_tower.rb + lib/control_tower/rack_socket.rb + lib/control_tower/server.rb + lib/rack/handler/control_tower.rb + lib/CTParser.bundle + bin/control_tower + ) + spec.executable = 'control_tower' +end + +verbose(true) + +desc "Same as all" +task :default => :all + +desc "Build everything" +task :all => ['build', 'gem'] + +desc "Build CTParser" +task :build do + gcc = RbConfig::CONFIG['CC'] + cflags = RbConfig::CONFIG['CFLAGS'] + ' ' + RbConfig::CONFIG['ARCH_FLAG'] + + Dir.chdir('ext/CTParser') do + sh "#{gcc} #{cflags} -fobjc-gc CTParser.m -c -o CTParser.o" + sh "#{gcc} #{cflags} http11_parser.c -c -o http11_parser.o" + sh "#{RbConfig::CONFIG['LDSHARED']} CTParser.o http11_parser.o -o CTParser.bundle" + end +end + +desc "Clean packages and extensions" +task :clean do + sh "rm -rf pkg ext/CTParser/*.o ext/CTParser/*.bundle lib/*.bundle" +end + +desc "Install as a standard library" +task :stdlib_install => [:build] do + prefix = (ENV['DESTDIR'] || '') + dest = File.join(prefix, RbConfig::CONFIG['sitelibdir']) + mkdir_p(dest) + sh "ditto lib \"#{dest}\"" + dest = File.join(prefix, RbConfig::CONFIG['sitearchdir']) + mkdir_p(dest) + sh "cp ext/CTParser/CTParser.bundle \"#{dest}\"" +end + +file 'ext/CTParser/CTParser.bundle' => 'build' + +file 'lib/CTParser.bundle' => ['ext/CTParser/CTParser.bundle'] do + FileUtils.cp('ext/CTParser/CTParser.bundle', 'lib/CTParser.bundle') +end + +Rake::GemPackageTask.new(GEM_SPEC) do |pkg| + pkg.need_zip = false + pkg.need_tar = true +end + +desc "Run Control Tower" +task :run do + sh "macruby -I./lib -I./ext/CTParser bin/control_tower" +end + Added: ControlTower/trunk/bin/control_tower =================================================================== --- ControlTower/trunk/bin/control_tower (rev 0) +++ ControlTower/trunk/bin/control_tower 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,57 @@ +#!/usr/bin/env macruby +# This file is covered by the Ruby license. See COPYING for more details. +# Copyright (C) 2009-2010, Apple Inc. All rights reserved. + +require 'control_tower' +require 'optparse' + +# Some default values +@options = { + :rackup => './config.ru', + :port => '8080', + :host => 'localhost' +} + +OptionParser.new do |opts| + opts.on("-R", "--rackup [FILE]", "Rack-up Configuration File") do |rackup| + @options[:rackup] = rackup + end + + opts.on("-p", "--port [SERVER_PORT]", Integer, "Port on which to run the server") do |port| + @options[:port] = port + end + + opts.on("-h", "--host [HOSTNAME]", "Hostname for the server") do |host| + @options[:host] = host + end + + opts.on("-c", "--[no]-concurrency", "Handle requests concurrently") do |concurrent| + @options[:concurrent] = concurrent + end +end.parse! + +unless File.exist? File.expand_path(@options[:rackup]) + puts "We only know how to deal with Rack-up configs for now" + exit 1 +end + +unless File.exist? File.expand_path(@options[:rackup]) + puts "We only know how to deal with Rack-up configs for now" + exit 1 +end + +# Under construction...everything is development! +ENV['RACK_ENV'] = 'development' + +rackup_config = File.read(File.expand_path(@options[:rackup])) +app = eval("Rack::Builder.new {( #{rackup_config}\n )}.to_app", TOPLEVEL_BINDING) + +# Let's get to business! +server = ControlTower::Server.new(app, @options) +if server + puts "You are cleared for take-off!" + server.start +else + puts "Mayday! Mayday! Eject! Eject!\n#{$!}" + exit 1 +end Added: ControlTower/trunk/ext/CTParser/CTParser.h =================================================================== --- ControlTower/trunk/ext/CTParser/CTParser.h (rev 0) +++ ControlTower/trunk/ext/CTParser/CTParser.h 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,37 @@ +/* + * This file is covered by the Ruby license. See COPYING for more details. + * Copyright (C) 2009-2010, Apple Inc. All rights reserved. + */ + +#include "http11_parser.h" +#import <Foundation/Foundation.h> + +// TODO - We should grab this from a plist somewhere... +#define SERVER_SOFTWARE @"Control Tower v0.1" + +@interface CTParser : NSObject +{ + http_parser *_parser; + NSString *_body; +} + +@property(copy) NSString *body; + +- (id)init; +- (void)reset; + +- (NSNumber *)parseData:(NSString *)dataBuf forEnvironment:(NSDictionary *)env startingAt:(NSNumber *)startingPos; +- (NSNumber *)parseData:(NSString *)dataBuf forEnvironment:(NSDictionary *)env; + +- (BOOL)errorCond; +- (BOOL)finished; +- (NSNumber *)nread; + +- (void)finalize; + +@end + +// Describe enough of the StringIO interface to write to one +@interface RBStringIO : NSObject +- (void)write:(NSString *)dataBuf; +@end Added: ControlTower/trunk/ext/CTParser/CTParser.m =================================================================== --- ControlTower/trunk/ext/CTParser/CTParser.m (rev 0) +++ ControlTower/trunk/ext/CTParser/CTParser.m 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,194 @@ +/* + * This file is covered by the Ruby license. See COPYING for more details. + * Copyright (C) 2009-2010, Apple Inc. All rights reserved. + */ + +#import "CTParser.h" + +#pragma mark Parser Callbacks + +#define DEF_MAX_LENGTH(N, val) const size_t MAX_##N##_LENGTH = val + +#define VALIDATE_MAX_LENGTH(len, N)\ + if(len > MAX_##N##_LENGTH) {\ + [NSException raise:@"ParserFieldLengthError"\ + format:@"HTTP element " # N " is longer than the " # len " character allowed length."];\ + } + +#define PARSE_FIELD(field)\ + void parse_##field (void *env, const char *at, size_t length)\ + {\ + VALIDATE_MAX_LENGTH(length, field)\ + [(NSMutableDictionary *)env setObject:[[NSString alloc] initWithBytes:at\ + length:length\ + encoding:NSUTF8StringEncoding ]\ + forKey:@"" #field];\ + return;\ + } + +// Max field lengths +DEF_MAX_LENGTH(FIELD_NAME, 256); +DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024); +DEF_MAX_LENGTH(REQUEST_METHOD, 256); +DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12); +DEF_MAX_LENGTH(FRAGMENT, 1024); +DEF_MAX_LENGTH(PATH_INFO, 1024); +DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10)); +DEF_MAX_LENGTH(HTTP_VERSION, 256); +DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32))); + +void parse_HTTP_FIELD(void *env, const char *field, size_t flen, const char *value, size_t vlen) +{ + VALIDATE_MAX_LENGTH(flen, FIELD_NAME); + VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE); + [(NSMutableDictionary *)env setObject:[[NSString alloc] initWithBytes:value + length:vlen + encoding:NSUTF8StringEncoding] + forKey:[@"HTTP_" stringByAppendingString:[[NSString alloc] initWithBytes:field + length:flen + encoding:NSUTF8StringEncoding]]]; + return; +} + +// Parsing callback functions +PARSE_FIELD(REQUEST_METHOD); +PARSE_FIELD(REQUEST_URI); +PARSE_FIELD(FRAGMENT); +PARSE_FIELD(PATH_INFO); +PARSE_FIELD(QUERY_STRING); +PARSE_FIELD(HTTP_VERSION); + +void header_done(void *env, const char *at, size_t length) +{ + NSMutableDictionary *environment = (NSMutableDictionary *)env; + NSString *contentLength = [environment objectForKey:@"HTTP_CONTENT_LENGTH"]; + if (contentLength != nil) { + [environment setObject:contentLength forKey:@"CONTENT_LENGTH"]; + } + + NSString *contentType = [environment objectForKey:@"HTTP_CONTENT_TYPE"]; + if (contentType != nil) { + [environment setObject:contentType forKey:@"CONTENT_TYPE"]; + } + + [environment setObject:@"CGI/1.2" forKey:@"GATEWAY_INTERFACE"]; + + NSString *hostString = [environment objectForKey:@"HTTP_HOST"]; + NSString *serverName = nil; + NSString *serverPort = nil; + if (hostString != nil) { + NSRange colon_pos = [hostString rangeOfString:@":"]; + if (colon_pos.location != NSNotFound) { + serverName = [hostString substringToIndex:colon_pos.location]; + serverPort = [hostString substringFromIndex:(colon_pos.location + 1)]; + } else { + serverName = [NSString stringWithString:hostString]; + serverPort = @"80"; + } + [environment setObject:serverName forKey:@"SERVER_NAME"]; + [environment setObject:serverPort forKey:@"SERVER_PORT"]; + } + + [environment setObject:@"HTTP/1.1" forKey:@"SERVER_PROTOCOL"]; + [environment setObject:SERVER_SOFTWARE forKey:@"SERVER_SOFTWARE"]; + + // We don't do tls yet + [environment setObject:@"http" forKey:@"rack.url_scheme"]; + + // To satisfy Rack specs... + if ([environment objectForKey:@"QUERY_STRING"] == nil) { + [environment setObject:@"" forKey:@"QUERY_STRING"]; + } + + // If we've been given any part of the body, put it here + NSMutableString *body = [environment objectForKey:@"rack.input"]; + if (body != nil) { + [body appendString:[[NSString alloc] initWithBytes:at length:length encoding:NSASCIIStringEncoding]]; + } else { + NSLog(@"Hmm...you seem to have body data but no where to put it. That's probably an error."); + } + + return; +} + +@implementation CTParser + +@synthesize body = _body; + +- (id)init { + [super init]; + _parser = malloc(sizeof(http_parser)); + + // Setup the callbacks + _parser->http_field = parse_HTTP_FIELD; + _parser->request_method = parse_REQUEST_METHOD; + _parser->request_uri = parse_REQUEST_URI; + _parser->fragment = parse_FRAGMENT; + _parser->request_path = parse_PATH_INFO; + _parser->query_string = parse_QUERY_STRING; + _parser->http_version = parse_HTTP_VERSION; + _parser->header_done = header_done; + + http_parser_init(_parser); + return self; +} + +- (void)reset +{ + http_parser_init(_parser); + return; +} + + +- (NSNumber *)parseData:(NSString *)dataBuf forEnvironment:(NSMutableDictionary *)env startingAt:(NSNumber *)startingPos +{ + const char *data = [dataBuf UTF8String]; + size_t length = [dataBuf length]; + size_t offset = [startingPos unsignedLongValue]; + _parser->data = env; + + http_parser_execute(_parser, data, length, offset); + if (http_parser_has_error(_parser)) { + [NSException raise:@"CTParserError" format:@"Invalid HTTP format, parsing failed."]; + } + + NSNumber *headerLength = [NSNumber numberWithUnsignedLong:_parser->nread]; + VALIDATE_MAX_LENGTH([headerLength unsignedLongValue], HEADER); + return headerLength; +} + +- (NSNumber *)parseData:(NSString *)dataBuf forEnvironment:(NSDictionary *)env +{ + return [self parseData:dataBuf forEnvironment:env startingAt:0]; +} + +- (BOOL)errorCond +{ + return http_parser_has_error(_parser); +} + +- (BOOL)finished +{ + return http_parser_is_finished(_parser); +} + +- (NSNumber *)nread +{ + return [NSNumber numberWithInt:_parser->nread]; +} + +- (void)finalize +{ + if (_parser != NULL) + free(_parser); + [super finalize]; +} + +@end + +void +Init_CTParser(void) +{ + // Do nothing. This function is required by the MacRuby runtime when this + // file is compiled as a C extension bundle. +} Added: ControlTower/trunk/ext/CTParser/extconf.rb =================================================================== --- ControlTower/trunk/ext/CTParser/extconf.rb (rev 0) +++ ControlTower/trunk/ext/CTParser/extconf.rb 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,3 @@ +require 'mkmf' +$CFLAGS << ' -fobjc-gc -g ' +create_makefile("CTParser") Added: ControlTower/trunk/ext/CTParser/http11_parser.c =================================================================== --- ControlTower/trunk/ext/CTParser/http11_parser.c (rev 0) +++ ControlTower/trunk/ext/CTParser/http11_parser.c 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,1086 @@ +/** + * Copyright (c) 2005 Zed A. Shaw + * You can redistribute it and/or modify it under the same terms as Ruby. + */ +#include "http11_parser.h" +#include <stdio.h> +#include <assert.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> + +/* + * capitalizes all lower-case ASCII characters, + * converts dashes to underscores. + */ +static void snake_upcase_char(char *c) +{ + if (*c >= 'a' && *c <= 'z') + *c &= ~0x20; + else if (*c == '-') + *c = '_'; +} + +#define LEN(AT, FPC) (FPC - buffer - parser->AT) +#define MARK(M,FPC) (parser->M = (FPC) - buffer) +#define PTR_TO(F) (buffer + parser->F) + +static const int http_parser_start = 1; +static const int http_parser_first_final = 57; +static const int http_parser_error = 0; + +static const int http_parser_en_main = 1; + +int http_parser_init(http_parser *parser) { + int cs = 0; + + cs = http_parser_start; + parser->cs = cs; + parser->body_start = 0; + parser->content_len = 0; + parser->mark = 0; + parser->nread = 0; + parser->field_len = 0; + parser->field_start = 0; + + return(1); +} + + +size_t http_parser_execute(http_parser *parser, const char *buffer, size_t len, size_t off) { + const char *p, *pe; + int cs = parser->cs; + + assert(off <= len && "offset past end of buffer"); + + p = buffer+off; + pe = buffer+len; + + assert(*pe == '\0' && "pointer does not end on NUL"); + assert(pe - p == len - off && "pointers aren't same distance"); + + + + if ( p == pe ) + goto _out; + switch ( cs ) + { + case 1: + switch( (*p) ) { + case 36: goto tr0; + case 95: goto tr0; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto tr0; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto tr0; + } else + goto tr0; + goto st0; +st0: + goto _out0; +tr0: + MARK(mark, p); + goto st2; +st2: + if ( ++p == pe ) + goto _out2; + case 2: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st38; + case 95: goto st38; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st38; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st38; + } else + goto st38; + goto st0; +tr2: + if(parser->request_method != NULL) + parser->request_method(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st3; +st3: + if ( ++p == pe ) + goto _out3; + case 3: + switch( (*p) ) { + case 42: goto tr4; + case 43: goto tr5; + case 47: goto tr6; + case 58: goto tr7; + } + if ( (*p) < 65 ) { + if ( 45 <= (*p) && (*p) <= 57 ) + goto tr5; + } else if ( (*p) > 90 ) { + if ( 97 <= (*p) && (*p) <= 122 ) + goto tr5; + } else + goto tr5; + goto st0; +tr4: + MARK(mark, p); + goto st4; +st4: + if ( ++p == pe ) + goto _out4; + case 4: + switch( (*p) ) { + case 32: goto tr8; + case 35: goto tr9; + } + goto st0; +tr8: + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st5; +tr30: + if(parser->fragment != NULL) + parser->fragment(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st5; +tr40: + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p)); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st5; +tr51: + MARK(query_start, p); + if(parser->query_string != NULL) + parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p)); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st5; +tr55: + if(parser->query_string != NULL) + parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p)); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st5; +st5: + if ( ++p == pe ) + goto _out5; + case 5: + if ( (*p) == 72 ) + goto tr10; + goto st0; +tr10: + MARK(mark, p); + goto st6; +st6: + if ( ++p == pe ) + goto _out6; + case 6: + if ( (*p) == 84 ) + goto st7; + goto st0; +st7: + if ( ++p == pe ) + goto _out7; + case 7: + if ( (*p) == 84 ) + goto st8; + goto st0; +st8: + if ( ++p == pe ) + goto _out8; + case 8: + if ( (*p) == 80 ) + goto st9; + goto st0; +st9: + if ( ++p == pe ) + goto _out9; + case 9: + if ( (*p) == 47 ) + goto st10; + goto st0; +st10: + if ( ++p == pe ) + goto _out10; + case 10: + if ( 48 <= (*p) && (*p) <= 57 ) + goto st11; + goto st0; +st11: + if ( ++p == pe ) + goto _out11; + case 11: + if ( (*p) == 46 ) + goto st12; + if ( 48 <= (*p) && (*p) <= 57 ) + goto st11; + goto st0; +st12: + if ( ++p == pe ) + goto _out12; + case 12: + if ( 48 <= (*p) && (*p) <= 57 ) + goto st13; + goto st0; +st13: + if ( ++p == pe ) + goto _out13; + case 13: + if ( (*p) == 13 ) + goto tr18; + if ( 48 <= (*p) && (*p) <= 57 ) + goto st13; + goto st0; +tr18: + if(parser->http_version != NULL) + parser->http_version(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st14; +tr26: + if(parser->http_field != NULL) { + parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, p)); + } + goto st14; +st14: + if ( ++p == pe ) + goto _out14; + case 14: + if ( (*p) == 10 ) + goto st15; + goto st0; +st15: + if ( ++p == pe ) + goto _out15; + case 15: + switch( (*p) ) { + case 13: goto st16; + case 33: goto tr21; + case 124: goto tr21; + case 126: goto tr21; + } + if ( (*p) < 45 ) { + if ( (*p) > 39 ) { + if ( 42 <= (*p) && (*p) <= 43 ) + goto tr21; + } else if ( (*p) >= 35 ) + goto tr21; + } else if ( (*p) > 46 ) { + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto tr21; + } else if ( (*p) > 90 ) { + if ( 94 <= (*p) && (*p) <= 122 ) + goto tr21; + } else + goto tr21; + } else + goto tr21; + goto st0; +st16: + if ( ++p == pe ) + goto _out16; + case 16: + if ( (*p) == 10 ) + goto tr22; + goto st0; +tr22: + parser->body_start = p - buffer + 1; + if(parser->header_done != NULL) + parser->header_done(parser->data, p + 1, pe - p - 1); + goto _out57; + goto st57; +st57: + if ( ++p == pe ) + goto _out57; + case 57: + goto st0; +tr21: + MARK(field_start, p); + snake_upcase_char((char *)p); + goto st17; +tr23: + snake_upcase_char((char *)p); + goto st17; +st17: + if ( ++p == pe ) + goto _out17; + case 17: + switch( (*p) ) { + case 33: goto tr23; + case 58: goto tr24; + case 124: goto tr23; + case 126: goto tr23; + } + if ( (*p) < 45 ) { + if ( (*p) > 39 ) { + if ( 42 <= (*p) && (*p) <= 43 ) + goto tr23; + } else if ( (*p) >= 35 ) + goto tr23; + } else if ( (*p) > 46 ) { + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto tr23; + } else if ( (*p) > 90 ) { + if ( 94 <= (*p) && (*p) <= 122 ) + goto tr23; + } else + goto tr23; + } else + goto tr23; + goto st0; +tr24: + parser->field_len = LEN(field_start, p); + goto st18; +tr27: + MARK(mark, p); + goto st18; +st18: + if ( ++p == pe ) + goto _out18; + case 18: + switch( (*p) ) { + case 13: goto tr26; + case 32: goto tr27; + } + goto tr25; +tr25: + MARK(mark, p); + goto st19; +st19: + if ( ++p == pe ) + goto _out19; + case 19: + if ( (*p) == 13 ) + goto tr26; + goto st19; +tr9: + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st20; +tr41: + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p)); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st20; +tr52: + MARK(query_start, p); + if(parser->query_string != NULL) + parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p)); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st20; +tr56: + if(parser->query_string != NULL) + parser->query_string(parser->data, PTR_TO(query_start), LEN(query_start, p)); + if(parser->request_uri != NULL) + parser->request_uri(parser->data, PTR_TO(mark), LEN(mark, p)); + goto st20; +st20: + if ( ++p == pe ) + goto _out20; + case 20: + switch( (*p) ) { + case 32: goto tr30; + case 35: goto st0; + case 37: goto tr31; + case 127: goto st0; + } + if ( 0 <= (*p) && (*p) <= 31 ) + goto st0; + goto tr29; +tr29: + MARK(mark, p); + goto st21; +st21: + if ( ++p == pe ) + goto _out21; + case 21: + switch( (*p) ) { + case 32: goto tr30; + case 35: goto st0; + case 37: goto st22; + case 127: goto st0; + } + if ( 0 <= (*p) && (*p) <= 31 ) + goto st0; + goto st21; +tr31: + MARK(mark, p); + goto st22; +st22: + if ( ++p == pe ) + goto _out22; + case 22: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st23; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st23; + } else + goto st23; + goto st0; +st23: + if ( ++p == pe ) + goto _out23; + case 23: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st21; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st21; + } else + goto st21; + goto st0; +tr5: + MARK(mark, p); + goto st24; +st24: + if ( ++p == pe ) + goto _out24; + case 24: + switch( (*p) ) { + case 43: goto st24; + case 58: goto st25; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st24; + } else if ( (*p) > 57 ) { + if ( (*p) > 90 ) { + if ( 97 <= (*p) && (*p) <= 122 ) + goto st24; + } else if ( (*p) >= 65 ) + goto st24; + } else + goto st24; + goto st0; +tr7: + MARK(mark, p); + goto st25; +st25: + if ( ++p == pe ) + goto _out25; + case 25: + switch( (*p) ) { + case 32: goto tr8; + case 35: goto tr9; + case 37: goto st26; + case 127: goto st0; + } + if ( 0 <= (*p) && (*p) <= 31 ) + goto st0; + goto st25; +st26: + if ( ++p == pe ) + goto _out26; + case 26: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st27; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st27; + } else + goto st27; + goto st0; +st27: + if ( ++p == pe ) + goto _out27; + case 27: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st25; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st25; + } else + goto st25; + goto st0; +tr6: + MARK(mark, p); + goto st28; +st28: + if ( ++p == pe ) + goto _out28; + case 28: + switch( (*p) ) { + case 32: goto tr40; + case 35: goto tr41; + case 37: goto st29; + case 59: goto tr43; + case 63: goto tr44; + case 127: goto st0; + } + if ( 0 <= (*p) && (*p) <= 31 ) + goto st0; + goto st28; +st29: + if ( ++p == pe ) + goto _out29; + case 29: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st30; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st30; + } else + goto st30; + goto st0; +st30: + if ( ++p == pe ) + goto _out30; + case 30: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st28; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st28; + } else + goto st28; + goto st0; +tr43: + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p)); + goto st31; +st31: + if ( ++p == pe ) + goto _out31; + case 31: + switch( (*p) ) { + case 32: goto tr8; + case 35: goto tr9; + case 37: goto st32; + case 63: goto st34; + case 127: goto st0; + } + if ( 0 <= (*p) && (*p) <= 31 ) + goto st0; + goto st31; +st32: + if ( ++p == pe ) + goto _out32; + case 32: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st33; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st33; + } else + goto st33; + goto st0; +st33: + if ( ++p == pe ) + goto _out33; + case 33: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st31; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st31; + } else + goto st31; + goto st0; +tr44: + if(parser->request_path != NULL) + parser->request_path(parser->data, PTR_TO(mark), LEN(mark,p)); + goto st34; +st34: + if ( ++p == pe ) + goto _out34; + case 34: + switch( (*p) ) { + case 32: goto tr51; + case 35: goto tr52; + case 37: goto tr53; + case 127: goto st0; + } + if ( 0 <= (*p) && (*p) <= 31 ) + goto st0; + goto tr50; +tr50: + MARK(query_start, p); + goto st35; +st35: + if ( ++p == pe ) + goto _out35; + case 35: + switch( (*p) ) { + case 32: goto tr55; + case 35: goto tr56; + case 37: goto st36; + case 127: goto st0; + } + if ( 0 <= (*p) && (*p) <= 31 ) + goto st0; + goto st35; +tr53: + MARK(query_start, p); + goto st36; +st36: + if ( ++p == pe ) + goto _out36; + case 36: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st37; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st37; + } else + goto st37; + goto st0; +st37: + if ( ++p == pe ) + goto _out37; + case 37: + if ( (*p) < 65 ) { + if ( 48 <= (*p) && (*p) <= 57 ) + goto st35; + } else if ( (*p) > 70 ) { + if ( 97 <= (*p) && (*p) <= 102 ) + goto st35; + } else + goto st35; + goto st0; +st38: + if ( ++p == pe ) + goto _out38; + case 38: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st39; + case 95: goto st39; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st39; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st39; + } else + goto st39; + goto st0; +st39: + if ( ++p == pe ) + goto _out39; + case 39: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st40; + case 95: goto st40; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st40; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st40; + } else + goto st40; + goto st0; +st40: + if ( ++p == pe ) + goto _out40; + case 40: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st41; + case 95: goto st41; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st41; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st41; + } else + goto st41; + goto st0; +st41: + if ( ++p == pe ) + goto _out41; + case 41: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st42; + case 95: goto st42; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st42; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st42; + } else + goto st42; + goto st0; +st42: + if ( ++p == pe ) + goto _out42; + case 42: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st43; + case 95: goto st43; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st43; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st43; + } else + goto st43; + goto st0; +st43: + if ( ++p == pe ) + goto _out43; + case 43: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st44; + case 95: goto st44; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st44; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st44; + } else + goto st44; + goto st0; +st44: + if ( ++p == pe ) + goto _out44; + case 44: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st45; + case 95: goto st45; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st45; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st45; + } else + goto st45; + goto st0; +st45: + if ( ++p == pe ) + goto _out45; + case 45: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st46; + case 95: goto st46; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st46; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st46; + } else + goto st46; + goto st0; +st46: + if ( ++p == pe ) + goto _out46; + case 46: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st47; + case 95: goto st47; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st47; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st47; + } else + goto st47; + goto st0; +st47: + if ( ++p == pe ) + goto _out47; + case 47: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st48; + case 95: goto st48; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st48; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st48; + } else + goto st48; + goto st0; +st48: + if ( ++p == pe ) + goto _out48; + case 48: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st49; + case 95: goto st49; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st49; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st49; + } else + goto st49; + goto st0; +st49: + if ( ++p == pe ) + goto _out49; + case 49: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st50; + case 95: goto st50; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st50; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st50; + } else + goto st50; + goto st0; +st50: + if ( ++p == pe ) + goto _out50; + case 50: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st51; + case 95: goto st51; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st51; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st51; + } else + goto st51; + goto st0; +st51: + if ( ++p == pe ) + goto _out51; + case 51: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st52; + case 95: goto st52; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st52; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st52; + } else + goto st52; + goto st0; +st52: + if ( ++p == pe ) + goto _out52; + case 52: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st53; + case 95: goto st53; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st53; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st53; + } else + goto st53; + goto st0; +st53: + if ( ++p == pe ) + goto _out53; + case 53: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st54; + case 95: goto st54; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st54; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st54; + } else + goto st54; + goto st0; +st54: + if ( ++p == pe ) + goto _out54; + case 54: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st55; + case 95: goto st55; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st55; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st55; + } else + goto st55; + goto st0; +st55: + if ( ++p == pe ) + goto _out55; + case 55: + switch( (*p) ) { + case 32: goto tr2; + case 36: goto st56; + case 95: goto st56; + } + if ( (*p) < 48 ) { + if ( 45 <= (*p) && (*p) <= 46 ) + goto st56; + } else if ( (*p) > 57 ) { + if ( 65 <= (*p) && (*p) <= 90 ) + goto st56; + } else + goto st56; + goto st0; +st56: + if ( ++p == pe ) + goto _out56; + case 56: + if ( (*p) == 32 ) + goto tr2; + goto st0; + } +_out0: cs = 0; goto _out; +_out2: cs = 2; goto _out; +_out3: cs = 3; goto _out; +_out4: cs = 4; goto _out; +_out5: cs = 5; goto _out; +_out6: cs = 6; goto _out; +_out7: cs = 7; goto _out; +_out8: cs = 8; goto _out; +_out9: cs = 9; goto _out; +_out10: cs = 10; goto _out; +_out11: cs = 11; goto _out; +_out12: cs = 12; goto _out; +_out13: cs = 13; goto _out; +_out14: cs = 14; goto _out; +_out15: cs = 15; goto _out; +_out16: cs = 16; goto _out; +_out57: cs = 57; goto _out; +_out17: cs = 17; goto _out; +_out18: cs = 18; goto _out; +_out19: cs = 19; goto _out; +_out20: cs = 20; goto _out; +_out21: cs = 21; goto _out; +_out22: cs = 22; goto _out; +_out23: cs = 23; goto _out; +_out24: cs = 24; goto _out; +_out25: cs = 25; goto _out; +_out26: cs = 26; goto _out; +_out27: cs = 27; goto _out; +_out28: cs = 28; goto _out; +_out29: cs = 29; goto _out; +_out30: cs = 30; goto _out; +_out31: cs = 31; goto _out; +_out32: cs = 32; goto _out; +_out33: cs = 33; goto _out; +_out34: cs = 34; goto _out; +_out35: cs = 35; goto _out; +_out36: cs = 36; goto _out; +_out37: cs = 37; goto _out; +_out38: cs = 38; goto _out; +_out39: cs = 39; goto _out; +_out40: cs = 40; goto _out; +_out41: cs = 41; goto _out; +_out42: cs = 42; goto _out; +_out43: cs = 43; goto _out; +_out44: cs = 44; goto _out; +_out45: cs = 45; goto _out; +_out46: cs = 46; goto _out; +_out47: cs = 47; goto _out; +_out48: cs = 48; goto _out; +_out49: cs = 49; goto _out; +_out50: cs = 50; goto _out; +_out51: cs = 51; goto _out; +_out52: cs = 52; goto _out; +_out53: cs = 53; goto _out; +_out54: cs = 54; goto _out; +_out55: cs = 55; goto _out; +_out56: cs = 56; goto _out; + +_out: + parser->cs = cs; + parser->nread += p - (buffer + off); + + assert(p <= pe && "buffer overflow after parsing execute"); + assert(parser->nread <= len && "nread longer than length"); + assert(parser->body_start <= len && "body starts after buffer end"); + assert(parser->mark < len && "mark is after buffer end"); + assert(parser->field_len <= len && "field has length longer than whole buffer"); + assert(parser->field_start < len && "field starts after buffer end"); + + if(parser->body_start) { + /* final \r\n combo encountered so stop right here */ + + parser->nread++; + } + + return(parser->nread); +} + +int http_parser_finish(http_parser *parser) +{ + int cs = parser->cs; + + parser->cs = cs; + + if (http_parser_has_error(parser) ) { + return -1; + } else if (http_parser_is_finished(parser) ) { + return 1; + } else { + return 0; + } +} + +int http_parser_has_error(http_parser *parser) { + return parser->cs == http_parser_error; +} + +int http_parser_is_finished(http_parser *parser) { + return parser->cs == http_parser_first_final; +} Added: ControlTower/trunk/ext/CTParser/http11_parser.h =================================================================== --- ControlTower/trunk/ext/CTParser/http11_parser.h (rev 0) +++ ControlTower/trunk/ext/CTParser/http11_parser.h 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2005 Zed A. Shaw + * You can redistribute it and/or modify it under the same terms as Ruby. + */ + +#ifndef http11_parser_h +#define http11_parser_h + +#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); + +typedef struct http_parser { + int cs; + size_t body_start; + int content_len; + size_t nread; + size_t mark; + size_t field_start; + size_t field_len; + size_t query_start; + + void *data; + + field_cb http_field; + element_cb request_method; + element_cb request_uri; + element_cb fragment; + element_cb request_path; + element_cb query_string; + element_cb http_version; + element_cb header_done; + +} http_parser; + +int http_parser_init(http_parser *parser); +int http_parser_finish(http_parser *parser); +size_t http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off); +int http_parser_has_error(http_parser *parser); +int http_parser_is_finished(http_parser *parser); + +#define http_parser_nread(parser) (parser)->nread + +#endif Added: ControlTower/trunk/lib/control_tower/rack_socket.rb =================================================================== --- ControlTower/trunk/lib/control_tower/rack_socket.rb (rev 0) +++ ControlTower/trunk/lib/control_tower/rack_socket.rb 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,102 @@ +# This file is covered by the Ruby license. See COPYING for more details. +# Copyright (C) 2009-2010, Apple Inc. All rights reserved. + +require 'CTParser' + +module ControlTower + class RackSocket + READ_SIZE = 16 * 1024 + RACK_VERSION = 'rack.version'.freeze + VERSION = [1,0].freeze + + def initialize(host, port, server, concurrent) + @server = server + @socket = TCPServer.new(host, port) + @status = :closed # Start closed and give the server time to start + prepare_environment + + #if concurrent + # @env['rack.multithread'] = true + # @request_queue = Dispatch::Queue.concurrent + #else + # @env['rack.multithread'] = false + # @request_queue = Dispatch::Queue.new('com.apple.ControlTower.rack_socket_queue') + #end + #@request_group = Dispatch::Group.new + end + + def open + @status = :open + while (@status == :open) + connection = @socket.accept + + # TODO -- Concurrency doesn't quite work yet... + #@request_group.dispatch(@request_queue) do + req_data = parse!(connection, prepare_environment) + data = @server.handle_request(req_data) + data.each do |chunk| + connection.write chunk + end + connection.close + #end + end + end + + def close + @status = :close + + # You get 30 seconds to empty the request queue and get outa here! + Dispatch::Source.timer(30, 0, 1, Dispatch::Queue.concurrent) do + puts "Timed out waiting for connections to close" + exit 1 + end + #@request_group.wait + @socket.close + end + + + private + + def prepare_environment + { 'rack.errors' => $stderr, + 'rack.input' => '', + 'rack.multiprocess' => false, # No multiprocess, yet...probably never + 'rack.run_once' => false, + RACK_VERSION => VERSION } + end + + def parse!(connection, env) + parser = ::CTParser.new + data = "" + headers_done = false + content_length = 0 + + while (!headers_done || env['rack.input'].bytesize < content_length) do + select([connection], nil, nil, 1) + if headers_done + begin + data = connection.readpartial(READ_SIZE) + env['rack.input'] << data + rescue EOFError + break + end + else + data << connection.readpartial(READ_SIZE) + nread = parser.parseData(data, forEnvironment: env, startingAt: nread) + if parser.finished + headers_done = true + content_length = env['CONTENT_LENGTH'].to_i + end + end + end + + # Rack says "Make that a StringIO!" + body = Tempfile.new('control-tower-request-body-') + body << env['rack.input'] + body.rewind + env['rack.input'] = body + # Returning what we've got... + return env + end + end +end Added: ControlTower/trunk/lib/control_tower/server.rb =================================================================== --- ControlTower/trunk/lib/control_tower/server.rb (rev 0) +++ ControlTower/trunk/lib/control_tower/server.rb 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,69 @@ +# This file is covered by the Ruby license. See COPYING for more details. +# Copyright (C) 2009-2010, Apple Inc. All rights reserved. + +module ControlTower + class Server + def initialize(app, options) + @app = app + parse_options(options) + @socket = RackSocket.new(@host, @port, self, @concurrent) + end + + def start + trap 'INT' do + @socket.close + exit + end + + # Ok, let the server do it's thing + @socket.open + end + + def handle_request(env) + wrap_output(*@app.call(env)) + end + + private + + def parse_options(opt) + @port = (opt[:port] || 8080).to_i + @host = opt[:host] || `hostname`.chomp + @concurrent = opt[:concurrent] + end + + def wrap_output(status, headers, body) + # Unless somebody's already set it for us (or we don't need it), set the Content-Length + unless (status == -1 || + (100..199).include?(status) || + status == 204 || + status == 304 || + headers.has_key?("Content-Length")) + headers["Content-Length"] = if body.respond_to?(:each) + size = 0 + body.each { |x| size += x.bytesize } + size + else + body.bytesize + end + end + + # TODO -- We don't handle keep-alive connections yet + headers["Connection"] = 'close' + + headers_out = headers.map do |header, value| + "#{header}: #{value}\r\n" + end.join + + # Assemble our response... + chunks = ["HTTP/1.1 #{status}\r\n#{headers_out}\r\n"] + if body.respond_to?(:each) + body.each do |chunk| + chunks << chunk + end + else + chunks << body + end + chunks + end + end +end Added: ControlTower/trunk/lib/control_tower.rb =================================================================== --- ControlTower/trunk/lib/control_tower.rb (rev 0) +++ ControlTower/trunk/lib/control_tower.rb 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,11 @@ +# This file is covered by the Ruby license. See COPYING for more details. +# Copyright (C) 2009-2010, Apple Inc. All rights reserved. + +require 'rubygems' +require 'rack' +require 'socket' +require 'tempfile' +require File.join(File.dirname(__FILE__), 'control_tower', 'rack_socket') +require File.join(File.dirname(__FILE__), 'control_tower', 'server') + + Added: ControlTower/trunk/lib/rack/handler/control_tower.rb =================================================================== --- ControlTower/trunk/lib/rack/handler/control_tower.rb (rev 0) +++ ControlTower/trunk/lib/rack/handler/control_tower.rb 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,16 @@ +# This file is covered by the Ruby license. See COPYING for more details. +# Copyright (C) 2009-2010, Apple Inc. All rights reserved. + +require "control_tower" + +module Rack + module Handler + class ControlTower + def self.run(app, options={}) + server = ::ControlTower::Server.new(app, options) + yield server if block_given? + server.start + end + end + end +end Added: ControlTower/trunk/spec/ctparser_spec.rb =================================================================== --- ControlTower/trunk/spec/ctparser_spec.rb (rev 0) +++ ControlTower/trunk/spec/ctparser_spec.rb 2010-04-29 20:51:15 UTC (rev 3975) @@ -0,0 +1,67 @@ +# This file is covered by the Ruby license. See COPYING for more details. +# Copyright (C) 2009-2010, Apple Inc. All rights reserved. + +require "bacon" +require 'stringio' + +framework "CTParser" + +describe "Instantiating a new Parser" do + it "should return an object of class CTParser" do + parser = CTParser.new + parser.class.should == CTParser + end +end + +describe "Parsing a minimal header" do + before do + @env = { 'rack.input' => StringIO.new } + @parser = CTParser.new + @header = "GET / HTTP/1.1\r\n\r\n" + @read = @parser.parseData(@header, forEnvironment:@env) + end + + it "should parse the full header length" do + @read.should == @header.length + end + + it "should finish" do + @parser.finished.should == 1 + end + + it "should not have any errors" do + @parser.errorCond.should == 0 + end + + it "should have read as many bytes as it read" do + @parser.nread.should == @read + end + + it "should populate SERVER_PROTOCOL" do + @env['SERVER_PROTOCOL'].should == 'HTTP/1.1' + end + + it "should populate PATH_INFO" do + @env['PATH_INFO'].should == "/" + end + + it "should populate HTTP_VERSION" do + @env['HTTP_VERSION'].should == 'HTTP/1.1' + end + + it "should populate GATEWAY_INTERFACE" do + @env['GATEWAY_INTERFACE'].should == 'CGI/1.2' + end + + it "should populate REQUEST_METHOD" do + @env['REQUEST_METHOD'].should == 'GET' + end + + it "should not have generated any fragments" do + @env['FRAGMENT'].should.be.nil + end + + it "should not populate QUERY_STRING" do + @env['QUERY_STRING'].should.be.empty + end +end Property changes on: ControlTower/trunk/spec/ctparser_spec.rb ___________________________________________________________________ Added: svn:executable + *
participants (1)
-
source_changes@macosforge.org