Skip to:

Tiago Cogumbreiro

O Irrepupável

Back to top

Web Continuation

Web Continuation is a very tempting subject for programmers. It's programming as you are used too - yes, I am looking at you imperative programmers. Implementing a web continuation filter with CherryPy - in a naive way - it's pretty straight forward:
"""
To use the 'continuation' filter just use 'yield' to return values to the
browser. To get the arguments sent by the client to your page use the
'cpg.request.paramMap' map.
"""
__license__ = """
Copyright (c) 2005, Tiago Cogumbreiro
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright notice, 
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.
    * Neither the name of Sylvain Hellegouarch nor the names of his contributors 
      may be used to endorse or promote products derived from this software 
      without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
class RecurringMethod:
    def __init__ (self, func):
        self._func = func
        self._has_started = False
        self._iter = None
    def __call__ (self, obj, kwargs):
        # Initilize object
        if not self._has_started:
            self._iter = iter (self._func (obj, **kwargs))
            self._has_started = True
        try:
            data = self._iter.next ()
            return data
        except StopIteration:
            # Try to loop it one more time
            self._iter = iter (self._func (obj, **kwargs))
            return self._iter.next ()
def continuation (method):
    rec_meth = RecurringMethod (method)
    def func (self, **kwargs):
            return rec_meth (self, kwargs)
    return func

To use it just define a method as so:

    @continuation
    def foo (self):
        yield "Hello world next"
        yield "Hello again!"

This will show in your browser "Hello world" and when you click the hyperlink 'next' you'll be greet with "Hello again!". The problem is when you hit 'Refresh' or 'Back' buttons, when you do this you make your browser issue a 'GET' and tries to fetch the page again, which means that the generator will advanced.

Imagine doing a form in that method. The best way to do it would be:

    def is_valid (self, name, age, email):
        try:
            age = int (age.strip ())
        except ValueError:
            self.error_message = "Age is not a valid number"
            return False
        if age > 99 and age < 0:
            self.error_message = "Age must be between 0 and 99"
            return False
        try:
            username, host = email.strip().split ("@")
            assert len (username) > 0 and len (host) > 0
        except AssertionError:
        except ValueError:
            self.error_message = "Email is not valid"
            return False
        name = name.strip ()
        if name == "":
            self.error_message = "Name is not an optional field"
            return False
    @continuation
    def my_form (self):
        while not self.is_valid (** cpg.request.paramMap):
            yield self.FORM_TEMPLATE % (self.error_message)
        yield self.OK_TEMPLATE

More then working correctly this indeed makes programming just as you were used too in text-based programs: a main loop would be listening for the keyboard events you would enter the validation loop then show the screen what whent wrong/right.

The ugly head starts to appear when you try to, let's imagine, mix two forms, to do some kind of wizard. When you are in the second form you will recieve a POST to refresh, because that's what the last call was, therefore you won't be able to distinguish between the first and second states. This is why some web continuation frameworks loath the Back and Refresh buttons. This shows how flawed is it to turn a stateless protocol - HTTP - into a statefull one. It will give you more work trying to impose your macho geekness with your continuation framework then it is to admit defeat.

But there's a "saviour" in this no mans land: AJAX. With it you can make your page alter its contents after you've sent data to the browser, therefore it would be easier to maintain integrity of states. But better done then said so I am going to investiage this and come up with one more review on Web Continuation.


Back to top