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 log.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 body.each { |p| client.write(p) }
77 log.puts "Ebb Error! #{e.class} #{e.message}"
78 log.puts e.backtrace.join("\n")
91 # This array is created and manipulated in the C extension.
92 def FFI.waiting_clients
98 'SERVER_NAME' => '0.0.0.0',
100 'SERVER_SOFTWARE' => "Ebb #{Ebb::VERSION}",
101 'SERVER_PROTOCOL' => 'HTTP/1.1',
102 'rack.version' => [0, 1],
103 'rack.errors' => STDERR,
104 'rack.url_scheme' => 'http',
105 'rack.multiprocess' => false,
106 'rack.run_once' => false
110 env = FFI::client_env(self).update(BASE_ENV)
111 env['rack.input'] = RequestBody.new(self)
115 def write_status(status)
117 FFI::client_write_status(self, s, HTTP_STATUS_CODES[s])
121 FFI::client_write(self, data)
124 def write_header(field, value)
125 value.send(value.is_a?(String) ? :each_line : :each) do |v|
126 FFI::client_write_header(self, field, v.chomp)
131 FFI::client_set_body_written(self, true)
134 def begin_transmission
135 FFI::client_begin_transmission(self)
139 FFI::client_release(self)
144 def initialize(client)
154 while(chunk = read(10*1024)) do
159 FFI::client_read_input(@client, len)
173 @io ||= StringIO.new(read)
178 HTTP_STATUS_CODES = {
180 101 => 'Switching Protocols',
184 203 => 'Non-Authoritative Information',
186 205 => 'Reset Content',
187 206 => 'Partial Content',
188 300 => 'Multiple Choices',
189 301 => 'Moved Permanently',
190 302 => 'Moved Temporarily',
192 304 => 'Not Modified',
194 400 => 'Bad Request',
195 401 => 'Unauthorized',
196 402 => 'Payment Required',
199 405 => 'Method Not Allowed',
200 406 => 'Not Acceptable',
201 407 => 'Proxy Authentication Required',
202 408 => 'Request Time-out',
205 411 => 'Length Required',
206 412 => 'Precondition Failed',
207 413 => 'Request Entity Too Large',
208 414 => 'Request-URI Too Large',
209 415 => 'Unsupported Media Type',
210 500 => 'Internal Server Error',
211 501 => 'Not Implemented',
212 502 => 'Bad Gateway',
213 503 => 'Service Unavailable',
214 504 => 'Gateway Time-out',
215 505 => 'HTTP Version not supported'