Solstice::Dispatch - Dispatches the current request to the appropriate application.


Solstice documentation Contained in the Solstice distribution.

Index


Code Index:

NAME

Top

Solstice::Dispatch - Dispatches the current request to the appropriate application.

Export

None by default.

Methods

runWebserviceURL

loads the overarching REST controller and steps it through providing the requested resource

runAppURL

if an installed solstice app handles a given url, this primes the controller and pulls the trigger

runCGIURL

if the URL is handled by a simple CGI, fire it up.

serveStaticContent($filename)

find and serve a static file

handleError

if our eval fails, we run this to try and recover

show404($url, $screen)
redirectToSSL()
showWelcomeScreen($url)

This shows a message to people who have just installed Solstice, but no applications.

COPYRIGHT

Top


Solstice documentation Contained in the Solstice distribution.
package Solstice::Dispatch;

use strict;
use warnings;
use 5.006_000;

use base qw(Solstice);

use Solstice::Server;
use Solstice::Session;
use Solstice::Controller::Application::Auth;
use Solstice::Controller::Application::Main;
use Solstice::Controller::Remote;
use Solstice::Configure;
use Solstice::Service::Debug;
use Solstice::ButtonService;
use Solstice::LangService;
use Solstice::NamespaceService;
use Solstice::PositionService;
use Solstice::UserService;
use Solstice::ErrorHandler;
use Solstice::View::Application;
use Solstice::View::Redirect;
use Time::HiRes qw(gettimeofday tv_interval);
use File::stat;

use constant TRUE => 1;
use constant FALSE => 0;
use constant CONFIG_FILE_KEY => 'SOLSTICE_CONFIG_FILE';

my %CGIS = ();

sub dispatch {
    my $start_time = [gettimeofday];

    my $server = Solstice::Server->new();
    my $startup_error = $server->getStartupError();

    die "Solstice failed to start: $startup_error" if $startup_error;

    #pre-click cleanup - make sure there is nothing left in services
    $Solstice::Service::data_store = {};

    my $url = $server->getURI();
    my $config = Solstice::Configure->new();


    my $run_cleanup = FALSE;
    Solstice::Service::Debug->new( $config->getCentralDebugLevel() );
    if($config->getDevelopmentMode()){
        require Module::Reload;
        Module::Reload->check();
    }

    my $screen = '';

    #sorry, this sucks, but we need this here too to match
    #the image/static content stuff as well as in the
    #_getCGI/URLProfile functions
    $url =~ s/\/+/\//g;

    eval{
        # Start by making sure we're ssl if we need to be...
        if ($config->getRequireSSL() && !$server->getIsSSL()) {
            my $ssl_url = ($server->getServerURL().'/'.$url);
            $ssl_url =~ s/([^:])\/+/$1\//g;
            redirectToSSL($ssl_url, \$screen);

            #Is it a remote xmlhttp call?
        } elsif ( $url =~ /solstice_remote_call_url/ ){
            $run_cleanup = TRUE; #we'll need to clean up services/etc

            my $controller = Solstice::Controller::Remote->new();
            $controller->runApp(\$screen);

            #is it a webservice resource?
        }elsif( my $resource_profile = $config->_getWebserviceProfile($url)){
            $run_cleanup = TRUE; #we'll need to clean up services/etc


            runWebserviceURL($resource_profile, \$screen, $start_time);


            #is it perhaps a legacy CGI?
        }elsif( my $cgi_profile = $config->_getCGIProfile($url)){
            $run_cleanup = TRUE; #we'll need to clean up services/etc

            if($cgi_profile->{'requires_auth'}){
                return if (Solstice::Controller::Application::Auth->new()->requiresUserLogin());
            }

            runCGIURL($cgi_profile); 


            #Now, let's see if we can locate a static content dir
        }elsif( my $file = $config->_getStaticContent($url)){
            if (!serveStaticContent($file)) {
                show404($url, \$screen);
                $run_cleanup = TRUE;
            }

            #Is the URL an App url?
        }elsif( my $profile = $config->_getURLProfile($url)){
            $run_cleanup = TRUE; #we'll need to clean up services/etc

            #run the application code
            runAppURL($profile, \$screen, $start_time);


            #if there is nothing installed
        }elsif( 0 == scalar keys %{$config->getAppUrls()} ){
            showWelcomeScreen($url, \$screen);


            #then there's no pleasing you
        }else{
            # Show a 404
            show404($url, \$screen);
            $run_cleanup = TRUE;
        }
    };

    #If we crashed
    if($@){
        my $captured_error = $@;
        eval { #can't let a crash here prevent data_store cleanup
            handleError($captured_error, $url);
        };
    }

    #cleanup stateful info
    if($run_cleanup){ #these are only needed if we ran certain subs above
        eval {
            Solstice::ButtonService->new()->commit();
            my $session = Solstice::Session->new();
            $session->store();
        }; #can't let a crash here prevent data_store cleanup
    }

    utf8::downgrade($screen);
    if($server->getContentType() && $server->getContentType() =~ /html/){
        my $time = tv_interval($start_time, [gettimeofday]);
        $screen =~ s/___SOLSTICE_PAGE_LOAD_TIME___/$time/;

        if ($config->getDevelopmentMode()) {
            # Grab these service values before clearing the service data
            my $pos_service = Solstice::PositionService->new();
            my $read_count  = $pos_service->getQueueSize('db_read_count');
            my $write_count = $pos_service->getQueueSize('db_write_count');
            
            $screen =~ s/___SOLSTICE_DB_READ_COUNT___/$read_count/;
            $screen =~ s/___SOLSTICE_DB_WRITE_COUNT___/$write_count/;
        }
    }

    #if screen content was printed, fire off any headers and show it
    if($screen){
        $server->printHeaders();
        print $screen;
    }

    #always needed
    $Solstice::Service::data_store = {};


    return TRUE;
}

