## Services
``Web Services``:inxx ``API``:inxx
The W3C defines a web service as "a software system designed to support interoperable machine-to-machine interaction over a network". This is a broad definition, and it encompasses a large number of protocols designed not for machine-to-human communication, but for machine-to-machine communication such as XML, JSON, RSS, etc.
In this chapter we discuss how to expose web services using web2py. If you are interested in examples of consuming third party services (Twitter, Dropbox, etc.) you should look into Chapter 9 and Chapter 14.
web2py provides, out of the box, support for many protocols, including XML, JSON, RSS, CSV, XMLRPC, JSONRPC, AMFRPC, and SOAP. web2py can also be extended to support additional protocols.
Each of those protocols are supported in multiple ways, and we make a distinction between:
- Rendering the output of a function in a given format (for example XML, JSON, RSS, CSV)
- Remote Procedure Calls (for example XMLRPC, JSONRPC, AMFRPC)
### Rendering a dictionary
#### HTML, XML, and JSON
``HTML``:inxx ``XML``:inxx ``JSON``:inxx
Consider the following action:
``
def count():
session.counter = (session.counter or 0) + 1
return dict(counter=session.counter, now=request.now)
``:code
This action returns a counter that is increased by one when a visitor reloads the page, and the timestamp of the current page request.
Normally this page would be requested via:
``
and rendered in HTML. Without writing one line of code, we can ask web2py to ren
http://127.0.0.1:8000/app/default/count.html
http://127.0.0.1:8000/app/default/count.xml
http://127.0.0.1:8000/app/default/count.json
``:code
The dictionary returned by the action will be rendered in HTML, XML and JSON, respectively.
Here is the XML output:
``
<document>
<counter>3</counter>
<now>2009-08-01 13:00:00</now>
</document>
``:code
Here is the JSON output:
``
{ 'counter':3, 'now':'2009-08-01 13:00:00' }
``:code
Notice that date, time and datetime objects are rendered as strings in ISO format. This is not part of the JSON standard, but rather a web2py convention.
#### Generic views
When, for example, the ".xml" extension is called, web2py looks for a template file called "default/count.xml", and if it does not find it, looks for a template called "generic.xml". The files "generic.html", "generic.xml", "generic.json" are provided with the current scaffolding application. Other extensions can be easily defined by the user.
------
For security reasons the generic views are only allowed to be accessed on localhost. In order to enable the access from remote clients you may need to set the response.generic_patterns.
------
Assuming you are using a copy of scaffold app, edit the following line in models/db.py
- restrict access only to localhost
``
response.generic_patterns = ['*'] if request.is_local else []
``:code
- to allow all generic views
``
response.generic_patterns = ['*']
``:code
- to allow only .json
``
response.generic_patterns = ['*.json']
``:code
The generic_patterns is a glob pattern, it means you can use any patterns that matches with your app actions or pass a list of patterns.
``
animals.export_to_csv_file(stream)
response.headers['Content-Type']='application/vnd.ms-excel'
response.write(stream.getvalue(), escape=False)
}}
``:code
Notice that one could also define a "generic.csv" file, but one would have to specify the name of the object to be serialized ("animals" in the example). This is why we do not provide a "generic.csv" file.
### Remote procedure calls
``RPC``:inxx
web2py provides a mechanism to turn any function into a web service.
The mechanism described here differs from the mechanism described before because:
- The function may take arguments
- The function may be defined in a model or a module instead of controller
- You may want to specify in detail which RPC method should be supported
- It enforces a more strict URL naming convention
- It is smarter than the previous methods because it works for a fixed set of protocols. For the same reason it is not as easily extensible.
To use this feature:
First, you must import and initiate a service object.
``
from gluon.tools import Service
service = Service()
``:code
-------
This is already done in the "db.py" model file in the scaffolding application.
-------
Second, you must expose the service handler in the controller:
``
def call():
session.forget()
return service()
``:code
-------
This is already done in the "default.py" controller of the scaffolding application. Remove ``session.forget()`` if you plan to use session cookies with the services.
-------
def weekdays():
import gluon.contrib.simplejson
return gluon.contrib.simplejson.dumps(names)
``:code
Below is a sample HTML page that sends an Ajax request to the above action, receives the JSON message and stores the list in a corresponding JavaScript variable:
``
{{extend 'layout.html'}}
<script>
$.getJSON('/application/default/weekdays',
function(data){ alert(data); });
</script>
``:code
The code uses the jQuery function ``$.getJSON``, which performs the Ajax call and, on response, stores the weekdays names in a local JavaScript variable ``data`` and passes the variable to the callback function. In the example the callback function simply alerts the visitor that the data has been received.
#### PyRTF
``PyRTF``:inxx ``RTF``:inxx
Another common need of web sites is that of generating Word-readable text documents. The simplest way to do so is using the Rich Text Format (RTF) document format. This format was invented by Microsoft and it has since become a standard.
web2py includes gluon.contrib.pyrtf, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate RTF documents programmatically, including colored formatted text and pictures.
In the following example we initiate two basic RTF classes, Document and Section, append the latter to the former and insert some dummy text in the latter:
``
def makertf():
import gluon.contrib.pyrtf as q
doc=q.Document()
section=q.Section()
doc.Sections.append(section)
section.append('Section Title')
section.append('web2py is great. '*100)
response.headers['Content-Type']='text/rtf'
return q.dumps(doc)
``:code
In the end the Document is serialized by ``q.dumps(doc)``. Notice that before returning an RTF document it is necessary to specify the content-type in the header else the browser does not know how to handle the file.
Depending on the configuration, the browser may ask you whether to save this file or open it using a text editor.
#### ReportLab and PDF
``ReportLab``:inxx ``PDF``:inxx
web2py can also generate PDF documents, with an additional library called "ReportLab"``ReportLab``:cite .
In the case of web2py each request can be split into three parts:
``
http://127.0.0.1/myapp/default/api/
``
- The name of the resource (``person``, ``persons``, ``person/1``, etc.)
- The communication protocol specified y the extension.
Notice that we can always use the router to eliminate any unwanted prefix in the URL and for example simplify this:
``
http://127.0.0.1/myapp/default/api/person/1.json
``
into this:
``
http://127.0.0.1/api/person/1.json
``
yet this is a matter of test and we have already discussed it at length in chapter 4.
In our example we used an action called ``api`` but this is not a requirement. We can in fact name the action that exposes the RESTful service any way we like and we can in fact even create more than one. For the sake of argument we will continue to assume that our RESTful action is called ``api``.
We will also assume we have defined the following two tables:
``
db.define_table('person',Field('name'),Field('info'))
db.define_table('pet',Field('owner',db.person),Field('name'),Field('info'))
``
and they are the resources we want to expose.
The first thing we do is create the RESTful action:
``
def api():
return locals()
``
Now we modify it so that the extension is filtered out of the request args (so that ``request.args`` can be used to identify the resource) and so that it can handle the different methods separately:
``
which for out ``person`` and ``pet`` tables results in:
"/person/info/pet[pet.owner]/owner/{pet.owner}",
"/person/info/pet[pet.owner]/owner/{pet.owner}/:field",
"/pet[pet]",
"/pet/id/{pet.id}",
"/pet/id/{pet.id}/:field",
"/pet/owner/{pet.owner}",
"/pet/owner/{pet.owner}/:field"
]}
``
You can specify auto patterns for some tables only:
``
patterns = [':auto[person]',':auto[pet]']
``
#### ``smart_query`` (experimental)
``smart_query``:inxx
There are times when you need more flexibility and you want to be able to pass to a RESTful service an arbitrary query like