Satine WS

A Web Services application server for Python

Home    Documentation    FAQ    Download    Satine WS                     

Overview

Web Services are a new interesting paradigm of distributed computing on the Internet. A web service is any software component that talks XML and that uses typical Internet protocols, such as HTTP and SMTP.
Satine WS is a application framework to make development of a WS easy and effective. In spite of other solutions, it manages equally SOAP posts and usual HTTP requests (GET and POST). Also it responds with either XML or HTML documents.
This approach is really interesting, for Satine WS applications can offer services for remote components and can be monitored or costumized by a web interface.

 

Design

Satine WS is a framework based on different technologies:

Satine XML data binding

A XML data binding is a technology to translate a XML document to a object oriented representation with little or no help from the developers. In Satine a powerful data binding is available and it represents the core of all the system. Indeed what make web services different from remote component models (e.g. CORBA, RMI...) is the use of the XML language as primary protocol. This choice offers important benefits, because it enhances portability and interoperability between distributed applications. Unfortunately XML is hardly managed with programming languages. Hence an appropriate data binding can definitely reduce design and development efforts. Satine data binding is extremely easy. It converts XML documents to a new Python data type, named xlist, that is a hybrid between a class and a list. The table shows some simple examples:

Code Meaning
obj = satine.kernel.xml2py("<message>hello world</message>") converts a XML formatted string to a xlist
obj.list(__tag__ = "cuisine") returns all items in a xlist where the tag was cuisine
satine.kernel.visit(obj, [('<cuisine name="italian">', italian_fun)]) visits a xlist and calls the function italian_fun when the tag cousine with a attribute name equal to italian is found.
satine.kernel.query(obj, '<cuisine name="italian"'>) retrieves all fragments that match the pattern inside a xlist

The Satine WS has been developed as an application of Satine data binding. Iit is a different project but it relays on data binding to manage the XML communication protocol. Moreover, while developing Satine WS, appropriate changes of the data binding library have been required. Indeed most of the work to develop Satine WS has been focused to add new features in the data binding library. A lot of time as been required because the new features are code in C for performance reasons. In particular the two functions satine.kernel.visit and satine.kernel.query are two important new contributes. The former visits an object that represents the XML document, and it executes a callbacks function when the specified XML fragment is matched. The latter retrieves all items that matches the pattern passed as parameter.

Satine grapes

Satine grapes is the component model used in Satine WS. A grape is an object that receives and processes messages coded as xlists. A grape must reply to each message with either a xlist or a Python string. In a WS server, each grape manages different incoming XML documents according to the request URL. So, for two different URL there are usually at least two different grapes.
The framework offers two basic component model: satine.grapes.listener and satine.grapes.mailbox. Both those classes extend that class satine.grapes.grape

Listener
A listener processes the incoming messages with a visitor pattern. When the visit algorithm matches a predefined patterns, it executed the related method. Patterns are defined using the doc attribute of a method. The table shows a simple listener for the 'cuisines example':

from satine.grapes import listener

class cuisines(listener):
  def hnd_list(self, x):
     """<list>"""
     return self.cuisines_list()

  def hnd_add(self, x):
     """<add><cuisine>"""
     return self.add_cuisine(x[0])
  ....

Listener grape

The callback methods have a prefix hnd_. When a new listener is instantiated, the constructor scans the class methods looking for those that have a hnd_ prefix. If the method has a doc attribute (the string after the method declaration and before its body), a link between the method and its doc is save. In this case the attribute doc represents the pattern that matches incoming messages. Infact when a listener receives a message, coded as a xlist, it applies the visitor pattern and different methods are called according to the items encoded in the message. For example, if the incoming message represents the document "<add><cuisine>italian</cuisine></add>", the visitor pattern calls the method hnd_add.

Mailbox
A mailbox saves incoming messages in a internal queue. Usually a mailbox should be implemented as a thread that periodically checks the queue for incoming messages. This approach is not as powerful as listeners, but it make the development of pro-active agents easier.

The communication among grapes and other modules is managed by the class satine.grapes.dispatcher.Dispatcher. The dispatcher receives messages from an external source, indeed the HTTP server, and it dispatches them to the different available grapes. The messages delivery policy is stored in an external XML document.

<policy>
   <block>
     <rule on="" do="satine.grapes.HTTP.Echo" then="break" />
   </block>
   <block>
      <rule on=".xml$" do="satine.grapes.HTTP.Xml2Html" then="break" />
   </block>
   <block>
      <rule on=".html$" do="satine.grapes.HTTP.HtmlFilter" then="break">
          <param>.</param>
      </rule>
   </block>
</policy>
A simple dispatcher policy file

Each policy file defines a set of blocks. Inside each block there are different rules. A rule is defined by the attributes on (a regular expression), do (a Python class or function) and then (that can be either break or continue). When the dispatcher receives a message from the HTTP server, it compares the HTTP request path with the rugular expressions stored in the policy configuration. It browses the policy and it compares the path with the attribute on in the rule elements. If a match is found, the dispatcher creates a new Python object with the class named in the attribute do, and saves it into a dictionary. Then if the attribute then is equal to continue, the dispatcher analizes the following rule, if the attribute then is equal to break, it jumps to the next block.


