1 # Ruby Binding to the Ebb Web Server
2 # Copyright (c) 2008 Ry Dahl. This software is released under the MIT License.
3 # See README file for details.
6 LIBDIR = File.dirname(__FILE__)
7 require Ebb::LIBDIR + '/../src/ebb_ext'
8 autoload :Runner, LIBDIR + '/ebb/runner'
10 def self.start_server(app, options={})
11 port = (options[:port] || 4001).to_i
12 if options.has_key?(:threaded_processing)
13 threaded_processing = options[:threaded_processing] ? true : false
15 threaded_processing = true
18 Client::BASE_ENV['rack.multithread'] = threaded_processing
20 FFI::server_listen_on_port(port)
22 #trap('INT') { stop_server }
24 puts "Ebb listening at http://0.0.0.0:#{port}/ (#{threaded_processing ? 'threaded' : 'sequential'} processing, PID #{Process.pid})"
27 FFI::server_process_connections()
28 while client = FFI::waiting_clients.shift
29 if threaded_processing
30 Thread.new(client) { |c| process(app, c) }
36 FFI::server_unlisten()
43 def self.stop_server()
47 def self.process(app, client)
49 status, headers, body = app.call(client.env)
53 headers = {'Content-Type' => 'text/plain'}
54 body = "Internal Server Error\n"
57 client.write_status(status)
59 if headers.respond_to?(:[]=) and body.respond_to?(:length) and status != 304
60 headers['Connection'] = 'close'
61 headers['Content-Length'] = body.length.to_s
64 headers.each { |field, value| client.write_header(field, value) }
67 if body.kind_of?(String)
70 client.begin_transmission()
72 client.begin_transmission()
73 client.body.each { |p| write(p) }
77 puts "Ebb Error! #{e.class} #{e.message}"
78 puts e.backtrace.join("\n")
83 # This array is created and manipulated in the C extension.
84 def FFI.waiting_clients
90 'SERVER_NAME' => '0.0.0.0',
92 'SERVER_SOFTWARE' => "Ebb #{Ebb::VERSION}",
93 'SERVER_PROTOCOL' => 'HTTP/1.1',
94 'rack.version' => [0, 1],
95 'rack.errors' => STDERR,
96 'rack.url_scheme' => 'http',
97 'rack.multiprocess' => false,
98 'rack.run_once' => false
102 env = FFI::client_env(self).update(BASE_ENV)
103 env['rack.input'] = RequestBody.new(self)
107 def write_status(status)
109 FFI::client_write_status(self, s, HTTP_STATUS_CODES[s])
113 FFI::client_write(self, data)
116 def write_header(field, value)
117 value.send(value.is_a?(String) ? :each_line : :each) do |v|
118 FFI::client_write_header(self, field, v.chomp)
123 FFI::client_set_body_written(self, true)
126 def begin_transmission
127 FFI::client_begin_transmission(self)
131 FFI::client_release(self)
136 def initialize(client)
146 while(chunk = read(10*1024)) do
151 FFI::client_read_input(@client, len)
165 @io ||= StringIO.new(read)
170 HTTP_STATUS_CODES = {
172 101 => 'Switching Protocols',
176 203 => 'Non-Authoritative Information',
178 205 => 'Reset Content',
179 206 => 'Partial Content',
180 300 => 'Multiple Choices',
181 301 => 'Moved Permanently',
182 302 => 'Moved Temporarily',
184 304 => 'Not Modified',
186 400 => 'Bad Request',
187 401 => 'Unauthorized',
188 402 => 'Payment Required',
191 405 => 'Method Not Allowed',
192 406 => 'Not Acceptable',
193 407 => 'Proxy Authentication Required',
194 408 => 'Request Time-out',
197 411 => 'Length Required',
198 412 => 'Precondition Failed',
199 413 => 'Request Entity Too Large',
200 414 => 'Request-URI Too Large',
201 415 => 'Unsupported Media Type',
202 500 => 'Internal Server Error',
203 501 => 'Not Implemented',
204 502 => 'Bad Gateway',
205 503 => 'Service Unavailable',
206 504 => 'Gateway Time-out',
207 505 => 'HTTP Version not supported'