File Coverage

blib/lib/Continuity.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Continuity;
2              
3             our $VERSION = '1.6';
4              
5             =head1 NAME
6              
7             Continuity - Abstract away statelessness of HTTP, for stateful Web applications
8              
9             =head1 SYNOPSIS
10              
11             #!/usr/bin/perl
12              
13             use strict;
14             use Continuity;
15              
16             my $server = new Continuity;
17             $server->loop;
18              
19             sub main {
20             my $request = shift;
21             $request->print("Your name:
");
22             $request->next; # this waits for the form to be submitted!
23             my $name = $request->param('name');
24             $request->print("Hello $name!");
25             }
26              
27             =head1 DESCRIPTION
28              
29             Continuity is a library to simplify web applications. Each session is written
30             and runs as a persistent application, and is able to request additional input
31             at any time without exiting. This is significantly different from the
32             traditional CGI model of web applications in which a program is restarted for
33             each new request.
34              
35             The program is passed a C<< $request >> variable which holds the request
36             (including any form data) sent from the browser. In concept, this is a lot like
37             a C<$cgi> object from CGI.pm with one very very significant difference. At any
38             point in the code you can call $request->next. Your program will then suspend,
39             waiting for the next request in the session. Since the program doesn't actually
40             halt, all state is preserved, including lexicals -- getting input from the
41             browser is then similar to doing C<< $line = <> >> in a command-line
42             application.
43              
44             =head1 GETTING STARTED
45              
46             The first thing to make a note of is that your application is a continuously
47             running program, basically a self contained webserver. This is quite unlike a
48             CGI.pm based application, which is re-started for each new request from a
49             client browser. Once you step away from your CGI.pm experience this is actually
50             more natural (IMO), more like writing an interactive desktop or command-line
51             program.
52              
53             Here's a simple example:
54              
55             #!/usr/bin/perl
56              
57             use strict;
58             use Continuity;
59              
60             my $server = new Continuity;
61             $server->loop;
62              
63             sub main {
64             my $request = shift;
65             while(1) {
66             $request->print("Hello, world!");
67             $request->next;
68             $request->print("Hello again!");
69             }
70             }
71              
72             First, check out the small demo applications in the eg/ directory of the
73             distribution. Sample code there ranges from simple counters to more complex
74             multi-user ajax applications. All of the basic uses and some of the advanced
75             uses of Continuity are covered there.
76              
77             Here is an brief explanation of what you will find in a typical application.
78              
79             Declare all your globals, then declare and create your server. Parameters to
80             the server will determine how sessions are tracked, what ports it listens on,
81             what will be served as static content, and things of that nature. You are
82             literally initializing a web server that will serve your application to client
83             browsers. Then call the C method of the server, which will get the server
84             listening for incoming requests and starting new sessions (this never exits).
85              
86             use Continuity;
87             my $server = Continuity->new( port => 8080 );
88             $server->loop;
89              
90             Continuity must have a starting point when starting new sessions for your
91             application. The default is C<< \&::main >> (a sub named "main" in the default
92             global scope), which is passed the C<< $request >> handle. See the
93             L documentation for details on the methods available from
94             the C<$request> object beyond this introduction.
95              
96             sub main {
97             my $request = shift;
98             # ...
99             }
100              
101             Outputting to the client (that is, sending text to the browser) is done by
102             calling the C<$request-Eprint(...)> method, rather than the plain C used
103             in CGI.pm applications.
104              
105             $request->print("Hello, guvne'
");
106             $request->print("'ow ya been?");
107              
108             HTTP query parameters (both GET and POST) are also gotten through the
109             C<$request> handle, by calling C<$p = $request-Eparam('x')>, just like in
110             CGI.pm.
111              
112             # If they go to http://webapp/?x=7
113             my $input = $request->param('x');
114             # now $input is 7
115              
116             Once you have output your HTML, call C<$request-Enext> to wait for the next
117             response from the client browser. While waiting other sessions will handle
118             other requests, allowing the single process to handle many simultaneous
119             sessions.
120              
121             $request->print("Name:
");
122             $request->next; # <-- this is where we suspend execution
123             my $name = $request->param('n'); # <-- start here once they submit
124              
125             Anything declared lexically (using my) inside of C
is private to the
126             session, and anything you make global is available to all sessions. When
127             C
returns the session is terminated, so that another request from the
128             same client will get a new session. Only one continuation is ever executing at
129             a given time, so there is no immediate need to worry about locking shared
130             global variables when modifying them.
131              
132             =head1 ADVANCED USAGE
133              
134             Merely using the above code can completely change the way you think about web
135             application infrastructure. But why stop there? Here are a few more things to
136             ponder.
137              
138             =head2 Coro::Event
139              
140             Since Continuity is based on L, we also get to use L. This
141             means that you can set timers to wake a continuation up after a while, or you
142             can have inner-continuation signaling by watch-events on shared variables.
143              
144             =head2 Multiple sessions per-user
145              
146             For AJAX applications, we've found it handy to give each user multiple
147             sessions. In the chat-ajax-push demo each user gets a session for sending
148             messages, and a session for receiving them. The receiving session uses a
149             long-running request (aka COMET) and watches the globally shared chat message
150             log. When a new message is put into the log, it pushes to all of the ajax
151             listeners.
152              
153             =head2 Lexical storage and callback links
154              
155             Don't forget about those pretty little lexicals you have at your disposal.
156             Taking a hint from the Seaside folks, instead of regular links you could have
157             callbacks that trigger a anonymous subs. Your code could look like:
158              
159             use Continuity;
160             use strict;
161             my @callbacks;
162             my $callback_count;
163             Continuity->new->loop;
164             sub gen_link {
165             my ($text, $code) = @_;
166             $callbacks[$callback_count++] = $code;
167             return qq{$text};
168             }
169             sub process_links {
170             my $request = shift;
171             my $cb = $request->param('cb');
172             if(exists $callbacks[$cb]) {
173             $callbacks[$cb]->($request);
174             delete $callbacks[$cb];
175             }
176             }
177             sub main {
178             my $request = shift;
179             my $x;
180             my $link1 = gen_link('This is a link to stuff' => sub { $x = 7 });
181             my $link2 = gen_link('This is another link' => sub { $x = 42 });
182             $request->print($link1, $link2);
183             $request->next;
184             process_links($request);
185             $request->print("\$x is now: $x");
186             }
187              
188             =head2 Scaling
189              
190             To scale a Continuity-based application beyond a single process you need to
191             investigate the keywords "session affinity". The Seaside folks have a few
192             articles on various experiments they've done for scaling, see the wiki for
193             links and ideas. Note, however, that premature optimization is evil. We
194             shouldn't even be talking about this.
195              
196             =head1 EXTENDING AND CUSTOMIZING
197              
198             This library is designed to be extensible but have good defaults. There are two
199             important components which you can extend or replace.
200              
201             The Adapter, such as the default L, actually
202             makes the HTTP connections with the client web browser. If you want to use
203             FastCGI or even a non-HTTP protocol, then you will use or create an Adapter.
204              
205             The Mapper, such as the default L, identifies incoming
206             requests from The Adapter and maps them to instances of your program. In other
207             words, Mappers keep track of sessions, figuring out which requests belong to
208             which session. The default mapper can identify sessions based on any
209             combination of cookie, IP address, and URL path. Override The Mapper to create
210             alternative session identification and management.
211              
212             =head1 METHODS
213              
214             The main instance of a continuity server really only has two methods, C
215             and C. These are used at the top of your program to do setup and start
216             the server. Please look at L for documentation on the
217             C<$request> object that is passed to each session in your application.
218              
219             =cut
220              
221 1     1   19737 use strict;
  1         2  
  1         37  
222 1     1   4 use warnings;
  1         1  
  1         22  
223              
224 1     1   350 use Coro;
  0            
  0            
225             use HTTP::Status; # to grab static response codes. Probably shouldn't be here
226             use Continuity::RequestHolder;
227             use List::Util 'first';
228              
229             sub debug_level :lvalue { $_[0]->{debug_level} } # Debug level (integer)
230             sub adapter :lvalue { $_[0]->{adapter} }
231             sub mapper :lvalue { $_[0]->{mapper} }
232             sub debug_callback :lvalue { $_[0]->{debug_callback} }
233              
234             =head2 $server = Continuity->new(...)
235              
236             The C object wires together an Adapter and a mapper.
237             Creating the C object gives you the defaults wired together,
238             or if user-supplied instances are provided, it wires those together.
239              
240             Arguments:
241              
242             =over 4
243              
244             =item * C -- coderef of the main application to run persistently for each unique visitor -- defaults to C<\&::main>
245              
246             =item * C -- defaults to an instance of C
247              
248             =item * C -- defaults to an instance of C
249              
250             =item * C -- defaults to C<.>
251              
252             =item * C -- defaults to C<< sub { $_[0]->url =~ m/\.(jpg|jpeg|gif|png|css|ico|js)$/ } >>, used to indicate whether any request is for static content
253              
254             =item * C -- Set level of debugging. 0 for nothing, 1 for warnings and system messages, 2 for request status info. Default is 1
255              
256             =item * C -- Callback for debug messages. Default is print.
257              
258             =back
259              
260             Arguments passed to the default adapter:
261              
262             =over 4
263              
264             =item * C -- the port on which to listen
265              
266             =item * C -- defaults to 0, set to 1 to disable the C header and similar headers
267              
268             =back
269              
270             Arguments passed to the default mapper:
271              
272             =over 4
273              
274             =item * C -- set to name of cookie or undef for no cookies (defaults to 'cid')
275              
276             =item * C -- set to the name of a query variable for session tracking (defaults to undef)
277              
278             =item * C -- coderef of routine to custom generate session id numbers (defaults to a simple random string generator)
279              
280             =item * C -- lifespan of the cookie, as in CGI::set_cookie (defaults to "+2d")
281              
282             =item * C -- set to true to enable ip-addresses for session tracking (defaults to false)
283              
284             =item * C -- set to true to use URL path for session tracking (defaults to false)
285              
286             =item * C -- set to false to get an empty first request to the main callback (defaults to true)
287              
288             =back
289              
290             =cut
291              
292             sub new {
293              
294             my $this = shift;
295             my $class = ref($this) || $this;
296              
297             no strict 'refs';
298             my $self = bless {
299             docroot => '.', # default docroot
300             mapper => undef,
301             adapter => undef,
302             debug_level => 1,
303             debug_callback => sub { print STDERR "@_\n" },
304             reload => 1, # XXX
305             callback => (exists &{caller()."::main"} ? \&{caller()."::main"} : undef),
306             staticp => sub { $_[0]->url =~ m/\.(jpg|jpeg|gif|png|css|ico|js)$/ },
307             no_content_type => 0,
308             reap_after => undef,
309             allowed_methods => ['GET', 'POST'],
310             @_,
311             }, $class;
312              
313             use strict 'refs';
314              
315             if($self->{reload}) {
316             eval "use Module::Reload";
317             $self->{reload} = 0 if $@;
318             $Module::Reload::Debug = 1 if $self->debug_level > 1;
319             }
320              
321             # Set up the default Adapter.
322             # The adapter plugs the system into a server (probably a Web server)
323             # The default has its very own HTTP::Daemon running.
324             if(!$self->{adapter} || !(ref $self->{adapter})) {
325             my $adapter_name = 'HttpDaemon';
326             if(defined &Plack::Runner::new) {
327             require Continuity::Adapt::PSGI;
328             $adapter_name = 'PSGI';
329             }
330             my $adapter = "Continuity::Adapt::" . ($self->{adapter} || $adapter_name);
331             eval "require $adapter";
332             die "Continuity: Unknown adapter '$adapter'\n" if $@;
333             $self->{adapter} = $adapter->new(
334             docroot => $self->{docroot},
335             server => $self,
336             debug_level => $self->debug_level,
337             debug_callback => $self->debug_callback,
338             no_content_type => $self->{no_content_type},
339             $self->{port} ? (LocalPort => $self->{port}) : (),
340             $self->{cookie_life} ? (cookie_life => $self->{cookie_life}) : (),
341             );
342             }
343              
344             # Set up the default mapper.
345             # The mapper associates execution contexts (continuations) with requests
346             # according to some criteria. The default version uses a combination of
347             # client IP address and the path in the request.
348              
349             if(!$self->{mapper}) {
350              
351             require Continuity::Mapper;
352              
353             my %optional;
354             $optional{LocalPort} = $self->{port} if defined $self->{port};
355             for(qw/ip_session path_session query_session cookie_session assign_session_id
356             implicit_first_next/) {
357             # be careful to pass 0 too if the user specified 0 to turn it off
358             $optional{$_} = $self->{$_} if defined $self->{$_};
359             }
360              
361             $self->{mapper} = Continuity::Mapper->new(
362             debug_level => $self->debug_level,
363             debug_callback => sub { print "@_\n" },
364             callback => $self->{callback},
365             server => $self,
366             %optional,
367             );
368              
369             } else {
370              
371             # Make sure that the provided mapper knows who we are
372             $self->{mapper}->{server} = $self;
373              
374             }
375              
376             $self->start_request_loop;
377              
378             return $self;
379             }
380              
381             sub start_request_loop {
382             my ($self) = @_;
383             async {
384             local $Coro::current->{desc} = 'Continuity Request Loop';
385             while(1) {
386             $self->debug(3, "Getting request from adapter");
387             my $r = $self->adapter->get_request;
388             $self->debug(3, "Handling request");
389             $self->handle_request($r);
390             }
391             };
392             }
393              
394             sub handle_request {
395             my ($self, $r) = @_;
396              
397             if($self->{reload}) {
398             Module::Reload->check;
399             }
400              
401             my $method = $r->method;
402             unless(first { $_ eq $method } @{$self->{allowed_methods}}) {
403             $r->conn->send_error(
404             RC_BAD_REQUEST,
405             "$method not supported -- only (@{$self->{allowed_methods}}) for now"
406             );
407             $r->conn->close;
408             return;
409             }
410              
411             # We need some way to decide if we should send static or dynamic
412             # content.
413             # To save users from having to re-implement (likely incorrectly)
414             # basic security checks like .. abuse in GET paths, we should provide
415             # a default implementation -- preferably one already on CPAN.
416             # Here's a way: ask the mapper.
417              
418             if($self->{staticp}->($r)) {
419             $self->debug(3, "Sending static content... ");
420             $self->{adapter}->send_static($r);
421             $self->debug(3, "done sending static content.");
422             return;
423             }
424              
425             # Right now, map takes one of our Continuity::RequestHolder objects (with conn and request set) and sets queue
426              
427             # This actually finds the thing that wants it, and gives it to it
428             # (executes the continuation)
429             $self->debug(3, "Calling map... ");
430             $self->mapper->map($r);
431             $self->debug(3, "done mapping.");
432             $self->debug(2, "Done processing request, waiting for next\n");
433             }
434              
435             =head2 $server->loop()
436              
437             Calls Coro::Event::loop and sets up session reaping. This never returns!
438              
439             =cut
440              
441             no warnings 'redefine';
442              
443             sub loop {
444             my ($self) = @_;
445              
446             if($self->{adapter}->can('loop_hook')) {
447             return $self->{adapter}->loop_hook;
448             }
449            
450             eval 'use Coro::Event';
451             $self->reaper;
452              
453             Coro::Event::loop();
454             }
455              
456             sub reaper {
457             # This is our reaper event. It looks for expired sessions and kills them off.
458             # TODO: This needs some documentation at the very least
459             # XXX hello? configurable timeout? hello?
460             my $self = shift;
461             async {
462             local $Coro::current->{desc} = 'Session Reaper';
463             my $timeout = 300;
464             $timeout = $self->{reap_after} if $self->{reap_after} and $self->{reap_after} < $timeout;
465             my $timer = Coro::Event->timer(interval => $timeout, );
466             while ($timer->next) {
467             $self->debug(3, "debug: loop calling reap");
468             $self->mapper->reap($self->{reap_after}) if $self->{reap_after};
469             }
470             };
471             # cede once to get the reaper running
472             cede;
473             }
474              
475             # This is our internal debugging tool.
476             # Call it with $self->Continuity::debug(2, '...');
477             sub debug {
478             my ($self, $level, @msg) = @_;
479             my $output;
480             if($self->debug_level && $level <= $self->debug_level) {
481             if($level > 2) {
482             my ($package, $filename, $line) = caller;
483             $output .= "$package:$line: ";
484             }
485             $output .= "@msg";
486             $self->debug_callback->($output) if $self->can('debug_callback');
487             }
488             }
489              
490             =head1 SEE ALSO
491              
492             See the Wiki for development information, more waxing philosophic, and links to
493             similar technologies such as L.
494              
495             Website/Wiki: L
496              
497             L, L, L,
498             L, L
499              
500             L and L for concurrent database access.
501              
502             =head1 AUTHOR
503              
504             Brock Wilcox - http://thelackthereof.org/
505             Scott Walters - http://slowass.net/
506             Special thanks to Marc Lehmann for creating (and maintaining) Coro
507              
508             =head1 COPYRIGHT
509              
510             Copyright (c) 2004-2014 Brock Wilcox . All
511             rights reserved. This program is free software; you can redistribute it
512             and/or modify it under the same terms as Perl itself.
513              
514             =cut
515              
516             1;
517