The figure below shows a simple example. The dispatcher receives from the HTTP server a message those path is "/cuisine". The path is compared to the different rules in the policy. The first found rule doesn't match the path because of the trailing '/'. The second rule matches the path, and the dispatcher adds a new "ICS234.cuicines" object in the list for the path "/cuisines". Then the dispatcher jumps to the next block. This is a good behavior; infact the third rule matches any path, and it provides an appropriate error function. The four rule matches and a new instance of "satine.grapes.HTTP.HtmlFilter" is added to the grapes for the path. This process is performed only the first time a path is encountered. As the policy is constant, the dispatcher can binds the path to its grapes in a dictionary, ant retrieves them each request but the first.


When the dispatcher has the list of grapes for the requested path, it forwards messages like in a assembly line. It sends the message from the HTTP server to the first grape (step 1). Then it forwards the result from "ICS234.cuisines" to the second grape. Finally, as it reaches the last grape in the list, it forwards the object from "satine.grapes.HTTP.HtmlFilter" to the HTTP server. In particular in this case, the first grape provides the real information, while the second formats this information inside a HTML page.

Predefined grapes
Satine WS provides three predefined grapes in the module satine.grapes.HTTP. The first, satine.grapes.HTTP.Echo, is a simple echo component. It forwards the input message back to the sender. The second, satine.grapes.HTTP.Xml2Html, formats a message inside a HTML page. The last, satine.grapes.HTTP.HtmlFilter is the most interesting. This grape loads in memory a file whose path relative to a base directory is the same as the HTTP request path. This file usually is a HTML document that contains special intructions encoded between the symbol '@'. The figure below shows a very simple file:

<html>
<body>
<b>The available cuisines are:</b>
@begin@
@<list><cuisine>?name@ cuisine<br>
@end@
</body>
</html>

Template file for the HtmlFilter

The two tokens @begin@ and @end@ define a block. Inside the block there should be query instructions. A query instruction is defined by the expression:

<query> := <xml_fragment> "?" <var1>,<var2>...

When the filter encounters a query instruction, it applies the query to the input message (using satine.kernel.query), and replaces the token with the query result. If the query result is a list of many items, the filter duplicates a different block for each item. The figure below shows a possible input message and the corresponding output.

<list>
  <cuisine name="italian"/>
  <cuisine name="french"/>
</list>

<html>
<body>
<b>The available cuisines are:</b>
italian cuisine<br>
french cuisine<br>
</body>
</html>

Input message
Output Message

HTTP Server

Satine WS includes a HTTP Server that supports both usual browser requests and SOAP requests. The SOAP requests are HTTP POSTs where the header content-type is equal to 'application/soap+xml'. When the server receives a such request, converts the request to a xlist and forwards only the SOAP message content to the dispatcher. When the dispatcher returns the response, the HTTP server foldes the response in a SOAP envelope and sends the message to the SOAP client.

Instead, the behavior is pretty different, when the request is either a GET or a POST where the header content-type is equal to 'application/x-www-form-urlencoded'. In both cases, the HTTP server creates a xlist that represents the XML fragment '<satine:Form>', and it adds as attributes the form parameters. For example if the server receives a HTTP command "GET /cuisines?action=list", it generates a fragment '<satine:Form action="list">'. Then the server forwards the XML fragment to the dispatcher and waits for the answer.

 

Test application

Satine WS provides a very simple example application, a public forum where people or software agents can save restaurant tips. The can retrieve the list of all available cuisines using the path /cuisines. In this case, the server supports two operations: the client can list all the cuisines, or can add some new cuisines. The figure below shows some simple input and output messages when the list is retrieved

<list/>

<satine:Form action="list" cuisine="">

<list>
<cuisine>mexican</cuisine>
<cuisine>continental</cuisine>
</list>

<html>
<body>
<b>The available cuisines are:</b>
mexican cuisine<br>
continental cuisine<br>
</body>
</html>
SOAP Input message
Browser Input message
SOAP Output Message
Html Output Message

When a new cuisine is inserted, the messages are:

<add>
<cuisine>italian</cuisine>
</add>

<satine:Form action="add" cuisine="italian">

<add>
<cuisine>italian</cuisine>
</add>

<html>
<body>
<b>Added cuisines are:</b>
italian cuisine<br>
</body>
</html>
SOAP Input message
Browser Input message
SOAP Output Message
Html Output Message

The other operations are about a particular cuisine. The client can gets all reviews for a cuisine or can add new reviews. In the first case, the client has to send a message to the path /cuisines/<name of the cuisine> (e.g. /cuisines/italian, /cuisines/french...). The simple messages:

<list/>

<satine:Form action="list" cuisine="">

<list cuisine="italian">
<restaurant name ="lampone">
very good pasta
</restaurant>
</list>

<html>
<body>
<b>italian restaurant:</b>
lampone - bery good pasta<br>
</body>
</html>
SOAP Input message
Browser Input message
SOAP Output Message
Html Output Message

In the second case, some sample messages are:

<add>
<restaurant name="lanterna">
good pesto

</restaurant>
</add>

<satine:Form action="add"
name="lanterna"
comment="good pesto">

<add>
<restaurant name="lanterna"/>
</add>

<html>
<body>
<b>new italian restaurant:</b>
lanterna<br>
</body>
</html>
SOAP Input message
Browser Input message
SOAP Output Message
Html Output Message

 

SourceForge.net Logo