Skip to content

Commit

Permalink
initial revision of ControlTower project
Browse files Browse the repository at this point in the history
git-svn-id: http://svn.macosforge.org/repository/ruby/ControlTower/trunk@3975 23306eb0-4c56-4727-a40e-e92c0eb68959
  • Loading branch information
lrz committed Apr 29, 2010
1 parent 6c00e56 commit 2862945
Show file tree
Hide file tree
Showing 13 changed files with 1,780 additions and 0 deletions.
9 changes: 9 additions & 0 deletions README
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.
80 changes: 80 additions & 0 deletions Rakefile
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

57 changes: 57 additions & 0 deletions bin/control_tower
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
37 changes: 37 additions & 0 deletions ext/CTParser/CTParser.h
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
194 changes: 194 additions & 0 deletions ext/CTParser/CTParser.m
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.
}
3 changes: 3 additions & 0 deletions ext/CTParser/extconf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require 'mkmf'
$CFLAGS << ' -fobjc-gc -g '
create_makefile("CTParser")
Loading

0 comments on commit 2862945

Please sign in to comment.