Permalink
Browse files

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...
1 parent c4990fa commit ce4c0dd00e1cc147c64bc3aa88396adf0b58af2d lsansonetti@apple.com committed Apr 29, 2010
View
@@ -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.
View
@@ -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
+
View
@@ -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
@@ -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
@@ -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.
+}
@@ -0,0 +1,3 @@
+require 'mkmf'
+$CFLAGS << ' -fobjc-gc -g '
+create_makefile("CTParser")
Oops, something went wrong.

0 comments on commit ce4c0dd

Please sign in to comment.