# # File:: web.rb # Description:: Web shell # # Author:: David Muir # Date:: 28 February 2009 # #---------------------------------------------------------------------------- # Uses #---------------------------------------------------------------------------- require 'builder/shell/command' require 'builder/shell/shell' require 'os/path' require 'erb' require 'webrick' require 'webrick/httpservlet/erbhandler.rb' #---------------------------------------------------------------------------- # Implementation #---------------------------------------------------------------------------- module Pipeline module Builder module Shell # # == Description # Custom HTTP request/response class so we can spit out ERB pages and # process POST forms to enqueue commands onto the server. # # It is expected that HTML form submit buttons are called the name of # the command to execute and the value set to any parameters to be sent # to that command. # # === References # http://microjet.ath.cx/webrickguide/html/ # c:\ruby\lib\ruby\1.8\webrick\httpservlet\erbhandler.rb # # === Example Usage # WEBrick::HTTPServlet::FileHandler.add_handler( 'rhtml', WebShellServlet ) # module WebShellServlet # # Respond to HTTP GET requests. We steal the ERBHandler's do_GET # method as we want to use ERB templates for ease. # def do_GET( req, resp ) unless defined?( ERB ) @logger.warn( "#{self.class}: ERB not defined." ) throw HTTPStatus::Forbidden, "WebShellServlet cannot work." end begin filename = OS::Path::combine( Dir::pwd, req.path ) data = nil if ( File::exists?( filename ) && File::file?( filename ) ) then data = File::open( filename, 'r' ) do |io| io.read; end else # Default to @web_page filename = @web_page data = File::open( @web_page, 'r' ) do |io| io.read; end end # Process everything with ERB resp.body = evaluate( ERB.new( data ), req, resp ) resp['Content-Type'] = WEBrick::HTTPUtils::mime_type( filename, @server.config[:MimeTypes] ) rescue StandardError => ex puts "Ex: #{ex.message}" ex.backtrace.each do |m| puts m; end throw rescue Exception => ex @logger.error( ex ) throw HTTPStatus::InternalServerError, ex.message end end # # Respond to HTTP POST requests. We steal the ERBHandler's do_GET # method as we want to use ERB templates for ease. # # We differ from the GET request processing by processing form # data. # def do_POST( req, resp ) unless defined?( ERB ) @logger.warn( "#{self.class}: ERB not defined." ) throw HTTPStatus::Forbidden, "WebShellServlet cannot work." end puts "POST body: #{req.body}" cmd = CommandInst::from_POST( req.body, @@commands ) enqueue_command( cmd, WebShell::HOST, WebShell::USER ) begin filename = OS::Path::combine( Dir::pwd, req.path ) data = nil if ( File::exists?( filename ) && File::file?( filename ) ) then data = File::open( filename, 'r' ) do |io| io.read; end else # Default to @web_page filename = @web_page data = File::open( @web_page, 'r' ) do |io| io.read; end end # Process everything with ERB resp.body = evaluate( ERB.new( data ), req, resp ) resp['Content-Type'] = WEBrick::HTTPUtils::mime_type( filename, @server.config[:MimeTypes] ) rescue StandardError => ex puts "Ex: #{ex.message}" ex.backtrace.each do |m| puts m; end throw rescue Exception => ex @logger.error( ex ) throw HTTPStatus::InternalServerError, ex.message end end #-------------------------------------------------------------------- # Class Methods #-------------------------------------------------------------------- # # Override self.get_instance to reload this file for each request. # This saves us restarting the server/builder during testing. # def WebShellServlet::get_instance( config, *options ) load( __FILE__ ) WebShellServlet.new( config, *options ) end # # To parse POST String objects into CommandInst objects to enqueue # we need to validate against an Array of Command objects. We store # that here. # def WebShellServlet::set_commands( commands ) throw ArgumentError.new( "Invalid Hash of valid commands (#{commands.class})." ) \ unless ( commands.is_a?( Hash ) ) @@commands = commands end #-------------------------------------------------------------------- # Private Methods #-------------------------------------------------------------------- private def evaluate( erb, servlet_request, servlet_response ) Module.new.module_eval do meta_vars = servlet_request.meta_vars query = servlet_request.query erb.result( binding ) end end end # # == Description # Web implementation of the Builder Shell. This shell creates an # internal Webrick HTTP server and allows users to send commands to # the builder application. # # === Example Usage # # commands = [ # Command.new( 'sync', Command::NO_SHORTCUTS, 'Sync special directory.', [ Param.new( 'dir', String ) ], sync_proc ), # Command.new( 'enable', [ 'e' ], 'Enable internal feature.', [ Param.new( 'flag', String ) ], enable_proc ), # Command.new( 'disable', [ 'd' ], 'Disable internal feature.', [ Param.new( 'flag', String ) ], disable_proc ), # Command.new( 'status', [ 's' ], 'Display status information.', Param::NONE, status_proc ) # ] # shell = Console.new( commands ) # server = WebShell.new( commands ) # ... # server.start( ) # # Start up our interactive shell in calling thread. # shell.start( ) # class WebShell < ShellBase include WebShellServlet DEFAULT_WEB_PORT = 81 HOST = ENV['COMPUTERNAME'] USER = ENV['USERNAME'] attr_reader :active attr_reader :web_port attr_reader :web_page def initialize( commands, web_port = DEFAULT_WEB_PORT, web_page = './index.rhtml', server = '127.0.0.1', port = CommandQueue::DEFAULT_PORT ) super( commands, server, port ) @web_port = web_port @web_page = File::expand_path( web_page ) @active = false WebShellServlet::set_commands( @commands ) end # # Start the HTTP server and web UI so we can display status # information. # def start( &block ) @active = true mime_file = OS::Path::combine( OS::Path::get_directory( __FILE__ ), 'mime.types' ) @mime_types = WEBrick::HTTPUtils::load_mime_types( mime_file ) @mime_types.store( 'rhtml', 'text/html' ) Thread.new() do Thread.current[:name] = THREAD_NAME begin # Start Webrick HTTP Server # WEBrick::HTTPServlet::FileHandler.add_handler( 'rhtml', WebShellServlet ) @server = WEBrick::HTTPServer.new( :Port => @web_port, :DocumentRoot => @web_page, :MimeTypes => @mime_types ) @server.mount_proc( '/' ) do |req, resp| case req.request_method when 'GET' do_GET( req, resp ) when 'POST' do_POST( req, resp ) end end trap( 'INT' ) do @server.shutdown() end @server.start( ) puts "WEBrick started." rescue Exception => ex puts "WebShell Exception: #{ex.message}" ex.backtrace.each do |msg| puts msg; end end end end #-------------------------------------------------------------------- # Private #-------------------------------------------------------------------- private THREAD_NAME = '__BuilderFramework_WebShell' end end # Shell module end # Builder module end # Pipeline module # web.rb