Continuity
view release on metacpan or search on metacpan
lib/Continuity.pm view on Meta::CPAN
my $request = shift;
# ...
}
Outputting to the client (that is, sending text to the browser) is done by
calling the C<$request-E<gt>print(...)> method, rather than the plain C<print> used
in CGI.pm applications.
$request->print("Hello, guvne'<br>");
$request->print("'ow ya been?");
HTTP query parameters (both GET and POST) are also gotten through the
C<$request> handle, by calling C<$p = $request-E<gt>param('x')>, just like in
CGI.pm.
# If they go to http://webapp/?x=7
my $input = $request->param('x');
# now $input is 7
Once you have output your HTML, call C<$request-E<gt>next> to wait for the next
response from the client browser. While waiting other sessions will handle
other requests, allowing the single process to handle many simultaneous
sessions.
$request->print("Name: <form><input type=text name=n></form>");
$request->next; # <-- this is where we suspend execution
my $name = $request->param('n'); # <-- start here once they submit
Anything declared lexically (using my) inside of C<main> is private to the
session, and anything you make global is available to all sessions. When
C<main> returns the session is terminated, so that another request from the
same client will get a new session. Only one continuation is ever executing at
a given time, so there is no immediate need to worry about locking shared
global variables when modifying them.
=head1 ADVANCED USAGE
Merely using the above code can completely change the way you think about web
application infrastructure. But why stop there? Here are a few more things to
ponder.
=head2 Coro::Event
Since Continuity is based on L<Coro>, we also get to use L<Coro::Event>. This
means that you can set timers to wake a continuation up after a while, or you
can have inner-continuation signaling by watch-events on shared variables.
=head2 Multiple sessions per-user
For AJAX applications, we've found it handy to give each user multiple
sessions. In the chat-ajax-push demo each user gets a session for sending
messages, and a session for receiving them. The receiving session uses a
long-running request (aka COMET) and watches the globally shared chat message
log. When a new message is put into the log, it pushes to all of the ajax
listeners.
=head2 Lexical storage and callback links
Don't forget about those pretty little lexicals you have at your disposal.
Taking a hint from the Seaside folks, instead of regular links you could have
callbacks that trigger a anonymous subs. Your code could look like:
use Continuity;
use strict;
my @callbacks;
my $callback_count;
Continuity->new->loop;
sub gen_link {
my ($text, $code) = @_;
$callbacks[$callback_count++] = $code;
return qq{<a href="?cb=$callback_count">$text</a>};
}
sub process_links {
my $request = shift;
my $cb = $request->param('cb');
if(exists $callbacks[$cb]) {
$callbacks[$cb]->($request);
delete $callbacks[$cb];
}
}
sub main {
my $request = shift;
my $x;
my $link1 = gen_link('This is a link to stuff' => sub { $x = 7 });
my $link2 = gen_link('This is another link' => sub { $x = 42 });
$request->print($link1, $link2);
$request->next;
process_links($request);
$request->print("\$x is now: $x");
}
=head2 Scaling
To scale a Continuity-based application beyond a single process you need to
investigate the keywords "session affinity". The Seaside folks have a few
articles on various experiments they've done for scaling, see the wiki for
links and ideas. Note, however, that premature optimization is evil. We
shouldn't even be talking about this.
=head1 EXTENDING AND CUSTOMIZING
This library is designed to be extensible but have good defaults. There are two
important components which you can extend or replace.
The Adapter, such as the default L<Continuity::Adapt::HttpDaemon>, actually
makes the HTTP connections with the client web browser. If you want to use
FastCGI or even a non-HTTP protocol, then you will use or create an Adapter.
The Mapper, such as the default L<Continuity::Mapper>, identifies incoming
requests from The Adapter and maps them to instances of your program. In other
words, Mappers keep track of sessions, figuring out which requests belong to
which session. The default mapper can identify sessions based on any
combination of cookie, IP address, and URL path. Override The Mapper to create
alternative session identification and management.
=head1 METHODS
The main instance of a continuity server really only has two methods, C<new>
and C<loop>. These are used at the top of your program to do setup and start
the server. Please look at L<Continuity::Request> for documentation on the
C<$request> object that is passed to each session in your application.
=cut
use strict;
use warnings;
use Coro;
use HTTP::Status; # to grab static response codes. Probably shouldn't be here
use Continuity::RequestHolder;
use List::Util 'first';
sub debug_level :lvalue { $_[0]->{debug_level} } # Debug level (integer)
sub adapter :lvalue { $_[0]->{adapter} }
sub mapper :lvalue { $_[0]->{mapper} }
sub debug_callback :lvalue { $_[0]->{debug_callback} }
=head2 $server = Continuity->new(...)
( run in 0.708 second using v1.01-cache-2.11-cpan-39bf76dae61 )