| OpenInteract documentation | Contained in the OpenInteract distribution. |
OpenInteract - mod_perl handler to process all OpenInteract requests
This documentation is for the OpenInteract Apache content handler. For general information about OpenInteract, see OpenInteract::Intro (OpenInteract::Intro).
This content handler creates the OpenInteract::Request object and farms requests out to all the relevant handlers -- cookies, session, authentication, themes, etc.
We walk through a number of class methods here. They are probably self-evident by checking out the code, but just to be on the safe side.
$R (an OpenInteract::Request)
die with error message. $R. We
reuse this object throughout the request so we should not have any
issues with POST values being empty on a second read.
send_html() then
die with Apache return code (e.g., 'OK' ) send_html() then
die with Apache return code (e.g., 'OK' ) send_html() then
die with Apache return code (e.g., 'OK' ) send_html() then
die with Apache return code (e.g., 'OK' ) $R as the sole
argument. That method is responsible for resetting the URL in the
request to that specified in 'login.required_url'.
send_html() then
die with Apache return code (e.g., 'OK' ) $R->{theme}.
send_html() then
die with Apache return code (e.g., 'OK' ) send_html() then
die with Apache return code (e.g., 'OK' ) send_html() then
die with Apache return code (e.g., 'OK' ) Since all of the above are class methods, you can subclass OpenInteract so you override one or more of the above methods.
If you get an error with something like:
Can't locate object method "cookies" via package "OpenInteract::Request" at /usr/lib/perl5/site_perl/5.6.1/OpenInteract.pm line 226.
This likely means that the OpenInteract::Request::setup_aliases()
wasn't run. Typically this is run in the PerlChildInitHandler when an
Apache child is first created. This points to a larger problem if it
is not run. (What exactly is that larger problem? Still working on
that...)
Nothing known
None known
Copyright (c) 2001-2002 intes.net, inc.. All rights reserved.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Chris Winters <chris@cwinters.com>
| OpenInteract documentation | Contained in the OpenInteract distribution. |
package OpenInteract; # $Id: OpenInteract.pm,v 1.55 2004/09/28 16:10:37 lachoy Exp $ use strict; use Apache::Constants qw( :common :remotehost :response ); use Apache::Request; use Data::Dumper qw( Dumper ); $OpenInteract::VERSION = '1.62'; # Generic separator used in display my $SEP = '=' x 30; # Keep track of what's been require'd my %REQ = (); sub handler ($$) { my ( $class, $apache ) = @_; # Create the big cheese object (aka, "Big R") and populate with some # basic info my $R = eval { $class->setup_request( $apache ) }; if ( $@ ) { $class->send_html( $apache, $@ ); return OK; } $R->DEBUG && $R->scrib( 1, "\n\n$SEP\nRequest started:", scalar localtime( $R->{time} ), "\n", "path: (", $apache->parsed_uri->path, ") PID: ($$)" ); # Go through all of our important steps -- we setup the basic # environment, generate the content and run additional routines after # the content has completed for tracking (session, cookies, etc.) # # If there is a problem with a routine it will die with the # appropriate Apache constant (usually OK) and if we find it we force # it to numeric context and return it. my ( $page ); eval { $class->setup_server_interface( $R, $apache ); $class->setup_cache( $R ); $class->parse_uri( $R ); $class->find_action_handler( $R ); $class->check_database( $R ); $class->setup_cookies_and_session( $R ); $class->setup_authentication( $R ); $class->setup_theme( $R ); $page = $class->run_content_handler( $R ); $class->finish_cookies_and_session( $R ); }; if ( $@ ) { warn " --EXITED WITH ERROR from main handler eval block\nError: $@\n"; return $class->bail( $@ ); } if ( $R->{page}{send_file} ) { $class->send_static_file( $R ); } elsif ( my $redirect_url = $R->{page}{http_redirect} ) { my $apr = $R->apache; $apr->no_cache(1); $apr->headers_out->set( Location => $redirect_url ); $apr->status( REDIRECT ); $apr->send_http_header; } else { $class->send_html( $apache, $page, $R ); } $class->cleanup( $R ); return OK; } sub bail { my ( $class, $msg ) = @_; $msg = $msg + 0; # force scalar to numeric return $msg; } # Setup the OpenInteract::Request object sub setup_request { my ( $class, $apache ) = @_; # Read the stash class from our httpd.conf and grab the config # object my $STASH_CLASS = $apache->dir_config( 'OIStashClass' ); unless ( $REQ{ $STASH_CLASS } ) { eval "require $STASH_CLASS"; if ( $@ ) { $apache->child_terminate; die "Cannot require stash class ($STASH_CLASS) -- ", "fatal event, will terminate Apache child\n"; } $REQ{ $STASH_CLASS }++; } my $C = $STASH_CLASS->get_stash( 'config' ); unless ( ref $C and scalar keys %{ $C } ) { $apache->child_terminate; die "Cannot find configuration object from stash class ($STASH_CLASS) -- ", "fatal event, will terminate Apache child\n"; } # Create the base request object that contains other objects and # info my $REQUEST_CLASS = $C->{server_info}{request_class}; unless ( $REQ{ $REQUEST_CLASS } ) { eval "require $REQUEST_CLASS"; if ( $@ ) { $apache->child_terminate; die "Cannot require request class ($REQUEST_CLASS) -- ", "fatal event, will terminate Apache child\n"; } $REQ{ $REQUEST_CLASS }++; } my $R = $REQUEST_CLASS->instance; $R->{stash_class} = $STASH_CLASS; $R->{pid} = $$; $R->{time} = time; return $R; } # The Apache::Request subclasses the main Apache object and has # additional methods to parse GET/POST data, including file # uploads. Stash this object then get the server name and remote IP # address -- if you're using a proxy, be sure that this has been # passed from the front end server using mod_proxy_add_forward sub setup_apache { my $c = shift; return $c->setup_server_interface( @_ ) } sub setup_server_interface { my ( $class, $R, $apache ) = @_; my $apr = Apache::Request->new( $apache ); $R->stash( 'apache', $apr ); my $srv = $apr->server; $R->{server_name} = $srv->server_hostname; $R->DEBUG && $R->scrib( 1, "Server hostname set to $R->{server_name}" ); $R->{remote_host} = $apr->connection->remote_ip(); $R->DEBUG && $R->scrib( 1, "Request coming from $R->{remote_host}" ); return; } # Create the cache object if we're supposed to sub setup_cache { my ( $class, $R ) = @_; my $CONFIG = $R->CONFIG; my $cache_info = $CONFIG->{cache_info}{data}; if ( ! $R->cache and $cache_info->{use} ) { my $cache_class = $cache_info->{class}; eval "require $cache_class"; if ( $@ ) { $R->scrib( 0, "Cannot include cache class [$cache_class]: $@\n", "Continuing with operation..." ); return; } $R->DEBUG && $R->scrib( 1, "Using cache and setting up with [$cache_class]" ); my $cache = $cache_class->new( $CONFIG ); $R->stash( 'cache', $cache ); } return; } # Parse the URL into pieces, stroing everything relevant in # $R->{path}. Also find the 'action' specified in the URL -- we use # this to find a handler in the action table. Note that if the first # item is actually a directive (such as 'Popup'), then we shift it off # and it in $R->{ui}{directive} so the UI handler knows it's # around. After we do this the $R->{path}{current} should be # consistent, with the action as the first member. sub parse_uri { my ( $class, $R, $uri ) = @_; my $apache = $R->apache; # Get the Apache::URI object and put it in $R unless ( $uri ) { $uri = $apache->parsed_uri; } # TODO: Do we EVER retrieve the URI object from the stash? Why put # it there? $R->stash( 'uri', $uri ); # Get the path info from the URL and put it in $R; we save it twice # so we can shift items from one and still keep the original; we # also get the action name from the first item in the path my $location = $apache->location; my $path = $uri->path; $R->DEBUG && $R->scrib( 1, "Original path: ($path)" ); if ( $location ne '/' ) { $path =~ s/^$location//; $R->{path}{location} = $location; $R->DEBUG && $R->scrib( 1, "Modified path by removing ($location): ($path)" ); } my @choices = split /\//, $path; shift @choices; $R->DEBUG && $R->scrib( 1, "Items in the path: ", join( " // ", @choices ) ); my @full_choices = @choices; $R->{path}{current} = \@choices; $R->{path}{full} = \@full_choices; # If the first item is a directive, remove it and save it for the ui # handler; otherwise it's as if it never existed if ( $R->CONFIG->{page_directives}{ $R->{path}{current}->[0] } ) { $R->{ui}{directive} = shift @{ $R->{path}{current} }; $path = '/' . join( '/', @{ $R->{path}{current} } ); } $R->{ui}{action} = $R->{path}{current}->[0]; $R->DEBUG && $R->scrib( 1, "Action found from URL: $R->{ui}{action}" ); # Note that $path might have been modified if the first item was a # directive $R->{path}{original} = $path; $R->{path}{original} .= '?' . $uri->query if ( $uri->query ); $R->DEBUG && $R->scrib( 1, "Original path/query string set to: $R->{path}{original}" ); return; } # Match up the URL path to the UI action (Conductor) and store the # relevant information in $R sub find_action_handler { my ( $class, $R ) = @_; ( $R->{ui}{class}, $R->{ui}{method} ) = $R->lookup_conductor( $R->{ui}{action} ); unless ( $R->{ui}{class} ) { $R->scrib( 0, " Conductor not found; displaying oops page." ); eval { $R->throw({ code => 301, type => 'file', user_msg => "Bad URL", system_msg => "Cannot find conductor for $R->{ui}{action}", extra => { url => $R->{path}{original} } }) }; if ( $@ ) { $class->send_html( $R->apache, $@, $R ); die OK . "\n"; } } $R->DEBUG && $R->scrib( 1, "Found $R->{ui}{class} // $R->{ui}{method} for conductor" ); return; } # Ensure our main database is up, otherwise bail. sub check_database { my ( $class, $R ) = @_; my $db = $R->db( 'main' ); eval { unless ( $db ) { $R->apache->child_terminate; die "Database not found -- fatal event, will terminate Apache child\n"; } $R->DEBUG && warn "Found item: ", ref( $db ), "\n"; $db->ping; }; if ( $@ ) { $R->apache->child_terminate; $R->scrib( 0, "Cannot ping database -- fatal event, will terminate Apache child" ); my $error_msg = $R->throw({ code => 11 }); $class->send_html( $R->apache, $error_msg, $R ); die OK . "\n"; } return; } sub setup_cookies_and_session { my ( $class, $R ) = @_; eval { $R->DEBUG && $R->scrib( 2, "Trying to use cookie class: ", $R->cookies ); $R->cookies->parse; $R->DEBUG && $R->scrib( 2, "Cookies in:", Dumper( $R->{cookie}{in} ) ); $R->DEBUG && $R->scrib( 2, "Trying to use session class: ", $R->session ); $R->session->parse; }; if ( $@ ) { $class->send_html( $R->apache, $@, $R ); die OK . "\n"; } return; } sub finish_cookies_and_session { my ( $class, $R ) = @_; eval { $R->session->save; $R->cookies->bake; $R->DEBUG && $R->scrib( 2, "Cookies out:", join(" // ", map { $_->name . ' = ' . $_->value } values %{ $R->{cookie}{out} } ) ); }; if ( $@ ) { $class->send_html( $R->apache, $@, $R ); die OK . "\n"; } return; } # Call the various user/group authentication routines sub setup_authentication { my ( $class, $R ) = @_; my ( $error_msg ); if ( my $auth_class = $R->auth ) { eval { $auth_class->user; $auth_class->group; $auth_class->is_admin; $auth_class->custom_handler; }; $error_msg = $@; } else { $error_msg = "Authentication cannot be setup! Please ensure 'auth' " . "is setup in your server configuration under 'system_alias'"; } if ( $error_msg ) { $class->send_html( $R->apache, $error_msg, $R ); die OK . "\n"; } my $login_info = $R->CONFIG->{login}; return undef unless ( $login_info->{required} ); return undef if ( $R->{auth}{logged_in} ); my $url_requires_login = $class->url_requires_login( $R->{path}{original}, $login_info->{required_skip} ); return undef unless ( $url_requires_login ); return $class->required_login_not_found( $R ); } # True if the URL requires login (that is, it DOESN'T match a member # of \@url_to_skip) sub url_requires_login { my ( $class, $url, $url_to_skip ) = @_; return 1 unless ( $url_to_skip ); my @urls_to_check = ( ref $url_to_skip eq 'ARRAY' ) ? @{ $url_to_skip } : ( $url_to_skip ); my $url_skip = 0; foreach my $url_check ( @urls_to_check ) { next unless ( $url_check ); $url_skip++ if ( $url =~ /$url_check/ ); } return ( ! $url_skip ); } # Reset the URL to 'login.required_url' (could this have side # effects?) sub required_login_not_found { my ( $class, $R ) = @_; my $required_url = $R->CONFIG->{login}{required_url}; unless ( $required_url ) { $R->scrib( 0, "You have 'login.required' enabled so I'm ensuring ", "that all users have a login, but you don't have ", "'login_required_url' set to a URL where I should ", "send them. Ignoring login requirement setting..." ); return undef; } my $host = $R->{server_name}; my $full_url = join( '', 'http://', $host, $required_url ); my $uri = Apache::URI->parse( $R->apache, $full_url ); $R->DEBUG && $R->scrib( 1, "Resetting request URL to '$full_url' (composed ", "of host '$host' and path '$required_url') since ", "login required and none found" ); # This is available for the login page to set in a hidden variable # of its choosing; it's also used in the template plugin method # 'return_url()' called by the login box... $R->{path}{login_fail} = $R->{path}{original}; return $class->parse_uri( $R, $uri ); } # Create the theme used; note that logged-in users can choose # their own, but anonymous users have to stick with 'main'. Each # UI handler (conductor) can decide what to do with the object, but # for now we won't try to fetch all the properties or anything sub setup_theme { my ( $class, $R ) = @_; my $C = $R->CONFIG; my $theme_refresh = $R->CONFIG->{session_info}{cache_theme}; if ( $theme_refresh > 0 ) { if ( my $theme = $R->{session}{_oi_cache}{theme} ) { if ( time < $R->{session}{_oi_cache}{theme_refresh_on} ) { $R->DEBUG && $R->scrib( 1, "Got theme from session ok" ); $R->{theme} = $theme; return; } $R->DEBUG && $R->scrib( 1, "Theme session cache expired; refreshing from db" ); } } $R->{theme} = ( $R->{auth}{user} and $R->{auth}{user}{theme_id} ) ? eval { $R->{auth}{user}->theme } : eval { $R->theme->fetch( $C->{default_objects}{theme} ) }; if ( $@ ) { my $ei = SPOPS::Error->get; OpenInteract::Error->set( $ei ); $R->throw({ code => 404 }); $R->scrib( 0, "Error! Cannot retrieve theme! ( Class: ", $R->theme, ")", "with error ($@ / $ei->{system_msg}) Help!" ); my $admin_email = $C->{mail}{admin_email} || $C->{admin_email}; my $error_msg = <<THEMERR; Fundamental part of OpenInteract (themes) not functioning; please contact the system administrator (<a href="mailto:$admin_email">$admin_email</a>). THEMERR $class->send_html( $R->apache, $error_msg, $R ); die OK . "\n"; } # Find all the properties before we potentially cache $R->{theme}->discover_properties; if ( $theme_refresh > 0 ) { $R->{session}{_oi_cache}{theme} = $R->{theme}; $R->{session}{_oi_cache}{theme_refresh_on} = time + ( $theme_refresh + 60 ); $R->DEBUG && $R->scrib( 1, "Set theme to session cache, expires ", "in [$theme_refresh] minutes" ); } return; } # Runs the content handler -- this should either return the full page # ready for display or put the information into $R necessary to send a # static (non-HTML) file sub run_content_handler { my ( $class, $R ) = @_; my ( $ui_class, $ui_method ) = ( $R->{ui}{class}, $R->{ui}{method} ); $R->DEBUG && $R->scrib( 1, "Trying the conductor: <<$ui_class/$ui_method>>" ); return $ui_class->$ui_method(); } # Send a static (non-html/text) file to the user; note that the # content type should already have been set -- normally this is done # automatically by Apache, particularly if the URL ends with a known # filetype sub send_static_file { my ( $class, $R ) = @_; my $file_spec = $R->{page}{send_file}; my ( $file_size ); my ( $fh ); # File is a handle... if ( ref $file_spec ) { $fh = $file_spec; my $default_type = 'application/octet-stream'; unless ( $R->{page}{content_type} ) { $R->scrib( 0, "No content type set for filehandle to send, ", "using default '$default_type'\n" ); $R->{page}{content_type} = $default_type; } $file_size = (stat $fh)[7]; $R->DEBUG && $R->scrib( 1, "Sending filehandle of size", "($file_size) and type", "($R->{page}{content_type})" ); } # File is a filename... else { $fh = Apache->gensym; eval { open( $fh, $file_spec ) || die $! }; if ( $@ ) { $R->scrib( 0, "Cannot open static file from filesystem ($file_spec): $@" ); return NOT_FOUND; } $file_size = $R->{page}{send_file_size} || (stat $file_spec)[7]; $R->DEBUG && $R->scrib( 1, "Sending file ($file_spec) of size", "($file_size) and type", "($R->{page}{content_type})" ); } $R->apache->headers_out->{'Content-Length'} = $file_size; $R->apache->send_http_header( $R->{page}{content_type} ); $R->apache->send_fd( $fh ); close( $fh ); } # Send plain html (or text) to the browser sub send_html { my ( $class, $apache, $content, $R ) = @_; if ( ref $R ) { unless ( $R->CONFIG->{no_promotion} ) { $apache->headers_out->{'X-Powered-By'} = "OpenInteract $OpenInteract::VERSION"; } } my $content_type = $R->{page}{content_type} || $apache->content_type || 'text/html'; $content_type = ( $content_type eq 'httpd/unix-directory' ) ? 'text/html' : $content_type; $apache->send_http_header( $content_type ); $apache->print( $content ); } # Do any necessary cleanup -- logging, remove stash entries, etc. sub cleanup { my ( $class, $R ) = @_; # Wrap this in an eval so it won't bomb if 0.56 isn't installed eval { SPOPS::Exception->clear_stack }; $R->DEBUG && $R->scrib( 2, "\n\nErrors: ", Dumper( $R->error_object->report ), "\n" ); $R->error->clear; $R->error_object->clear_listing; $R->DEBUG && $R->scrib( 1, "\nRequest done:", scalar localtime, "\n", "path: ($R->{path}{original}) PID: ($$)\n", "from: ($R->{remote_host})\n$SEP\n" ); $R->finish_request; return; } 1; __END__