================================================
Creating quick-and-dirty WSGI Apps with WebHaiku
================================================
WebHaiku is a very small framework for creating quick-and-dirty WSGI apps. It
is designed for a bare minimum of startup cost and import footprint, so that it
can be used for CGI as well as long-running processes. It requires either
Python 2.5, or Python 2.4 and the "wsgiref" package from the CheeseShop. Apart
from ``wsgiref``, it is entirely self-contained, but it can also be used with
TurboGears/Buffet template engine plugins. (In which case, setuptools and the
appropriate plugin(s) are also required.)
Pages and HTTP Methods
======================
WebHaiku uses classes to represent web-accessible objects::
>>> from web_haiku import Page, test, HTML
>>> class HelloPage(Page):
... body = HTML("
Hello, world!
")
The above is a class representing a page whose ``GET`` response will be the
given "Hello world" message. We can emulate browsing to this page using the
``test()`` function (which incidentally works on any WSGI application, not just
WebHaiku pages!)::
>>> test(HelloPage)
HTTP/1.0 200 OK
Date: ...
Content-Type: text/html
...
Hello, world!
As you can see, we get back the expected content. ``HEAD`` requests are also
automatically supported::
>>> test(HelloPage, REQUEST_METHOD="HEAD")
HTTP/1.0 200 OK
Date: ...
Content-Type: text/html
Content-Length: 22
But ``POST`` requests are not (because we didn't define any form handlers; see
the `Form Handling`_ section, below)::
>>> test(HelloPage, form={"a":"b"})
HTTP/1.0 405 Method not allowed
Date: ...
Content-Type: text/plain
Allow: GET, HEAD
...
Excellent method!
Alas, my response must be:
"I cannot comply."
Notice that the response contains an automatically-generated ``Allow:`` header,
listing the HTTP methods implemented. You can add to these allowed methods by
defining ``HTTP`` methods::
>>> from web_haiku import HTTP
>>> class PostHello(HelloPage):
... @HTTP
... def POST(self):
... return self.body() # behave the same as GET
Now ``POST`` works the same as ``GET`` for this page::
>>> test(PostHello, form={"a":"b"})
HTTP/1.0 200 OK
Date: ...
Content-Type: text/html
...
Hello, world!
And the ``Allow`` for unrecognized methods will now include ``POST``::
>>> test(PostHello, REQUEST_METHOD='PUT')
HTTP/1.0 405 Method not allowed
Date: ...
Content-Type: text/plain
Allow: GET, HEAD, POST
...
Excellent method!
Alas, my response must be:
"I cannot comply."
Child URLs
==========
By default, pages have no child URLs, and any attempt to access such URLs
results in a 404 response::
>>> test(HelloPage, PATH_INFO="/x")
HTTP/1.0 404 Not Found
Date: ...
Content-Type: text/plain
...
404 not found
You deserve a kinder note
Than this web haiku!
...
But you can create subpages using template objects, embedded ``Page`` classes,
or ``@expose`` methods (or `Dynamic Children`_ as described later below)::
>>> from web_haiku import expose
>>> class Container(Page):
... x = HTML.page("This is page X")
...
... class y(Page):
... body = HTML("This is page Y")
...
... @expose
... def z(self):
... self.start_response(
... "200 Yeah!", [('Content-type','text/plain')]
... )
... return ['I be da Z!']
>>> test(Container, PATH_INFO="/x")
HTTP/1.0 200 OK
...
This is page X
>>> test(Container, PATH_INFO="/y")
HTTP/1.0 200 OK
...
This is page Y
>>> test(Container, PATH_INFO="/z")
HTTP/1.0 200 Yeah!
...
I be da Z!
Dynamic Children
----------------
You can implement dynamic child URLs by overriding the ``handle_child()``
method of your pages... XXX
Special URL Handling for Container Pages and Leaf Pages
-------------------------------------------------------
Note that even if a page has children, it must still have a ``body`` of
its own to support ``GET`` operations::
>>> test(Container)
HTTP/1.0 405 Method not allowed
Date: ...
Content-Type: text/plain
Allow:...
...
Excellent method!
Alas, my response must be:
"I cannot comply."
>>> Container.body = HTML("This is the container")
>>> test(Container)
HTTP/1.0 200 OK
...
This is the container
The URL used to access a page must end with a trailing ``/`` if it is a
container, and it must NOT end with a ``/`` if the page is NOT a container. If
the URL is incorrect, a redirect is automatically issued::
>>> test(Container, PATH_INFO="/x/")
HTTP/1.0 302 Found
Date: ...
Content-Type: text/html
Location: http://127.0.0.1/x
...
......
Note that this redirection will *NOT* include any form data, so ``PUT`` or
``POST`` bodies are lost! This redirection is intended to handle user-entered
URLs only. Query strings, however, are included in the redirection::
>>> test(Container, PATH_INFO="/x/", QUERY_STRING="y")
HTTP/1.0 302 Found
Date: ...
Content-Type: text/html
Location: http://127.0.0.1/x?y
...
......
Redirection
===========
>>> class InlineRedirect(Page):
... def body(self):
... return self.redirect('http://example.com/123')
>>> test(InlineRedirect)
HTTP/1.0 302 Found
Date: ...
Content-Type: text/html
Location: http://example.com/123
...
......
>>> from web_haiku import Redirector
>>> class TemplateRedirect(Page):
... body = Redirector('http://example.com/$(?environ["REQUEST_METHOD"]?)')
>>> test(TemplateRedirect)
HTTP/1.0 302 Found
Date: ...
Content-Type: text/html
Location: http://example.com/GET
...
......
XXX REDIRECT_TO
Form Handling
=============
>>> from web_haiku import Text, form_handler
>>> class TestForm(Page):
... """A stupid example to test the framework"""
...
... form_defaults = dict(name='Joey', animal='Dog', email='joe@dog.com')
...
... body = form_failure = HTML("""
...
...
... What is your favorite animal ?
...
... $show_errors
...
...
... """)
... form_success = Text("Hey Joe!")
...
... @form_handler
... def check_joe(self):
... if self.name!='Joe': self.errors.append("Hey, you're not Joe!")
...
... errors_found = HTML.fragment(
... "
Please correct the following problems:
\n"
... '
'
... r'$(? "
\n
".join(errors) ?)'
... '
'
... )
>>> test(TestForm)
HTTP/1.0 200 OK
...
Content-Type: text/html
...
...What is your favorite animal ?...
......
...
>>> test(TestForm, form=dict(name="Me", email="blah"))
HTTP/1.0 200 OK
...