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.
7 LIBDIR = File.dirname(__FILE__)
8 autoload :Runner, LIBDIR + '/ebb/runner'
9 autoload :FFI, LIBDIR + '/../src/ebb_ext'
11 def self.start_server(app, options={})
12 if options.has_key?(:fileno)
13 fd = options[:fileno].to_i
14 FFI::server_listen_on_fd(fd)
15 log.puts "Ebb is listening on file descriptor #{fd}"
16 elsif options.has_key?(:unix_socket)
17 socketfile = options[:unix_socket]
18 FFI::server_listen_on_unix_socket(socketfile)
19 log.puts "Ebb is listening on unix socket #{socketfile}"
21 port = (options[:port] || 4001).to_i
22 FFI::server_listen_on_port(port)
23 log.puts "Ebb is listening at http://0.0.0.0:#{port}/"
25 log.puts "Ebb PID #{Process.pid}"
28 trap('INT') { stop_server }
31 FFI::server_process_connections()
32 while client = FFI::server_waiting_clients.shift
33 if app.respond_to?(:deferred?) and !app.deferred?(client.env)
36 Thread.new(client) { |c| process(app, c) }
40 FFI::server_unlisten()
47 def self.stop_server()
51 def self.process(app, client)
53 status, headers, body = app.call(client.env)
56 client.write_status(status)
58 # Add Content-Length to the headers.
59 if headers['Content-Length'].nil? and
60 headers.respond_to?(:[]=) and
61 body.respond_to?(:length) and
64 headers['Content-Length'] = body.length.to_s
67 # Decide if we should keep the connection alive or not
68 if headers['Connection'].nil?
69 if headers['Content-Length'] and client.should_keep_alive?
70 headers['Connection'] = 'Keep-Alive'
72 headers['Connection'] = 'close'
77 headers.each { |field, value| client.write_header(field, value) }
80 if body.kind_of?(String)
81 client.write_body(body)
83 body.each { |p| client.write_body(p) }
87 log.puts "Ebb Error! #{e.class} #{e.message}"
88 log.puts e.backtrace.join("\n")
102 attr_reader :fd, :body_head, :content_length
104 'SERVER_NAME' => '0.0.0.0',
106 'SERVER_SOFTWARE' => "Ebb-Ruby #{Ebb::VERSION}",
107 'SERVER_PROTOCOL' => 'HTTP/1.1',
108 'rack.version' => [0, 1],
109 'rack.errors' => STDERR,
110 'rack.url_scheme' => 'http',
111 'rack.multiprocess' => false,
112 'rack.run_once' => false
117 env = FFI::client_env(self).update(BASE_ENV)
118 env['rack.input'] = RequestBody.new(self)
123 def write_status(status)
125 FFI::client_write_status(self, s, HTTP_STATUS_CODES[s])
129 FFI::client_write_body(self, data)
132 def write_header(field, value)
133 value.send(value.is_a?(String) ? :each_line : :each) do |v|
134 FFI::client_write_header(self, field, v.chomp)
139 FFI::client_release(self)
143 FFI::client_set_keep_alive(self)
146 def should_keep_alive?
147 if env['HTTP_VERSION'] == 'HTTP/1.0'
148 return true if env['HTTP_CONNECTION'] =~ /Keep-Alive/i
150 return true unless env['HTTP_CONNECTION'] =~ /close/i
157 def initialize(client)
158 @content_length = client.content_length
160 @body_head = StringIO.new(client.body_head)
161 if @body_head.length < @content_length
162 @socket = IO.new(client.fd)
169 to_read = len.nil? ? @content_length - @total_read : min(len, @content_length - @total_read)
170 return nil if to_read == 0 or @body_head.nil?
171 unless out = @body_head.read(to_read)
172 return nil if @socket.nil?
173 out = @socket.read(to_read)
175 @total_read += out.length
189 HTTP_STATUS_CODES = {
191 101 => 'Switching Protocols',
195 203 => 'Non-Authoritative Information',
197 205 => 'Reset Content',
198 206 => 'Partial Content',
199 300 => 'Multiple Choices',
200 301 => 'Moved Permanently',
201 302 => 'Moved Temporarily',
203 304 => 'Not Modified',
205 400 => 'Bad Request',
206 401 => 'Unauthorized',
207 402 => 'Payment Required',
210 405 => 'Method Not Allowed',
211 406 => 'Not Acceptable',
212 407 => 'Proxy Authentication Required',
213 408 => 'Request Time-out',
216 411 => 'Length Required',
217 412 => 'Precondition Failed',
218 413 => 'Request Entity Too Large',
219 414 => 'Request-URI Too Large',
220 415 => 'Unsupported Media Type',
221 500 => 'Internal Server Error',
222 501 => 'Not Implemented',
223 502 => 'Bad Gateway',
224 503 => 'Service Unavailable',
225 504 => 'Gateway Time-out',
226 505 => 'HTTP Version not supported'
234 def self.run(app, options={})
235 ::Ebb.start_server(app, options)
241 # cause i don't want to create an array