control_tower 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+