sub runWebserviceURL {
    my ($profile, $screen, $start_time) = @_;


    my $ns_service = Solstice::NamespaceService->new();
    $ns_service->_setAppNamespace($profile->{'config_namespace'});

    my $controller = Solstice::Controller::Application::REST->new($profile->{'controller'});

    if( $profile->{'requires_auth'} ){
        if( $controller->handleAuth() ){
            unless( $controller->runWebservice($screen) ){
                $controller->showError($screen);
            }
        }else{
            $controller->showError($screen);
        }
    }else{
        unless( $controller->runWebservice($screen) ){
            $controller->showError($screen);
        }
    }


    Solstice::LogService->new()->log({
            namespace   => 'Solstice',
            log_file    => 'webservice_log',
            content     => 
            Solstice::Server->new()->getMethod() .
            " on ".
            $profile->{'controller'}.
            ", Time: ". tv_interval($start_time, [gettimeofday])
        });
}


sub runAppURL {
    my ($profile, $screen, $start_time) = @_;

    Solstice::Service::Debug->new( $profile->{'debug_level'} );

    my $ns_service = Solstice::NamespaceService->new();
    $ns_service->_setAppNamespace($profile->{'config_namespace'});
    
    Solstice::Server->new()->setPostMax($profile->{'post_max'});

    my $controller = Solstice::Controller::Application::Main->new();

    $controller->setEscapeFrames( $profile->{'escape_frames'} );
    $controller->setDisableBackButton( $profile->{'disable_back_button'} );
    $controller->setViewTopNav( $profile->{'view_top_nav'} );
    $controller->setInitialState( $profile->{'initial_state'} );
    $controller->setDocumentTitle( $profile->{'title'} );
    $controller->setBoilerplateView( $profile->{'boilerplate_view'} );
    $controller->setPageFlow( $profile->{'pageflow'} );
    $controller->setRequireSession( defined $profile->{'require_session'} ? $profile->{'require_session'} : TRUE );
    $controller->setRequiresAuth( defined $profile->{'requires_auth'} ? $profile->{'requires_auth'} : TRUE );


    $controller->runApp($screen);

    my $user_service = Solstice::UserService->new();
    my $orig_user = $user_service->getOriginalUser() ? $user_service->getOriginalUser()->getLoginName(): '-';
    my $user = $user_service->getUser() ? $user_service->getUser()->getLoginName() : '-';

    Solstice::LogService->new()->log({
            namespace   => 'Solstice',
            log_file    => 'app_url_log',
            content     => 
            "Acting as: ". ($user eq $orig_user ? '-' : $user).
            ", PostClick: ". ($controller->getPostClickState() || '-') .
            ", Action: ". ($controller->getAction() || '-') .
            ", PreClick: ". ($controller->getPreClickState() || '-') .
            ", Time: ".  tv_interval($start_time, [gettimeofday])
        });

    return;
}


