control_tower 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Copyright (c) 2005 Zed A. Shaw
3
+ * You can redistribute it and/or modify it under the same terms as Ruby.
4
+ */
5
+
6
+ #ifndef http11_parser_h
7
+ #define http11_parser_h
8
+
9
+ #include <sys/types.h>
10
+
11
+ #if defined(_WIN32)
12
+ #include <stddef.h>
13
+ #endif
14
+
15
+ typedef void (*element_cb)(void *data, const char *at, size_t length);
16
+ typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
17
+
18
+ typedef struct http_parser {
19
+ int cs;
20
+ size_t body_start;
21
+ int content_len;
22
+ size_t nread;
23
+ size_t mark;
24
+ size_t field_start;
25
+ size_t field_len;
26
+ size_t query_start;
27
+
28
+ void *data;
29
+
30
+ field_cb http_field;
31
+ element_cb request_method;
32
+ element_cb request_uri;
33
+ element_cb fragment;
34
+ element_cb request_path;
35
+ element_cb query_string;
36
+ element_cb http_version;
37
+ element_cb header_done;
38
+
39
+ } http_parser;
40
+
41
+ int http_parser_init(http_parser *parser);
42
+ int http_parser_finish(http_parser *parser);
43
+ size_t http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off);
44
+ int http_parser_has_error(http_parser *parser);
45
+ int http_parser_is_finished(http_parser *parser);
46
+
47
+ #define http_parser_nread(parser) (parser)->nread
48
+
49
+ #endif
@@ -0,0 +1,9 @@
1
+ # This file is covered by the Ruby license. See COPYING for more details.
2
+ # Copyright (C) 2009-2010, Apple Inc. All rights reserved.
3
+
4
+ require 'socket'
5
+ require 'tempfile'
6
+ $: << File.join(File.dirname(__FILE__), 'control_tower', 'vendor')
7
+ require 'rack'
8
+ require File.join(File.dirname(__FILE__), 'control_tower', 'rack_socket')
9
+ require File.join(File.dirname(__FILE__), 'control_tower', 'server')
@@ -0,0 +1,181 @@
1
+ # This file is covered by the Ruby license. See COPYING for more details.
2
+ # Copyright (C) 2009-2010, Apple Inc. All rights reserved.
3
+
4
+ framework 'Foundation'
5
+ require 'CTParser'
6
+ require 'stringio'
7
+
8
+ CTParser # Making sure the Objective-C class is pre-loaded
9
+
10
+ module ControlTower
11
+ class RackSocket
12
+ VERSION = [1,0].freeze
13
+
14
+ def initialize(host, port, server, concurrent)
15
+ @app = server.app
16
+ @socket = TCPServer.new(host, port)
17
+ @socket.listen(50)
18
+ @status = :closed # Start closed and give the server time to start
19
+
20
+ if concurrent
21
+ @multithread = true
22
+ @request_queue = Dispatch::Queue.concurrent
23
+ puts "Caution! Wake turbulance from heavy aircraft landing on parallel runway.\n(Parallel Request Action ENABLED!)"
24
+ else
25
+ @multithread = false
26
+ @request_queue = Dispatch::Queue.new('com.apple.ControlTower.rack_socket_queue')
27
+ end
28
+ @request_group = Dispatch::Group.new
29
+ end
30
+
31
+ def open
32
+ @status = :open
33
+ while (@status == :open)
34
+ connection = @socket.accept
35
+
36
+ @request_queue.async(@request_group) do
37
+ env = { 'rack.errors' => $stderr,
38
+ 'rack.multiprocess' => false,
39
+ 'rack.multithread' => @multithread,
40
+ 'rack.run_once' => false,
41
+ 'rack.version' => VERSION }
42
+ resp = nil
43
+ x_sendfile_header = 'X-Sendfile'
44
+ x_sendfile = nil
45
+ begin
46
+ request_data = parse!(connection, env)
47
+ if request_data
48
+ request_data['REMOTE_ADDR'] = connection.addr[3]
49
+ status, headers, body = @app.call(request_data)
50
+
51
+ # If there's an X-Sendfile header, we'll use sendfile(2)
52
+ if headers.has_key?(x_sendfile_header)
53
+ x_sendfile = headers[x_sendfile_header]
54
+ x_sendfile = ::File.open(x_sendfile, 'r') unless x_sendfile.kind_of? IO
55
+ x_sendfile_size = x_sendfile.stat.size
56
+ headers.delete(x_sendfile_header)
57
+ headers['Content-Length'] = x_sendfile_size
58
+ end
59
+
60
+ # Unless somebody's already set it for us (or we don't need it), set the Content-Length
61
+ unless (status == -1 ||
62
+ (status >= 100 and status <= 199) ||
63
+ status == 204 ||
64
+ status == 304 ||
65
+ headers.has_key?('Content-Length'))
66
+ headers['Content-Length'] = if body.respond_to?(:each)
67
+ size = 0
68
+ body.each { |x| size += x.bytesize }
69
+ size
70
+ else
71
+ body.bytesize
72
+ end
73
+ end
74
+
75
+ # TODO -- We don't handle keep-alive connections yet
76
+ headers['Connection'] = 'close'
77
+
78
+ resp = "HTTP/1.1 #{status}\r\n"
79
+ headers.each do |header, value|
80
+ resp << "#{header}: #{value}\r\n"
81
+ end
82
+ resp << "\r\n"
83
+
84
+ # Start writing the response
85
+ connection.write resp
86
+
87
+ # Write the body
88
+ if x_sendfile
89
+ connection.sendfile(x_sendfile, 0, x_sendfile_size)
90
+ elsif body.respond_to?(:each)
91
+ body.each do |chunk|
92
+ connection.write chunk
93
+ end
94
+ else
95
+ connection.write body
96
+ end
97
+
98
+ else
99
+ $stderr.puts "Error: No request data received!"
100
+ end
101
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE, Errno::EINVAL
102
+ $stderr.puts "Error: Connection terminated!"
103
+ rescue Object => e
104
+ if resp.nil? && !connection.closed?
105
+ connection.write "HTTP/1.1 400\r\n\r\n"
106
+ else
107
+ # We have a response, but there was trouble sending it:
108
+ $stderr.puts "Error: Problem transmitting data -- #{e.inspect}"
109
+ $stderr.puts e.backtrace.join("\n")
110
+ end
111
+ ensure
112
+ # We should clean up after our tempfile, if we used one.
113
+ input = env['rack.input']
114
+ input.unlink if input.class == Tempfile
115
+ connection.close rescue nil
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ def close
122
+ @status = :close
123
+
124
+ # You get 30 seconds to empty the request queue and get outa here!
125
+ Dispatch::Source.timer(30, 0, 1, Dispatch::Queue.concurrent) do
126
+ $stderr.puts "Timed out waiting for connections to close"
127
+ exit 1
128
+ end
129
+ @request_group.wait
130
+ @socket.close
131
+ end
132
+
133
+
134
+ private
135
+
136
+ def parse!(connection, env)
137
+ parser = Thread.current[:http_parser] ||= CTParser.new
138
+ parser.reset
139
+ data = NSMutableData.alloc.init
140
+ data.increaseLengthBy(1) # add sentinel
141
+ parsing_headers = true # Parse headers first
142
+ nread = 0
143
+ content_length = 0
144
+ content_uploaded = 0
145
+ connection_handle = NSFileHandle.alloc.initWithFileDescriptor(connection.fileno)
146
+
147
+ while (parsing_headers || content_uploaded < content_length) do
148
+ # Read the availableData on the socket and give up if there's nothing
149
+ incoming_bytes = connection_handle.availableData
150
+ return nil if incoming_bytes.length == 0
151
+
152
+ # Until the headers are done being parsed, we'll parse them
153
+ if parsing_headers
154
+ data.setLength(data.length - 1) # Remove sentinel
155
+ data.appendData(incoming_bytes)
156
+ data.increaseLengthBy(1) # Add sentinel
157
+ nread = parser.parseData(data, forEnvironment: env, startingAt: nread)
158
+ if parser.finished == 1
159
+ parsing_headers = false # We're done, now on to receiving the body
160
+ content_length = env['CONTENT_LENGTH'].to_i
161
+ content_uploaded = env['rack.input'].length
162
+ end
163
+ else # Done parsing headers, now just collect request body:
164
+ content_uploaded += incoming_bytes.length
165
+ env['rack.input'].appendData(incoming_bytes)
166
+ end
167
+ end
168
+
169
+ if content_length > 1024 * 1024
170
+ body_file = Tempfile.new('control-tower-request-body-')
171
+ NSFileHandle.alloc.initWithFileDescriptor(body_file.fileno).writeData(env['rack.input'])
172
+ body_file.rewind
173
+ env['rack.input'] = body_file
174
+ else
175
+ env['rack.input'] = StringIO.new(NSString.alloc.initWithData(env['rack.input'], encoding: NSASCIIStringEncoding))
176
+ end
177
+ # Returning what we've got...
178
+ return env
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,32 @@
1
+ # This file is covered by the Ruby license. See COPYING for more details.
2
+ # Copyright (C) 2009-2010, Apple Inc. All rights reserved.
3
+
4
+ module ControlTower
5
+ class Server
6
+ attr_reader :app
7
+
8
+ def initialize(app, options)
9
+ @app = app
10
+ parse_options(options)
11
+ @socket = RackSocket.new(@host, @port, self, @concurrent)
12
+ end
13
+
14
+ def start
15
+ trap 'INT' do
16
+ @socket.close
17
+ exit
18
+ end
19
+
20
+ # Ok, let the server do it's thing
21
+ @socket.open
22
+ end
23
+
24
+ private
25
+
26
+ def parse_options(opt)
27
+ @port = (opt[:port] || 8080).to_i
28
+ @host = opt[:host] || `hostname`.chomp
29
+ @concurrent = opt[:concurrent]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # This file is covered by the Ruby license. See COPYING for more details.
2
+ # Copyright (C) 2009-2010, Apple Inc. All rights reserved.
3
+
4
+ require "control_tower"
5
+
6
+ module Rack
7
+ module Handler
8
+ class ControlTower
9
+ def self.run(app, options={})
10
+ app = Rack::Chunked.new(Rack::ContentLength.new(app))
11
+ server = ::ControlTower::Server.new(app, options)
12
+ yield server if block_given?
13
+ server.start
14
+ end
15
+ end
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: control_tower
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ version: "1.0"
9
+ platform: ruby
10
+ authors:
11
+ - MacRuby Team
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+
16
+ date: 2010-09-20 00:00:00 -07:00
17
+ default_executable:
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: rack
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "="
25
+ - !ruby/object:Gem::Version
26
+ segments:
27
+ - 1
28
+ - 2
29
+ - 1
30
+ version: 1.2.1
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: " Control Tower is a Rack-based HTTP server designed to work with MacRuby. It can\n be used by calling to its Rack::Handler class, or by running the control_tower\n executable with a Rackup configuration file (see the control tower help for more\n details).\n"
34
+ email: macruby-devel@lists.macosforge.org
35
+ executables:
36
+ - control_tower
37
+ extensions:
38
+ - ext/CTParser/extconf.rb
39
+ extra_rdoc_files: []
40
+
41
+ files:
42
+ - lib/control_tower.rb
43
+ - lib/control_tower/rack_socket.rb
44
+ - lib/control_tower/server.rb
45
+ - lib/rack/handler/control_tower.rb
46
+ - bin/control_tower
47
+ - ext/CTParser/http11_parser.h
48
+ - ext/CTParser/http11_parser.c
49
+ - ext/CTParser/CTParser.h
50
+ - ext/CTParser/CTParser.m
51
+ - ext/CTParser/extconf.rb
52
+ has_rdoc: true
53
+ homepage: http://www.macruby.org
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options: []
58
+
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.3.6
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: A Rack-based HTTP server for MacRuby
82
+ test_files: []
83
+