class Gopher::Application
main application class for a gopher server. holds all the methods/data required to interact with clients.
Constants
- ACCESS_LOG_PATTERN
The output pattern we will use to generate access logs
Attributes
Public Class Methods
generate a method which we will use to run routes. this is based on generate_method as used by sinatra. @see github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb @param [String] method_name name to use for the method @yield block to use for the method
# File lib/gopher2000/base.rb, line 469 def generate_method(method_name, &block) define_method(method_name, &block) method = instance_method method_name remove_method method_name method end
Sanitizes a gopher selector
# File lib/gopher2000/base.rb, line 455 def sanitize_selector(raw) "/#{raw}".dup. strip. # Strip whitespace sub(/\/$/, ''). # Strip last rslash sub(/^\/*/, '/'). # Strip extra lslashes gsub(/\.+/, '.') # Don't want consecutive dots! end
Public Instance Methods
turn a path string with optional keys (/foo/:bar/:boo) into a regexp which will be used when searching for a route
@param [String] path the path to compile
# File lib/gopher2000/base.rb, line 434 def compile(path) keys = [] pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) } pattern.gsub!(/((:\w+)|\*)/) do |match| if match == "*" keys << 'splat' "(.*?)" else keys << $2[1..-1] "([^/?#]+)" end end [/^#{pattern}$/, keys] end
compile a route
# File lib/gopher2000/base.rb, line 420 def compile!(path, &block) method_name = path route_method = Application.generate_method(method_name, &block) pattern, keys = compile path [ pattern, keys, route_method ] end
output a debugging message
# File lib/gopher2000/base.rb, line 480 def debug_log(x) @@debug_logger ||= ::Logging.logger(STDERR) @@debug_logger.debug x end
are we in debugging mode?
# File lib/gopher2000/base.rb, line 67 def debug_mode? config[:debug] == true end
specify a default route to handle requests if no other route exists @yield a block to handle the default route
@example render a template
default_route do render :template end
# File lib/gopher2000/base.rb, line 161 def default_route(&block) @default_route = Application.generate_method("DEFAULT_ROUTE", &block) end
find and run the first route which matches the incoming request @param [Request] req Gopher::Request
object
# File lib/gopher2000/base.rb, line 201 def dispatch(req) debug_log(req) response = Response.new @request = req if ! @request.valid? response.body = handle_invalid_request response.code = :error elsif @request.url? response.body = handle_url(@request) response.code = :success else begin debug_log("do lookup for #{@request.selector}") @params, block = lookup(@request.selector) # # call the block that handles this lookup # response.body = block.bind(self).call response.code = :success rescue Gopher::NotFoundError => e debug_log("#{@request.selector} -- not found") response.body = handle_not_found response.code = :missing rescue Exception => e debug_log("#{@request.selector} -- error") debug_log(e.inspect) debug_log(e.backtrace) response.body = handle_error(e) response.code = :error end end access_log(req, response) response end
return the application environment
# File lib/gopher2000/base.rb, line 60 def env config[:env] ||= 'development' end
get the id of the template that will be used when rendering an error @return name of error template
# File lib/gopher2000/base.rb, line 355 def error_template menus.include?(:error) ? :error : :'internal/error' end
find a template @param [String/Symbol] t name of the template @return template block and the class context it should use
# File lib/gopher2000/base.rb, line 310 def find_template(t) x = menus[t] if x return x, Gopher::Rendering::Menu end x = text_templates[t] if x return x, Gopher::Rendering::Text end end
add a glob to the end of this string, if there’s not one already
# File lib/gopher2000/base.rb, line 413 def globify(p) p =~ /\*/ ? p : "#{p}/?*".gsub("//", "/") end
Add helpers to the Base renedering class, which allows them to be called when outputting the results of an action. Here’s the code in Sinatra for reference:
Makes the methods defined in the block and in the Modules given in ‘extensions` available to the handlers and templates
def helpers(*extensions, &block) class_eval(&block) if block_given? include(*extensions) if extensions.any? end
target - What class should receive the helpers – defaults to Gopher::Rendering::Base
, which will make it available when rendering block – a block which declares the helpers you want. for example:
helpers do
def foo; "FOO"; end
end
# File lib/gopher2000/base.rb, line 395 def helpers(target = Gopher::Application, &block) target.class_eval(&block) end
return the host we will use when outputting gopher menus
# File lib/gopher2000/base.rb, line 46 def host config[:host] ||= '0.0.0.0' end
get the id of the template that will be used when rendering an invalid request @return name of invalid_request template
# File lib/gopher2000/base.rb, line 373 def invalid_request_template menus.include?(:invalid_request) ? :invalid_request : :'internal/invalid_request' end
lookup an incoming path
@param [String] selector the selector path of the incoming request
# File lib/gopher2000/base.rb, line 170 def lookup(selector) unless routes.nil? routes.each do |pattern, keys, block| if match = pattern.match(selector) match = match.to_a url = match.shift params = to_params_hash(keys, match) # # @todo think about this # @params = params return params, block end end end unless @default_route.nil? return {}, @default_route end raise Gopher::NotFoundError end
mount a directory for browsing via gopher
@param [Hash] path A hash specifying the path your route will answer to, and the filesystem path to use ‘/route’ => ‘/home/path/etc’ @param [Hash] opts a hash of options for the mount. Primarily this is a filter, which will restrict the list files outputted. example: :filter => ‘*.jpg’ @param [Class] klass The class that should be used to handle this mount. You could write and use a custom handler if desired
@example mount the directory ‘/home/user/foo’ at the gopher path ‘/files’, and only show JPG files:
mount '/files' => '/home/user/foo', :filter => '*.jpg'
# File lib/gopher2000/base.rb, line 108 def mount(path, opts = {}, klass = Gopher::Handlers::DirectoryHandler) debug_log "MOUNT #{path} #{opts.inspect}" opts[:mount_point] = path handler = klass.new(opts) handler.application = self # # add a route for the mounted class # route(globify(path)) do # when we call, pass the params and request object for this # particular request handler.call(params, request) end end
should we use non-blocking operations? for now, defaults to false if in debug mode, true if we’re not in debug mode (presumably, in some sort of production state. HAH! Gopher
servers in production)
# File lib/gopher2000/base.rb, line 405 def non_blocking? config.key?(:non_blocking) ? config[:non_blocking] : ! debug_mode? end
specify a template to be used for missing requests
# File lib/gopher2000/base.rb, line 301 def not_found(&block) menu :not_found, &block end
get the id of the template that will be used when rendering a not found error @return name of not_found
template
# File lib/gopher2000/base.rb, line 347 def not_found_template menus.include?(:not_found) ? :not_found : :'internal/not_found' end
return the port we will use when outputting gopher menus
# File lib/gopher2000/base.rb, line 53 def port config[:port] ||= 70 end
reload scripts if needed
# File lib/gopher2000/base.rb, line 84 def reload_stale reload_check = should_reload? self.last_reload = Time.now return if ! reload_check reset! self.scripts.each do |f| debug_log "reload #{f}" load f end end
Find the desired template and call it within the proper context @param [String/Symbol] template name of the template to render @param [Array] arguments optional arguments to be passed to template @return result of rendering
# File lib/gopher2000/base.rb, line 327 def render(template, *arguments) # # find the right renderer we need # block, handler = find_template(template) raise TemplateNotFound if block.nil? ctx = handler.new(self) ctx.params = @params ctx.request = @request ctx.instance_exec(*arguments, &block) end
reset the app. clear out any routes, templates, config values, etc. this is used during the load process
# File lib/gopher2000/base.rb, line 27 def reset! self.routes = [] self.menus = {} self.text_templates = {} self.scripts ||= [] self.config ||= { :debug => false, :host => "0.0.0.0", :port => 70 } register_defaults self end
define a route. @param [String] path the path your route will answer to. This is basically a URI path @yield a block that handles your route
@example respond with a simple string
route '/path' do "hi, welcome to /path" end
@example respond by rendering a template
route '/render' do render :template end
# File lib/gopher2000/base.rb, line 141 def route(path, &block) selector = Gopher::Application.sanitize_selector(path) sig = compile!(selector, &block) debug_log("Add route for #{selector}") self.routes ||= [] self.routes << sig end
check if our script has been updated since the last reload
# File lib/gopher2000/base.rb, line 75 def should_reload? ! last_reload.nil? && self.scripts.any? do |f| File.mtime(f) > last_reload end end
Define a template which will be used for outputting text. This is not strictly required for outputting text, but it gives you access to the methods defined in Gopher::Rendering::Text
for wrapping strings, adding simple headers, etc.
@param [String/Symbol] name the name of the template. This is what identifies the template when making a call to render
@yield a block which will output the menu. This block is executed within an instance of Gopher::Rendering::Text
and will have access to all of its methods. @example simple example
text :hello do big_header "Hello There!" block "Really long text... ... the end" br end
# File lib/gopher2000/base.rb, line 294 def text(name, &block) text_templates[name.to_sym] = block end
get the id of the template that will be used when rendering an html page @return name of error template
# File lib/gopher2000/base.rb, line 364 def url_template menus.include?(:html) ? :html : :'internal/url' end
Protected Instance Methods
write out an entry to our access log
# File lib/gopher2000/base.rb, line 575 def access_log(request, response) return if access_log_dest.nil? @@access_logger ||= init_access_log code = response.respond_to?(:code) ? response.code.to_s : "success" size = response.respond_to?(:size) ? response.size : response.length output = [request.ip_address, request.selector, request.input, code.to_s, size].join("\t") @@access_logger.debug output end
where should we store access logs? if nil, don’t store them at all @return logfile path
# File lib/gopher2000/base.rb, line 549 def access_log_dest config.has_key?(:access_log) ? config[:access_log] : nil end
# File lib/gopher2000/base.rb, line 535 def handle_error(e) render error_template, e end
# File lib/gopher2000/base.rb, line 539 def handle_invalid_request render invalid_request_template end
# File lib/gopher2000/base.rb, line 527 def handle_not_found render not_found_template end
# File lib/gopher2000/base.rb, line 531 def handle_url(request) render url_template, request end
initialize a Logger for tracking hits to the server
# File lib/gopher2000/base.rb, line 556 def init_access_log return if access_log_dest.nil? log = ::Logging.logger['access_log'] pattern = ::Logging.layouts.pattern(:pattern => ACCESS_LOG_PATTERN) log.add_appenders( ::Logging.appenders.rolling_file(access_log_dest, :level => :debug, :age => 'daily', :layout => pattern) ) log end
set up some default templates to handle errors, missing templates, etc.
# File lib/gopher2000/base.rb, line 491 def register_defaults menu :'internal/not_found' do error "Sorry, #{@request.selector} was not found" end menu :'internal/error' do |details| error "Sorry, there was an error #{details}" end menu :'internal/invalid_request' do error "invalid request" end menu :'internal/url' do output = <<-EOHTML <html> <head> <meta http-equiv="refresh" content="5;URL=#{@request.url}"> </head> <body> <p> You are following a link from gopher to a website. If your browser supports it, you will be automatically taken to the web site shortly. If you do not get sent there, please click <a href="#{@request.url}">here</a>. </p> <p> The URL linked is: <a href="#{@request.url}">#{@request.url}</a>. </p> <p>Have a nice day!</p> </body> </html> EOHTML output end end
zip up two arrays of keys and values from an incoming request
# File lib/gopher2000/base.rb, line 590 def to_params_hash(keys,values) hash = {} keys.size.times { |i| hash[ keys[i].to_sym ] = values[i] } hash end