# Ruby Binding to the Ebb Web Server # Copyright (c) 2008 Ry Dahl. This software is released under the MIT License. # See README file for details. require 'stringio' module Ebb LIBDIR = File.dirname(__FILE__) require Ebb::LIBDIR + '/../src/ebb_ext' autoload :Runner, LIBDIR + '/ebb/runner' def self.start_server(app, options={}) port = (options[:port] || 4001).to_i if options.has_key?(:threaded_processing) threaded_processing = options[:threaded_processing] ? true : false else threaded_processing = true end Client::BASE_ENV['rack.multithread'] = threaded_processing FFI::server_listen_on_port(port) @running = true trap('INT') { stop_server } log.puts "Ebb listening at http://0.0.0.0:#{port}/ (#{threaded_processing ? 'threaded' : 'sequential'} processing, PID #{Process.pid})" while @running FFI::server_process_connections() while client = FFI::waiting_clients.shift if threaded_processing Thread.new(client) { |c| process(app, c) } else process(app, client) end end end FFI::server_unlisten() end def self.running? FFI::server_open? end def self.stop_server() @running = false end def self.process(app, client) begin status, headers, body = app.call(client.env) rescue raise if $DEBUG status = 500 headers = {'Content-Type' => 'text/plain'} body = "Internal Server Error\n" end client.write_status(status) if headers.respond_to?(:[]=) and body.respond_to?(:length) and status != 304 headers['Connection'] = 'close' headers['Content-Length'] = body.length.to_s end headers.each { |field, value| client.write_header(field, value) } client.write("\r\n") if body.kind_of?(String) client.write(body) client.body_written() client.begin_transmission() else client.begin_transmission() body.each { |p| client.write(p) } client.body_written() end rescue => e log.puts "Ebb Error! #{e.class} #{e.message}" log.puts e.backtrace.join("\n") ensure client.release end @@log = STDOUT def self.log=(output) @@log = output end def self.log @@log end # This array is created and manipulated in the C extension. def FFI.waiting_clients @waiting_clients end class Client BASE_ENV = { 'SERVER_NAME' => '0.0.0.0', 'SCRIPT_NAME' => '', 'SERVER_SOFTWARE' => "Ebb #{Ebb::VERSION}", 'SERVER_PROTOCOL' => 'HTTP/1.1', 'rack.version' => [0, 1], 'rack.errors' => STDERR, 'rack.url_scheme' => 'http', 'rack.multiprocess' => false, 'rack.run_once' => false } def env env = FFI::client_env(self).update(BASE_ENV) env['rack.input'] = RequestBody.new(self) env end def write_status(status) s = status.to_i FFI::client_write_status(self, s, HTTP_STATUS_CODES[s]) end def write(data) FFI::client_write(self, data) end def write_header(field, value) value.send(value.is_a?(String) ? :each_line : :each) do |v| FFI::client_write_header(self, field, v.chomp) end end def body_written FFI::client_set_body_written(self, true) end def begin_transmission FFI::client_begin_transmission(self) end def release FFI::client_release(self) end end class RequestBody def initialize(client) @client = client end def read(len = nil) if @io @io.read(len) else if len.nil? s = '' while(chunk = read(10*1024)) do s << chunk end s else FFI::client_read_input(@client, len) end end end def gets io.gets end def each(&block) io.each(&block) end def io @io ||= StringIO.new(read) end end HTTP_STATUS_CODES = { 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Moved Temporarily', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported' }.freeze end