Implementing the Atom Publishing Protocol

Implementing the Atom Publishing Protocol
by Joe Gregorio |

A Word About WSGI

Please read PEP 333, a nicely written and detailed account of the Web Services Gateway Interface (WSGI). WSGI is an API for writing web services or components in Python. It also allows you to write applications in a platform independent manner. Wsgiref is a library that will be making it's Python core library debut in Python 2.5; it includes a reference implementation of a server, some middleware, and a WSGI application. We'll write our APP implementation as a WSGI application, which gives us more portability and opens up possibilities to write less code, which is always a good thing.

WSGI is simple and simply explained:

The WSGI interface has two sides: the "server" or "gateway" side, and the "application" or "framework" side. The server side invokes a callable object that is provided by the application side. The specifics of how that object is provided are up to the server or gateway. It is assumed that some servers or gateways will require an application's deployer to write a short script to create an instance of the server or gateway, and supply it with the application object. Other servers and gateways may use configuration files or other mechanisms to specify where an application object should be imported from, or otherwise obtained. [PEP 333]

So the application side is a callable object, and if you are familiar with Python you realize soon that you can start doing functional type things with callable objects, like composing them. That observation leads to a concept of "middleware." Not to be confused with high-priced enterprisey solutions, this kind of middleware is made of Python objects that wrap themselves around application objects to provide enhanced behavior.

In addition to "pure" servers/gateways and applications/frameworks, it is also possible to create "middleware" components that implement both sides of this specification. Such components act as an application to their containing server, and as a server to a contained application, and can be used to provide extended APIs, content transformation, navigation, and other useful functions. [PEP 333]

Here is an example of a simple WSGI application, straight from PEP 333:

def simple_app(environ, start_response):

    """Simplest possible application object"""

    status = '200 OK'

    response_headers = [('Content-type','text/plain')]

    start_response(status, response_headers)

    return ['Hello world!\n']

I won't go into any further detail on WSGI here. PEP 333 does a very good job of describing it, and I heartily suggest you go read the PEP if you are at all curious.

Selector

Selector is a piece of WSGI middleware from Luke Arno that, "...provides WSGI middleware for 'RESTful' mapping of URL paths to WSGI applications." So if we know our URI structure and have built WSGI applications for each of the resources in our application, Selector lets us map all those pieces together in a completely natural way, by mapping from URI Templates and method names into WSGI applications. Let's take Table 2 from above and redo it one more time to drop the resource and representation columns and instead plug in our WSGI application names (see Table 3).

Table 3.

/collection/introspection/

GET

introspection

/collection/

GET

enumerate_collection

/collection/

POST

create_new_entry

/collection/member/{id}

GET

member_get

/collection/member/{id}

PUT

member_update

/collection/member/{id}

DELETE

member_delete

URI

Method

WSGI Application

Selector makes it easy to specify such a service. Assuming our applications are already defined, we can create a selector object that does the mapping:

import selector



s = selector.Selector()

s.add('/collection/introspection/', GET=introspection)

s.add('/collection/', POST=create_new_entry, GET=enumerate_collection)

s.add('/collection/member/{id}', GET=member_get, PUT=member_update, DELETE=member_delete)

If we wanted to run our service as a CGI application we can use the wrapper provided in the wsgirf library.

 from wsgiref.handlers import CGIHandler CGIHandler().run(s)

So, all that's left is the individual applications themselves -- the ones that do the work and provide an interface into our Store class. Let's look at the implementation of the WSGI application to create a new entry, remembering that a WSGI application is just a function or callable object that implements the WSGI interface. In this case the application is implemented as a function.

Create an Entry

def create_new_entry(environ, start_response):

    # 1. Check for a good Content-Type: header.

    content_type = environ.get('CONTENT_TYPE', '') 

    content_type = content_type.split(';')[0]

    if content_type and content_type != 'application/atom+xml':

        start_response("400 Bad Request", [('Content-Type','text/plain')])

        return ["Wrong media type."] 



    # 2. Read in the entry

    length = int(environ['CONTENT_LENGTH'])

    content = environ['wsgi.input'].read(length)

 

    # 3. Store the entry

    store = getstore(environ)

    id = store.post(content)

  

    # 4. Response includes a Location: header

    start_response("201 Created", 

        [

          ('Location', urljoin(

               wsgiref.util.application_uri, 

               expand_uri_template('/collection/member/{id}', {'id': id}))

           )

        ]

    )

    return [store.get(id).encode('utf-8')]

First, we do some basic checks (1) to ensure we have been sent the right kind of data.

Then we read in the entry that was sent (2) and place it in the store (3). If successful, we send a 201 Created response that includes a Location: header that points to the newly created resource. The response needs to include a Location: header with the URI of the newly created entry, and from the spec of HTTP (RFC 2616) we know that the URI returned must be an absolute URI.

The call to wsgiref.util.application_uri() gets us our base URI and then we use expand_uri_template() to expand the URI Template with the id we just assigned the new entry. The expand_uri_template() function is described fully in the XML.com article, "Constructing or Traversing URIs?".

If the store has problems with the entry -- for example, it isn't well-formed -- it will throw an exception that our WSGI wrapper will catch and turn into an appropriate error response. Note that the default error response isn't very helpful and a future enhancement will be to add more informative status codes and error messages.

Prev  [1] [2] [3] Next

Close    To Top
  • Prev Article-XML:
  • Next Article-XML: None
  • Now: Tutorial for Web and Software Design > XML > Web Service > XML Content
    Photoshop Tutorial
     

    Special Effect

      3D Effect
      Photoshop Articles
    Programming Tutorial
     

    C/C++ Tutorial

      Visual Basic
      C# Tutorial
    Database Tutorial
     

    MySQL Tutorial

      MS SQL Tutorial
      Oracle Tutorial
    Geek Tutorial
     

    Blogging Tutorial

      RSS Tutorial
      Podcasting Tutorial
    Graphic Design Tutorial
      Coreldraw Tutorial
      Illustrator Tutorial
      3D Tutorials
    Webmaster Articles
     

    Domain Service

      Web Hosting
      Site Promotion
    Java Tutorial/ Articles
     

    Java Servlets

      JavaEE Tutorial
     

    JavaBeans Tutorial

    XML Tutorial/ Articles
     

    XML Style

      AJAX Tutorial
      XML Mobile
    Flash Tutorial/ Articles
     

    Flash Video

      Action Script
      Flash Articles
    OS Tutorial/ Articles
      Linux Tutorial
      Symbian Tutorial
      MacOS Tutorial
    Personal Tech
      Hardware Tutorial
      Software Tutorial
      Online Auction