-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial revision of ControlTower project
git-svn-id: http://svn.macosforge.org/repository/ruby/ControlTower/trunk@3975 23306eb0-4c56-4727-a40e-e92c0eb68959
- Loading branch information
Showing
13 changed files
with
1,780 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require 'mkmf' | ||
$CFLAGS << ' -fobjc-gc -g ' | ||
create_makefile("CTParser") |
Oops, something went wrong.