Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Item13897: Saving intermidiate changes.
Notable changes:

- Started convertion of engine into a information center for the startup
stage. The preparePath() method is the first victim: it now returns a
hashref of action/path_info/uri keys.

- Foswiki::App::handleRequest is incorporating Foswiki::UI::handleRequest
functionality.

- New exception Foswiki::Exception::HTTPResponse. Used to send a HTTP
response to user. Not an error/fatal exception though might be used to send
error HTTP statuses to the user. By default it is using
$Foswiki::app->response object including the status code.

- Foswiki::Exception::Engine now inherits from
Foswiki::Exception::HTTPResponse.

- Foswiki::Request is incorporating code from Item14033 branch by George
Clark.

- Foswiki::Request is now self-initializing using $Foswiki::epp->engine. It
shall be kept in mind that whatever data comes from the engine it may be
later changed/overriden in the request object. Therefore they're not
interchangable. Actually request should be the only user of the engine
unless there is The Really Good Reason to use itby some other subsystem.
  • Loading branch information
vrurg committed Apr 5, 2016
1 parent 3736ccf commit fd9900d
Show file tree
Hide file tree
Showing 8 changed files with 583 additions and 88 deletions.
1 change: 1 addition & 0 deletions core/lib/Foswiki/Access.pm
Expand Up @@ -52,6 +52,7 @@ sub create {

print STDERR "using $imp Access Control\n" if MONITOR;

# SMELL Foswiki::local_class() would be preferable instead of eval.
my $ok = eval("require $imp; 1;");
ASSERT( $ok, $@ ) if DEBUG;
my $this = $imp->new( session => $session, _indirect => 1, );
Expand Down
212 changes: 209 additions & 3 deletions core/lib/Foswiki/App.pm
Expand Up @@ -3,9 +3,11 @@
package Foswiki::App;
use v5.14;

use constant TRACE_REQUEST => 0;

use Cwd;
use Try::Tiny;
use Foswiki::Config;
use Foswiki::Config ();

use Moo;
use namespace::clean;
Expand Down Expand Up @@ -44,6 +46,14 @@ has request => (
isa =>
Foswiki::Object::isaCLASS( 'request', 'Foswiki::Request', noUndef => 1, ),
);
has response => (
is => 'rw',
lazy => 1,
default => sub { new Foswiki::Response },
isa => Foswiki::Object::isaCLASS(
'response', 'Foswiki::Response', noUndef => 1,
),
);
has macros => (
is => 'rw',
lazy => 1,
Expand All @@ -64,6 +74,14 @@ has ui => (
return $_[0]->create('Foswiki::UI');
},
);
has _dispatcherObject => (
is => 'rw',
isa => Foswiki::Object::isaCLASS(
'_dispatcherObject', 'Foswiki::Object', noUndef => 1
),
);
has _dispatcherMethod => ( is => 'rw', );
has _dispatcherContext => ( is => 'rw', );

# App-local $Foswiki::system_message.
has system_message => ( is => 'rw', );
Expand Down Expand Up @@ -93,6 +111,31 @@ sub BUILD {

$Foswiki::app = $this;

my $cfg = $this->cfg;
if ( $cfg->data->{Store}{overrideUmask} && $cfg->data->{OS} ne 'WINDOWS' ) {

# Note: The addition of zero is required to force dirPermission and filePermission
# to be numeric. Without the additition, certain values of the permissions cause
# runtime errors about illegal characters in subtraction. "and" with 777 to prevent
# sticky-bits from breaking the umask.
my $oldUmask = umask(
(
oct(777) - (
(
$cfg->data->{Store}{dirPermission} + 0 |
$cfg->data->{Store}{filePermission} + 0
)
) & oct(777)
)
);

#my $umask = sprintf('%04o', umask() );
#$oldUmask = sprintf('%04o', $oldUmask );
#my $dirPerm = sprintf('%04o', $Foswiki::cfg{Store}{dirPermission}+0 );
#my $filePerm = sprintf('%04o', $Foswiki::cfg{Store}{filePermission}+0 );
#print STDERR " ENGINE changes $oldUmask to $umask from $dirPerm and $filePerm \n";
}

unless ( defined $this->engine ) {
Foswiki::Exception::Fatal->throw( text => "Cannot initialize engine" );
}
Expand Down Expand Up @@ -159,8 +202,100 @@ sub run {
sub handleRequest {
my $this = shift;

my $res = Foswiki::UI::handleRequest( $this->request );
$this->engine->finalize( $res, $this->request );
my $req = $this->request;

try {
$this->_prepareDispatcher;
$this->_checkTickle;

# Get the params cache from the path
my $cache = $req->param('foswiki_redirect_cache');
if ( defined $cache ) {
$req->delete('foswiki_redirect_cache');
}

# If the path specifies a cache path, use that. It's arbitrary
# as to which takes precedence (param or path) because we should
# never have both at once.
my $path_info = $req->pathInfo;
if ( $path_info =~ s#/foswiki_redirect_cache/([a-f0-9]{32})## ) {
$cache = $1;
$req->pathInfo($path_info);
}

if ( defined $cache && $cache =~ m/^([a-f0-9]{32})$/ ) {

# implicit untaint required, because $cache may be used in a filename.
# Note that the cache serialises the method and path_info, which
# will be restored.
Foswiki::Request::Cache->new->load( $1, $req );
}

if (TRACE_REQUEST) {
print STDERR "INCOMING "
. $req->method() . " "
. $req->url . " -> "
. $sub . "\n";
print STDERR "validation_key: "
. ( $req->param('validation_key') || 'no key' ) . "\n";

#require Data::Dumper;
#print STDERR Data::Dumper->Dump([$req]);
}

# XXX TODO vrurg – Continue from here...
if ( UNIVERSAL::isa( $Foswiki::engine, 'Foswiki::Engine::CLI' ) ) {
$this->_dispatcherContext->{command_line} = 1;
}
elsif (
defined $req->method
&& (
(
defined $dispatcher->{allow}
&& !$dispatcher->{allow}->{ uc( $req->method() ) }
)
|| ( defined $dispatcher->{deny}
&& $dispatcher->{deny}->{ uc( $req->method() ) } )
)
)
{
$res = new Foswiki::Response();
$res->header( -type => 'text/html', -status => '405' );
$res->print( '<H1>Bad Request:</H1> The request method: '
. uc( $req->method() )
. ' is denied for the '
. $req->action()
. ' action.' );
if ( uc( $req->method() ) eq 'GET' ) {
$res->print( '<br/><br/>'
. 'The <tt><b>'
. $req->action()
. '</b></tt> script can only be called with the <tt>POST</tt> type method'
. '<br/><br/>'
. 'For example:<br/>'
. '&nbsp;&nbsp;&nbsp;<tt>&lt;form method="post" action="%SCRIPTURL{'
. $req->action()
. '}%/%WEB%/%TOPIC%"&gt;</tt><br/>'
. '<br/><br/>See <a href="http://foswiki.org/System/CommandAndCGIScripts#A_61'
. $req->action()
. '_61">System.CommandAndCGIScripts</a> for more information.'
);
}
return $res;
}
$res = $this->_execute( \&$sub, %{ $dispatcher->{context} } );
return $res;

#my $res = Foswiki::UI::handleRequest( $this->request );
}
catch {
my $e = $_;
}
finally {
# Whatever happens at this stage we shall be able to reply with a valid
# HTTP response using valid HTML.
$this->engine->finalize( $res, $this->request );
};
}

=begin TML
Expand All @@ -176,6 +311,8 @@ sub create {
my $this = shift;
my $class = shift;

#Foswiki::load_class($class);

unless ( $class->isa('Foswiki::AppObject') ) {
Foswiki::Exception::Fatal->throw(
text => "Class $class is not a Foswiki::AppObject descendant." );
Expand Down Expand Up @@ -255,9 +392,16 @@ sub _prepareEngine {
return $engine;
}

# The request attribute default method.
sub _prepareRequest {
my $this = shift;
my $request = $this->engine->prepare;

# The following is preferable form of Request creation. The request
# constructor will then initialize itself using $app->engine as the source
# of information about the environment we're running under.

# my $request = Foswiki::Request->prepare(app => $this);
return $request;
}

Expand All @@ -267,6 +411,68 @@ sub _readConfig {
return $cfg;
}

# Determines what dispatcher to use for the action requested.
sub _prepareDispatcher {
my $this = shift;
my $req = $this->request;

my $dispatcher = $app->cfg->data->{SwitchBoard}{ $req->action };
unless ( defined $dispatcher ) {
$res = $this->response;
$res->header( -type => 'text/html', -status => '404' );
my $html = CGI::start_html('404 Not Found');
$html .= CGI::h1( {}, 'Not Found' );
$html .= CGI::p( {},
"The requested URL "
. $req->uri
. " was not found on this server." );
$html .= CGI::end_html();
$res->print($html);
Foswiki::Exception::HTTPResponse->throw( status => 404, );
}

# SMELL Shouldn't it be deprecated?
if ( ref($dispatcher) eq 'ARRAY' ) {

# Old-style array entry in switchboard from a plugin
my @array = @$dispatcher;
$dispatcher = {
package => $array[0],
function => $array[1],
context => $array[2],
};
}

$dispatcher->{package} //= 'Foswiki::UI';
$this->_dispatcherObject( $this->create( $dispatcher->{package} ) );
$this->_dispatcherMethod( $dispatcher->{method}
|| $dispatcher->{function} );
$this->_dispatcherContext( $dispatcher->{context} );
}

# If the X-Foswiki-Tickle header is present, this request is an attempt to
# verify that the requested function is available on this Foswiki. Respond with
# the serialised dispatcher, and finish the request. Need to stringify since
# VERSION is a version object.
sub _checkTickle {
my $this = shift;
my $req = $this->request;

if ( $req->header('X-Foswiki-Tickle') ) {
my $res = $this->response;
my $data = {
SCRIPT_NAME => $ENV{SCRIPT_NAME},
VERSION => $Foswiki::VERSION->stringify(),
RELEASE => $Foswiki::RELEASE,
};
$res->header( -type => 'application/json', -status => '200' );

my $d = JSON->new->allow_nonref->encode($data);
$res->print($d);
Foswiki::Exception::HTTPResponse->throw;
}
}

1;
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Expand Down
48 changes: 31 additions & 17 deletions core/lib/Foswiki/Engine.pm
Expand Up @@ -24,6 +24,16 @@ use Moo;
use namespace::clean;
extends qw(Foswiki::AppObject);

has env => (
is => 'rw',
isa => Foswiki::Object::isaHASH( 'env', noUndef => 1, ),
default => sub { $_[0]->app->env },
);

# pathData attribute is a hash with the following keys: action, path_info, uri
# uri key can be undef under certain circumstances.
has pathData => ( is => 'rw', lazy => 1, default => \&_preparePath, );

BEGIN {
if ( $Foswiki::cfg{UseLocale} ) {
require locale;
Expand Down Expand Up @@ -52,7 +62,7 @@ sub start {
}
elsif ( $params{env}{'psgi.version'} ) {

# We don't have PSGI support yet.
# SMELL TODO We don't have PSGI support yet.
$engine = 'Foswiki::Engine::PSGI';
}
else {
Expand All @@ -77,24 +87,24 @@ Constructs an engine object.

=begin TML
---++ ObjectMethod run()
---++ Obsolete ObjectMethod run()
Start point to Runtime Engines.
=cut

sub run {
my $this = shift;
my $req = $this->prepare();
if ( ref($req) ) {
my $res = Foswiki::UI::handleRequest($req);
$this->finalize( $res, $req );
}
}
#sub run {
# my $this = shift;
# my $req = $this->prepare();
# if ( ref($req) ) {
# my $res = Foswiki::UI::handleRequest($req);
# $this->finalize( $res, $req );
# }
#}

=begin TML
---++ ObjectMethod prepare() -> $req
---++ Obsolete ObjectMethod prepare() -> $req
Initialize a Foswiki::Request object by calling many preparation methods
and returns it, or a status code in case of error.
Expand Down Expand Up @@ -142,12 +152,16 @@ sub prepare {
$this->prepareUploads($req);
}
catch {
# SMELL returns within Try::Tiny try/catch block doesn't return from the
# calling sub but from the try/catch block itself.
my $e = $_;
unless ( ref($e) ) {
Foswiki::Exception::Fatal->rethrow($e);
}

if ( $e->isa('Foswiki::EngineException') ) {
if ( $e->isa('Foswiki::EngineException')
|| $e->isa('Foswiki::Exception::Engine') )
{
my $res = $e->response;
unless ( defined $res ) {
$res = Foswiki::Response->new;
Expand Down Expand Up @@ -260,16 +274,16 @@ sub prepareHeaders { }

=begin TML
---++ ObjectMethod preparePath( $req )
---++ Private ObjectMethod _preparePath( )
Abstract method, must be defined by inherited classes.
* =$req= - Foswiki::Request object to populate
Should fill $req's uri and pathInfo fields.
Should return a hashref used to initialize the pathData attribute. In other
words, the hashref must containt keys valid for the attribute (see its comment).
=cut

sub preparePath { }
sub _preparePath { }

=begin TML
Expand Down Expand Up @@ -376,7 +390,7 @@ sub finalizeUploads { }
---++ ObjectMethod finalizeError( $res, $req )
Called if some engine especific error happens.
Called if some engine specific error happens.
* =$res= - Foswiki::Response object to get data from
* =$req= - Foswiki::Request object to get data from
Expand Down

0 comments on commit fd9900d

Please sign in to comment.