Class: Monorail::Request

Inherits:
Object
  • Object
show all
Defined in:
lib/monorail/monorail_webd.rb

Overview

class Request

Defined Under Namespace

Classes: DirectoryAlias, DirectoryBrowsed, FileNotFound, InvalidVerb, TemplateNotFound, Unauthorized, UndefinedController, UndefinedControllerModule

Constant Summary collapse

@@authpage_directory =

authentication-page parameters These are set by configuration. @@authpage_directory is a real directory on the filesystem containing a monorail controller that generates a login page. @@authpage_controller is the name of the controller to invoke. If either of these is not initialized, then accesses requiring authentication will just fail with a 403 error.

nil
@@authpage_controller =
nil
@@use_sessions =

use_sessions This is a class method that sets the class variable @@use_sessions. When true, a session will automatically be created and maintained for each user. It will automatically be made available to controllers and page templates. Sessions may be used without authentication. Authentication REQUIRES sessions. Sessions are NOT used by default. Session cookies are hardcoded here in a class variable. Make it configurable someday.

false
"monorail_session"
@@authorization_proc =

authorization_proc This is a proc object which expects two parameters, a user and a password. It must be specified before authorization will work. This permits pluggable auth/az methods.

nil
@@mime_mappings =

mime mappings

{
  "gif" => "image/gif",
  "jpg" => "image/jpeg",
  "jpeg" => "image/jpeg",
  "txt" => "text/plain",
  "html" => "text/html",
  "js" => "text/javascript",
  "css" => "text/css",
}
@@verbose =

verbose flag Defaults false, triggers debugging info to stderr. Needs to be configurable.

false
@@debug =

debug flag When set, this will select behaviors appropriate for development. For example, controller files will be loaded on every request instead of just required.

false
@@default_pathinfo =

Default pathinfo, if specified, causes us to redirect requests to “/” TODO: We need some kind of handling for favicon.ico and robots.txt. Maybe we need to detect when path_info contains only a single branch and does not specify the name of a known controller. THIS IS CURRENTLY INCOMPLETE.

nil
@@directory_aliases =

directory_aliases class variable is an array of DirectoryAlias objects

