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
@@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



459
460
461
462
463
# File 'lib/monorail/monorail_webd.rb', line 459

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.



256
257
258
# File 'lib/monorail/monorail_webd.rb', line 256

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.



281
282
283
# File 'lib/monorail/monorail_webd.rb', line 281

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

.authorization_proc(pr) ⇒ Object



308
309
310
# File 'lib/monorail/monorail_webd.rb', line 308

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

.debug(d) ⇒ Object



345
346
347
# File 'lib/monorail/monorail_webd.rb', line 345

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).



451
452
453
454
# File 'lib/monorail/monorail_webd.rb', line 451

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



324
325
326
# File 'lib/monorail/monorail_webd.rb', line 324

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

.use_sessions(sess) ⇒ Object



297
298
299
# File 'lib/monorail/monorail_webd.rb', line 297

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

.verbose(v) ⇒ Object



335
336
337
# File 'lib/monorail/monorail_webd.rb', line 335

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

Instance Method Details

#check_authorizationObject

check_authorization



520
521
522
523
524
525
526
527
528
529
# File 'lib/monorail/monorail_webd.rb', line 520

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.



702
703
704
705
706
707
708
709
710
711
712
713
714
# File 'lib/monorail/monorail_webd.rb', line 702

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.



538
539
540
# File 'lib/monorail/monorail_webd.rb', line 538

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

#generate_contentObject

generate_content



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/monorail/monorail_webd.rb', line 489

def generate_content
  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.



546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
# File 'lib/monorail/monorail_webd.rb', line 546

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:



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
614
615
616
617
618
619
620
# File 'lib/monorail/monorail_webd.rb', line 583

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" )
  instance_eval verb

end

#generate_responseObject

generate_response



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
686
687
688
689
690
691
692
# File 'lib/monorail/monorail_webd.rb', line 651

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.



476
477
478
479
480
481
482
483
# File 'lib/monorail/monorail_webd.rb', line 476

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



626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
# File 'lib/monorail/monorail_webd.rb', line 626

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.



734
735
736
737
738
# File 'lib/monorail/monorail_webd.rb', line 734

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

#render(filename, context = binding) ⇒ Object

render



720
721
722
723
724
725
726
727
# File 'lib/monorail/monorail_webd.rb', line 720

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