/usr/share/perl5/Jifty/Continuation.pm is in libjifty-perl 1.10518+dfsg-3ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | use warnings;
use strict;
package Jifty::Continuation;
=head1 NAME
Jifty::Continuation - Allows for basic continuation-based programming
=head1 DESCRIPTION
In programming, a continuation is a construct that allows you to freeze the current state of a program and then recover that state later by calling the continuation. For example, you could save a continuation when throwing an exception to save the state, an exception handler could resolve the problem that caused the exception, and then call the continuation to resume execution at the point where the exception was thrown now that the problem has been solved.
In Jifty, continuations are used to save the state of a request (and sometimes the response). Continuations can be used in situations such as these:
=over
=item 1.
A user visits a page that requires login to view. The dispatcher saves a continuation and then sends the user off to the login page. Once the user logs in successfully, the login action can call the continuation to return the user back to the original page.
=item 2.
A blogging application might have a "Edit" link on each post to allow the editor to jump to the change page. If this link includes a saved continuation, then the "Save" button could trigger that continuation to be called to return the user back to the original page where they clicked "Edit". This way, it could return the user to the view page, or a list page, or an administrative view depending on which page the user started the edit process from.
=item 3.
If you have a wizard for editing some information in your application, but entering some data may require jumping to another page you can save a continuation to allow the user to return after editing. If that page also requires a jump to yet another page, you can save another continuation. Since continuations save a stack of previous continuations, you can return twice to get back to the wizard.
=back
C<Jifty::Continuation> handles the details of saving this information for later recovery. When a continuation is saved, the current request and response are saved to the database in the current user's session. When a continuation is called, the current request and response become those that were saved in the continuation. A continuation can be called at any point in the same session.
Continuations store a L<Jifty::Request> object and the
L<Jifty::Response> object for the request. They can also store
arbitrary code to be run when the continuation is called.
Continuations can also be arbitrarily nested. This means that
returning from one continuation will drop you into the continuation
that is one higher in the stack.
Continuations are generally created just before their request would
take effect, activated by the presence of certain query parameters.
The rest of the request is saved, its execution is to be continued at a
later time.
Continuations are run after any actions have run. When a continuation
is run, it restores the request that it has saved away into it, and
pulls into that request the values any return values that were
specified when it was created. The continuations code block, if any,
is then called, and then the filled-in request is then passed to the
L<Jifty::Dispatcher>.
=cut
use Storable 'dclone';
$Storable::Deparse = 1;
$Storable::Eval = 1;
$Storable::forgive_me = 1;
use base qw/Class::Accessor::Fast Jifty::Object/;
__PACKAGE__->mk_accessors(qw(id parent
request response code
version
));
=head2 new PARAMHASH
Saves a continuation at the current state. Possible arguments in the
C<PARAMHASH>:
=over
=item parent
A L<Jifty::Continuation> object, or the C<id> of one. This represents
the continuation that this continuation should return to when it is
called. Defaults to the current continuation of the current
L<Jifty::Request>.
=item request
The L<Jifty::Request> object to save away. Defaults to an empty
L<Jifty::Request> object.
=item response
The L<Jifty::Response> object that will be loaded up when the
continuation is run. Most of the time, the response isn't stored in
the continuation, since the continuation was saved away B<before> the
actions got run. In the case when continuations are used to preserve
state across a redirect, however, we tuck the L<Jifty::Response> value
of the previous request into the continuation as well. Defaults to an
empty L<Jifty::Response> object.
=item code
An optional subroutine reference to evaluate when the continuation is
called.
=back
=cut
sub new {
my $class = shift;
my $self = bless { }, $class;
my %args = (
parent => Jifty->web->request->continuation,
request => Jifty::Request->new(),
response => Jifty::Response->new(),
code => undef,
@_,
version => 2,
);
# We don't want refs
$args{parent} = $args{parent}->id
if $args{parent} and ref $args{parent};
# We're getting most of our properties from the arguments
for (keys %args) {
$self->$_($args{$_}) if $self->can($_);
}
# Generate a hopefully unique ID
# FIXME: use a real ID
my $key = Jifty->web->serial . "_" . int(rand(10)) . int(rand(10)) . int(rand(10)) . int(rand(10)) . int(rand(10)) . int(rand(10));
$self->id($key);
# Make sure we don't store any of the connection information
my $req = $self->request;
local $req->{env};
local $req->{_body_parser}{input_handle} if defined $req->{_body_parser};
# We may also need to clean out the top request, if this is a subrequest
$req = $req->top_request;
local $req->{env};
local $req->{_body_parser}{input_handle} if defined $req->{_body_parser};
# Save it into the session
Jifty->web->session->set_continuation($key => $self);
return $self;
}
=head2 return_path_matches
Returns true if the continuation matches the current request's path,
and it would return to its caller in this context. This can be used
to ask "are we about to call a continuation?"
=cut
sub return_path_matches {
my $self = shift;
return unless Jifty->web->request->path eq $self->request->path
or Jifty->web->request->path eq $self->request->uri->canonical->path;
my $args = Jifty->web->request->arguments;
return unless scalar keys %{$args} == 1;
return unless exists $args->{"J:RETURN"} and $args->{"J:RETURN"} eq $self->id;
return 1;
}
=head2 call
Call the continuation; this is generally done during request
processing, after actions have been run.
L<Jifty::Request::Mapper>-controlled values are filled into the stored
request based on the current request and response. During the
process, another continuation is created, with the filled-in results
of the current actions included, and the browser is redirected to the
proper path, with that continuation.
=cut
sub call {
my $self = shift;
$self->log->debug("Redirect to @{[$self->request->path]} via continuation");
if (Jifty->web->request->argument('_webservice_redirect')) {
# for continuation - perform internal redirect under webservices.
Jifty->web->webservices_redirect($self->request->path);
return;
}
# If we needed to fix up the path (it contains invalid
# characters) then warn, because this may cause infinite
# redirects
$self->log->warn("Redirect to '@{[$self->request->path]}' contains unsafe characters")
if $self->request->path =~ m{[^A-Za-z0-9\-_.!~*'()/?&;+%]};
# Clone our request
my $request = $self->request->clone;
# Fill in return value(s) into correct part of $request
$request->do_mapping;
my $response = $self->response;
# If the current response has results, we need to pull them
# in. For safety, monikers from the saved continuation
# override those from the request prior to the call
if (Jifty->web->response->results) {
$response = dclone(Jifty->web->response);
my %results = $self->response->results;
$response->result($_ => $results{$_}) for keys %results;
}
# Make a new continuation with that request
my $next = Jifty::Continuation->new(parent => $self->parent,
request => $request,
response => $response,
code => $self->code,
);
$next->request->continuation(Jifty->web->session->get_continuation($next->parent))
if defined $next->parent;
# Redirect to right page if we're not there already
# $next maybe only set path
my $path =
$next->request->request_uri
? URI->new( $next->request->request_uri )->path
: $next->request->path;
Jifty->web->_redirect($path . "?J:RETURN=" . $next->id);
return 1;
}
=head2 return
Returns from the continuation by pulling out the stored request, and
setting that to be the active request. This shouldn't need to be
called by hand -- use L<Jifty::Request/return_from_continuation>,
which ensures that all requirements are met before it calls this.
=cut
sub return {
my $self = shift;
# Pull state information out of the continuation and set it
# up; we use clone so that the continuation itself is
# immutable.
Jifty->web->response(dclone($self->response));
# Run any code in the continuation
$self->code->(Jifty->web->request)
if $self->code;
# We want to preserve the current actual request environment
# (headers, etc)
my $env = Jifty->web->request->top_request->env;
# Set the current request to the one in the continuation
Jifty->web->request($self->request->clone);
# Restore the environment we came in with
Jifty->web->request->top_request->{env} = $env;
Jifty->web->request->setup_subrequest_env
if Jifty->web->request->is_subrequest;
return Jifty->web->request;
}
=head2 delete
Remove the continuation, and any continuations that would return to
its scope, from the session.
=cut
sub delete {
my $self = shift;
# Remove all continuations that point to me
my %continuations = Jifty->web->session->continuations;
$_->delete for grep {$_->parent eq $self->id} values %continuations;
# Finally, remove me from the list of continuations
Jifty->web->session->remove_continuation($self->id);
}
=head1 SEE ALSO
L<Jifty::Manual::Continuations>
=head1 LICENSE
Jifty is Copyright 2005-2010 Best Practical Solutions, LLC.
Jifty is distributed under the same terms as Perl itself.
=cut
1;
|