sub runCGIURL {
    my ($cgi_profile) = @_;

    my $script_loaded = $CGIS{$cgi_profile->{'filesys_path'}};
    my $file_info = stat($cgi_profile->{'filesys_path'});
    my $file_modified = $file_info->mtime;

    # Create a namespace for each cgi... this is a bit ugly... tried to make it so this could be cross platform
    my $package = "Solstice::Handler::$cgi_profile->{'filesys_path'}";
    $package =~ s/\./__/g;
    $package =~ s'/|\\'::'g;
    $package =~ s/::([:]+)/::/g;

    if (!defined $script_loaded or ($script_loaded < $file_modified)) {
        local($/, undef);
        open(my $FILE, "<", $cgi_profile->{'filesys_path'}) or die "Unable to open: $cgi_profile->{'filesys_path'}\n";
        my $script = "package $package; sub handler {".join("\n",<$FILE>)."}";
        close($FILE);

        eval $script; ## no critic
        if ($@) {
            die 'Unable to load '.$cgi_profile->{'filesys_path'}." : $@\n";
        }
        else {
            $CGIS{$cgi_profile->{'filesys_path'}} = time;
        }
    }
    { ## no critic
        no strict 'refs';
        eval { &{"${package}::handler"}() };
        if ($@) {
            delete $CGIS{$cgi_profile->{'filesys_path'}};
            die "Error in ".$cgi_profile->{'filesys_path'}." : $@\n";
        }
        return;
    }
}


sub serveStaticContent {
    my ($filename) = @_;

    return FALSE unless (-f $filename);

    my $server = Solstice::Server->new();
    if( $server->getMeetsConditions($filename) ) {
        my $type_service = Solstice::ContentTypeService->new();
        if( my $type = $type_service->getContentTypeByFilename($filename) ){
            if( $type_service->isTextType($type) && !$type_service->includesCharset($type) ){
                $type .= '; charset=UTF-8';
            }
            $server->setContentType($type);
        }else{
            $server->setContentType($type_service->getDownloadContentType());
        }
        $server->printHeaders();

        if( open(my $fh, '<', $filename) ){
            while(<$fh>){
                print $_;
            }
            close $fh;
        }
    }
    return TRUE;
}