[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRequest

initialize



447
448
449
450
451
# File 'lib/monorail/monorail_webd.rb', line 447

def initialize
  @headers = {"content-type" => "text/html"}
  @cookies = []
  @http_response_code = 200
end

Instance Attribute Details

#http_response_codeObject (readonly)

Returns the value of attribute http_response_code.



233
234
235
# File 'lib/monorail/monorail_webd.rb', line 233

def http_response_code
  @http_response_code
end

Class Method Details

.authentication_page(actual, controller) ⇒ Object

authentication_page Called by a configurator to specify the real directory and controller name of a single (global) page that will generate an auth challenge.



258
259
260
# File 'lib/monorail/monorail_webd.rb', line 258

def Request::authentication_page actual, controller
  @@authpage_directory,@@authpage_controller = actual,controller
end

.authorization_proc(pr) ⇒ Object



285
286
287
# File 'lib/monorail/monorail_webd.rb', line 285

def Request::authorization_proc pr
  @@authorization_proc = pr
end

.debug(d) ⇒ Object



322
323
324
# File 'lib/monorail/monorail_webd.rb', line 322

def Request::debug d
  @@debug = d
end

.directory_alias(prefix, actual, responder, authreq) ⇒ Object

Request::directory_alias This is used to specify directories. Could also do it via a config file, that might be better. Each entry consists of a prefix name which is head-matched against incoming HTTP requests, an actual pathname in the filesystem, which should be FQ, a responder type (:directory and :monorail are currently defined), and an authentication-required flag (T/F). There is one oddity: we support a notion of a “default” directory, where the prefix is “/”. Now this array of directory aliases is searched in order, but there is a problem with the default directory because a prefix of “/” will head-match any request. So as a special case, we ENSURE here that any such directory will be added to the bottom of the list. Use a sort instead of a tail-inspection because there could be more than one default directory specified (even though that would make no sense).



439
440
441
442
# File 'lib/monorail/monorail_webd.rb', line 439

def Request::directory_alias prefix, actual, responder, authreq
  @@directory_aliases << DirectoryAlias.new( prefix, actual, responder, authreq )
  @@directory_aliases.sort! {|a,b| ((a.prefix == '/') ? 1 : 0) <=> ((b.prefix == '/') ? 1 : 0) }
end

.mime_mapping(suffix, mimetype) ⇒ Object



301
302
303
# File 'lib/monorail/monorail_webd.rb', line 301

def Request::mime_mapping suffix, mimetype
  @mime_mappings [suffix] = mimetype
end

.set_default_pathinfo(pi) ⇒ Object



333
334
335
# File 'lib/monorail/monorail_webd.rb', line 333

def self.set_default_pathinfo pi
  @@default_pathinfo = pi
end

.use_sessions(sess) ⇒ Object



274
275
276
# File 'lib/monorail/monorail_webd.rb', line 274

def Request::use_sessions sess
  @@use_sessions = sess
end

.verbose(v) ⇒ Object



312
313
314
# File 'lib/monorail/monorail_webd.rb', line 312

def Request::verbose v
  @@verbose = v
end

Instance Method Details

#check_authorizationObject

check_authorization



512
513
514
515
516
517
518
519
520
521
# File 'lib/monorail/monorail_webd.rb', line 512

def check_authorization
  if @session
    if @session.authorized?
      true
    elsif @@authorization_proc and @@authorization_proc.call( @session.username, @session.password )
      @session.authorize
      true
    end
  end
end

#compute_content_typeObject

compute_content_type This is probably too crude and will need to be modified. We look at the tail of the path_info and infer a mime type. This is really only appropriate for requests fulfilled out of the filesystem. For script-generated responses, they will want to define content-type manually.



695
696
697
698
699
700
701
702
703
704
705
706
707
# File 'lib/monorail/monorail_webd.rb', line 695

def compute_content_type
  hdr = "text/html"
  path_tail = if @cgi
    if @cgi.path_info =~ /\.([^\.]+)$/i
      $1
    end
  end
  if path_tail and @@mime_mappings.has_key?(path_tail)
    hdr = @@mime_mappings[path_tail]
  end

  @headers["content-type"] = hdr
end

#compute_file_etag(filename) ⇒ Object

compute_file_etag We concatenate the inode mtime and size from a filename and MD5-hash them. We expect valid input so throw an exception on error.



530
531
532
# File 'lib/monorail/monorail_webd.rb', line 530

def compute_file_etag filename
  MD5.new( "#{File.mtime( filename ).to_i}::#{File.size( filename )}" ).to_s
end

#generate_contentObject

generate_content



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/monorail/monorail_webd.rb', line 477

def generate_content
  if @cgi.path_info == '/' and @@default_pathinfo
    return redirect_to( @@default_pathinfo )
  end

  diralias = @@directory_aliases.detect {|da| da.match_request @cgi.path_info }

  diralias or raise FileNotFound

  if diralias.auth_required? and !check_authorization
    if @@authpage_directory and @@authpage_controller
      generate_content_from_monorail( @@authpage_directory, @@authpage_controller )
    else
      raise Unauthorized
    end

  else
    # here, we're either authorized or no auth is required. Gen the page.
    case diralias.processor
    when :directory
      generate_content_from_directory( diralias.translate_pathname( @cgi.path_info ))
    when :monorail
      generate_content_from_monorail( diralias.actual, diralias.get_path_tail( @cgi.path_info ))
    else
      raise FileNotFound
    end

  end

end

#generate_content_from_directory(filename) ⇒ Object

generate_content_from_directory We come here to generate content by reading a file on the filesystem.



538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
# File 'lib/monorail/monorail_webd.rb', line 538

def generate_content_from_directory filename
  if filename =~ /[\/]+$/
    @@verbose and $stderr.puts "failing directory request: #{filename}"
    raise DirectoryBrowsed
  end
  if File.exist?(filename) && !File.directory?(filename)
    etag = compute_file_etag( filename )
    if if_none_match = ENV["IF_NONE_MATCH"] and if_none_match == etag
      @@verbose and $stderr.puts "fulfilling directory request (Etag): #{filename}"
      @http_response_code = 304
      ""
    else
      @@verbose and $stderr.puts "fulfilling directory request: #{filename}"
      @headers['ETag'] = etag
      compute_content_type
      File.read filename
    end
  else
    raise FileNotFound
  end
end

#generate_content_from_monorail(path_prefix, pathname) ⇒ Object

generate_content_from_monorail We load up a ruby file based on the name in the path_info. OBSERVE: there are no subdirectories. The ruby module needs to be in the top subdirectory under the actual pathname. Any rendered pages will be in subdirectory “pages” which is a security feature. Since we won’t let a URL specify a controller file below the top-level, that means the files which define page templates are not accessible by URL. Observe that there is a debug path available when mixin in the controller personality, but it hardcodes that the controller filename end in .rb.

Raises:



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/monorail/monorail_webd.rb', line 575

def generate_content_from_monorail path_prefix, pathname
  @@verbose and $stderr.puts "fulfilling monorail request: #{path_prefix} :: #{pathname}"
  paths = pathname.split(/[\/]+/)
  (action = paths.shift || "index") and action.gsub!(/[\.]/, "_")

  verb = paths.shift || "index"

  # Before mixing in the personality, make sure the request verb
  # isn't already present in this class. This prevents people calling
  # things like initialize.
  raise InvalidVerb if self.respond_to?( verb )
  
  # load action handler
  begin
    if @@debug
      load File.join( path_prefix, action ) + ".rb"
    else
      require File.join( path_prefix, action )
    end
    instance_eval "extend Controller_#{action}"
  rescue LoadError
    raise UndefinedController
  rescue NameError
    raise UndefinedControllerModule
  end

  # Now throw something if the requested verb has NOT been mixed in.
  raise InvalidVerb unless self.respond_to?( verb )

  @headers ['Pragma'] = "no-cache"
  @headers ['Expires'] = "-1"
  @headers ['Cache-control'] = "no-cache"

  @extra_paths = paths || []
  #@path_prefix = File.join( path_prefix, "pages" )
  @path_prefix = File.join( path_prefix, action )
  instance_eval verb

end

#generate_responseObject

generate_response



644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
# File 'lib/monorail/monorail_webd.rb', line 644

def generate_response
  content = begin
    timeout(3) {
      @cgi = CGI.new
      initialize_session
      read_post_contents
      generate_content
    }
  rescue Timeout::Error
    @http_response_code = 500
    "Timeout Error: #{$!}"
  rescue RuntimeError
    @http_response_code = 500
    "Runtime Error: #{$!.message}"
  rescue FileNotFound, UndefinedController, UndefinedControllerModule, TemplateNotFound, InvalidVerb
    @http_response_code = 404
    $!.message # Add a custom 404 handler here if desired
  rescue DirectoryBrowsed
    @http_response_code = 403
    "Directory browsing not permitted" # Add a custom 403 handler here if desired
  rescue Unauthorized
    @http_response_code = 403
    "Unauthorized" # Add a custom 403 handler here if desired
  rescue SessionManager::TooManySessions
    @http_response_code = 500
    $!
  rescue
    @http_response_code = 500
    "Unspecified Error: #{$!}"
  end

  ss = StringIO.new
  ss << "HTTP/1.1 #{@http_response_code} ...\r\n"

  @headers.each {|k,v| ss << "#{k}: #{v}\r\n" }
  @cookies.each {|c| ss << "Set-Cookie: #{c.to_s}\r\n" }
  ss << "Content-length: #{content.to_s.length}\r\n"
  ss << "\r\n"
  ss << content

  ss.string
end

#initialize_sessionObject

initialize_session We use a HARDCODED cookie name. Revise this later if it’s necessary to support multiple applications with distinct sessions. When initializing a CGI::Cookie, DON’T LEAVE OUT THE PATH. It’s optional but the default is the current path, not the whole site, so it’s not really a session cookie. WARNING, that may not end up being good enough.



464
465
466
467
468
469
470
471
# File 'lib/monorail/monorail_webd.rb', line 464

def initialize_session
  return unless @@use_sessions
  sc = @cgi.cookies[@@session_cookie_name] and sc = sc.shift
  @session = SessionManager.instance.retrieve_or_create_session( sc )
  if @session.first_use?
    @cookies << CGI::Cookie::new( 'name' => @@session_cookie_name, 'value' => [@session.session_name], 'path' => "/" )
  end
end

#read_post_contentsObject

read_post_contents



619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
# File 'lib/monorail/monorail_webd.rb', line 619

def read_post_contents
  return unless @cgi.request_method == "POST"
  clen = ENV["POST_CONTENT_LENGTH"] and clen = clen.to_i
  return unless clen && (clen > 0)

  pc = ::Monorail::module_eval { retrieve_post_content }
  if pc and pc.respond_to?(:to_s)
    pc = pc.to_s( clen )
    if pc and pc.length == clen
      if @cgi.content_type.downcase == "application/x-www-form-urlencoded"
        @cgi.params.merge!( CGI::parse( pc ))
      else
        # We have some kind of possibly binary content.
        # Might be multipart too.
        # Sit on it until we need to do something with it.
        @post_content = pc
      end
    end
  end

end

#redirect_to(url) ⇒ Object

redirect_to Sets up a 301 redirect and returns a empty string. So it can be used as the last line in a controller.



727
728
729
730
731
# File 'lib/monorail/monorail_webd.rb', line 727

def redirect_to url
  @headers['Location'] = url
  @http_response_code = 301
  ""
end

#render(filename, context = binding) ⇒ Object

render



713
714
715
716
717
718
719
720
# File 'lib/monorail/monorail_webd.rb', line 713

def render filename, context = binding
  filename = File.join( @path_prefix, filename )
  unless File.exist?(filename) and !File.directory?(filename)
    raise TemplateNotFound
  end
  template = File.read( filename )
  ERB.new( template ).result( context )
end