require 'webrick' require 'stringio' require 'nitro/cgi' require 'nitro/context' require 'nitro/dispatcher' # Speeds things up, more comaptible with OSX. Socket.do_not_reverse_lookup = true module Nitro # Helper methods for the WebrickAdapter. class Webrick class << self attr_accessor :webrick # Start the Webrick adapter. def start(server) if RUBY_PLATFORM !~ /mswin32/ wblog = WEBrick::BasicLog::new('/dev/null') elsif server.access_log wblog = WEBrick::BasicLog::new(server.access_log) elsif File.exist? 'log' wblog = WEBrick::BasicLog::new('log/access.log') else wblog = STDERR end webrick_options = server.options.dup require 'webrick/https' if webrick_options[:SSLEnable] webrick_options.update( :BindAddress => server.address, :Port => server.port, :DocumentRoot => server.public_root, :AccessLog => [ [wblog, WEBrick::AccessLog::COMMON_LOG_FORMAT], [wblog, WEBrick::AccessLog::REFERER_LOG_FORMAT] ] ) enable_record_mode($record_session_filename) if $record_session_filename enable_playback_mode($playback_session_filename) if $playback_session_filename @webrick = WEBrick::HTTPServer.new(webrick_options) trap('INT') { stop } @webrick.mount('/', WebrickAdapter, server) initialize_webrick(server) @webrick.start end # Stop the Webrick adapter. def stop @webrick.shutdown end # Override this method to perform customized webrick # initialization. def initialize_webrick(server) end # Enables session recording. The recorded data can be used # for automatic app testing by means of the playback mode. def enable_record_mode(filename = 'session.yaml') Logger.info "Recording application server session to '#{filename}'." require 'facets/core/file/self/create' $record_session = [] $last_record_time = Time.now Nitro::WebrickAdapter.class_eval %{ def do_GET(req, res) record_context(req, res) handle(req, res) end alias_method :do_POST, :do_GET def record_context(req, res) delta = Time.now - $last_record_time $last_record_time = Time.now $record_session << [delta, req, res] end } at_exit do File.create(filename, YAML.dump($record_session)) end end # Playback a recorded session. Typically used for testing. def enable_playback_mode(filename = 'session.yaml') Logger.info "Playing back application server session from '#{filename}'." $playback_session = YAML.load_file(filename) $playback_exception_count = 0 WEBrick::HTTPServer.class_eval %{ def start(&block) run(nil) end def run(sock) while true delta, req, res = $playback_session.shift if delta sleep(delta) begin service(req, res) rescue Object => ex $playback_exception_count += 1 p '---', ex end else return end end end } at_exit do puts "\n\n" puts "Playback raised #$playback_exception_count exceptions.\n" puts "\n" end end end end # A special handler for Xhtml files. class XhtmlFileHandler < WEBrick::HTTPServlet::DefaultFileHandler def do_GET(req, res) res['content-type'] = 'text/html' res.body = 'Permission denied' end end # A Webrick Adapter for Nitro. Webrick is a pure Ruby web server # included in the default Ruby distribution. The Webrick Adapter # is the prefered adapter in development/debug environments. It # is also extremely easy to setup. # # However, for live/production environments, you should prefer # a more performant adapter like FCGI or SCGI. class WebrickAdapter < WEBrick::HTTPServlet::AbstractServlet include WEBrick def initialize(webrick, server) @server = server @server.options[:HandlerTable] = { 'xhtml' => XhtmlFileHandler } # Handles static resources. Useful when running # a stand-alone webrick server. @file_handler = WEBrick::HTTPServlet::FileHandler.new( webrick, server.public_root, server.options ) end # Handle a static file. Also handles cached pages. def handle_file(req, res) begin rewrite(req) @file_handler.do_GET(req, res) return true rescue WEBrick::HTTPStatus::PartialContent, WEBrick::HTTPStatus::NotModified => err res.set_error(err) return true rescue Object => ex return false ensure unrewrite(req) end end # Handle the request. def handle(req, res) unless handle_file(req, res) path = req.request_uri.path begin path = req.request_uri.path context = Context.new(@server) context.in = StringIO.new(req.body || "") context.headers = {} req.header.each { |h, v| context.headers[h.upcase] = v.first } context.headers.update(req.meta_vars) # gmosx: make compatible with fastcgi. context.headers['REQUEST_URI'].slice!(/http:\/\/(.*?)\//) context.headers['REQUEST_URI'] = '/' + context.headers['REQUEST_URI'] Cgi.parse_params(context) Cgi.parse_cookies(context) context.render(path) res.status = context.status res.instance_variable_set(:@header, context.response_headers || {}) res.instance_variable_set(:@cookies, context.response_cookies || {}) res.body = context.out res.chunked = true if context.out.is_a?(IO) and context["SERVER_PROTOCOL"] == "HTTP/1.1" context.close ensure $autoreload_dirty = false Og.manager.put_store if defined?(Og) and Og.respond_to?(:manager) and Og.manager end end end alias do_GET handle alias do_POST handle # Try to rewrite the path to a filename. def rewrite(req) if req.path_info == '/' req.instance_variable_set(:@path_info, '/index.html') elsif req.path_info =~ /^([^.]+)$/ req.instance_variable_set(:@path_info, "#{$1}/index.html") end end # Rewrite back to the original path. def unrewrite(req) if req.path_info == '/index.html' req.instance_variable_set(:@path_info, '/') elsif req.path_info =~ /^([^.]+)\/index.html$/ req.instance_variable_set(:@path_info, $1) end end end end # * George Moschovitis # * Guillaume Pierronnet # * Bryan Soto