sub handleError {
    my ($error, $url, $r) = @_;

    my $server = Solstice::Server->new();
    $@ = undef; #clear this out, we have some evals to do 

    # Avoid specific nuisance 500s
    # http://httpd.apache.org/docs/1.3/misc/FAQ.html#peerreset
    return if ($error =~ /Connection reset by peer/);
    return if ($error =~ /^Apache2::RequestIO::sendfile:/);

    # There are some errors that we know about, and while they need to halt execution, they shouldn't send us email.
    # An example of this is a user with no session making remote calls.
    return if ($error =~ /^Solstice Exception: /);

    #Print an error to the logs
    my $err;

    #gather some useful info
    $err .= "URL: $url\n";
    $err .= "Remote User: ". $ENV{'REMOTE_USER'} ."\n" if $ENV{'REMOTE_USER'};

    my $sess_user = eval{ return Solstice::UserService->new()->getUser()->getLoginName();};
    if(!$@ && $sess_user){
        $err .="Session User: $sess_user\n";
    }

    $@ = undef;
    my $orig_user = eval{ return Solstice::UserService->new()->getOriginalUser()->getLoginName();};
    if(!$@ && $orig_user){
        $err .="Original User: $orig_user\n";
    }


    chomp $error;
    $err .=  "$error\n";
    print STDERR "==== Solstice Application Error ====\n$err====================================\n";

    if(Solstice::Configure->new()->getDevelopmentMode()){
        if(!$server->getContentType()){
            $server->setContentType('text/html');
        }
        $err =~ s/\n/<br>\n/g;
        $server->printHeaders();
        print "<html><body><span style='color:red;'>Solstice Application Error</span><br/><code>$err</code></body></html>";
    }else{


        my $run_default_error = TRUE;
        #First, try to run the app-defined error handler
        $@ = undef;
        eval{
            die "500 on home screen\n" if Solstice::CGI::param('solstice_err');
            my $ns_service = Solstice::NamespaceService->new();
            my $app_config = Solstice::Configure->new($ns_service->getAppNamespace());

            if(defined $app_config->getErrorHandler() && $app_config->getErrorHandler()){

                my $handler = $app_config->getErrorHandler();
                Solstice->new()->loadModule($handler);
                eval {
                    $handler->new($error)->handleError();
                }; die "$handler->handleError(): $@\n" if $@;
                $run_default_error = FALSE;
            }
        };
        print STDERR "==== Solstice Application Error ====\nErrorHandler for URI $url failed:\n$@\n========================\n" if $@;

        #Otherwise, run something generic
        if($run_default_error){

            #send some email
            Solstice::ErrorHandler->new($error)->sendAlert();

            #print a message
            if(!$server->getContentType()){
                $server->setContentType('text/html');
            }
            my $error_html = Solstice::Configure->new()->getErrorHTML();
            $server->printHeaders();
            print $error_html || 'Solstice has experienced an error.';
        }
    }
}

sub show404 {
    my ($url, $screen) = @_;

    print STDERR "Solstice 404: $url not handled!\n";

    my $server = Solstice::Server->new();
    $server->setStatus(404);
    $server->setContentType('text/html');

    my $config = Solstice::Configure->new(); 

    my $boiler_pkg = $config->getBoilerplateView() || 'Solstice::View::Boilerplate';
    Solstice->new()->loadModule($boiler_pkg);
    my $boiler_view = $boiler_pkg->new();

    my $title = Solstice::LangService->new()->getString('404_title');
    if ($config->defined('404_view') && $config->get('404_view') ){ 
        my $error_pkg = $config->get('404_view');
        Solstice->new()->loadModule($error_pkg);
        my $error_view = $error_pkg->new($url);
        $boiler_view->addChildView('content', $error_view);
    } else {
        $boiler_view->setParam('content', "<h1>$title</h1>"); 
    }

    my $app_view = Solstice::View::Application->new();  
    $app_view->setDocumentTitle($title);
    $app_view->addChildView('boilerplate', $boiler_view);
    $app_view->paint($screen);
}

sub redirectToSSL {
    my $url = shift;
    my $screen = shift;

    my $view = Solstice::View::Redirect->new($url);
    $view->sendHeaders();
    $view->printData();
}

sub showWelcomeScreen {
    my ($url, $screen) = @_;

    my $usable_url = $url;
    $usable_url =~ s/\/+$//; #chop any trailing slashes

    my $config = Solstice::Configure->new();
    if($url =~ /installer\/$/ && $config->getNoConfig() ){
        #create a custom cgi profile that specifies the installer
        runCGIURL({
                filesys_path => $config->getRoot(). '/cgis/configure_solstice.pl', 
                requires_auth => 0
            });
    }else{
        Solstice::Server->new()->setContentType('text/html');
        my $lang_service = Solstice::LangService->new();
        if($config->getNoConfig()){
            $$screen = $lang_service->getString('welcome_to_solstice', {
                    installer_info => $lang_service->getString('solstice_installer_info', {
                            url => $usable_url,
                        }),
                });
        }else{
            $$screen = $lang_service->getString('welcome_to_solstice');
        }
    }
}

1;

__END__