Using the REST Architecture for Web Services

In 2000, Roy Thomas Fielding published the paper "Architectural Styles and the Design of Network-based Software Architectures" (http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm). This paper described a software architecture style called REST (short for "Representational State Transfer") and explained how this architecture could be seen in the design of the Web. REST isn't tied to any particular technology or data formats. Instead, it offers a set of principles for designing web applications that take advantage of the fundamental design of the Web.

Applications that use the REST architectural style use the Web the way it was designed to be used. Instead of struggling to overcome the limitations of the Web, REST embraces those constraints to give developers an elegant, powerful approach to designing and thinking about web applications.

5.1.1. How Do I Do That?

Design your web application in terms of resources. Every URI on the Web identifies a resource. A resource may be any information or service, including a document, an image, or a web service that takes input data and performs some action. What information and services is your application going to provide? Each should be available through the Web at a logical URI. HTTP defines four actions that can be taken on resources: GET, POST, PUT, and DELETE. Web browsers generally support only two of these: GET, for retrieving the representation of a resource, and POST, for submitting data to a service for processing. But when designing services meant for use by non-browser clients you can take advantage of PUT and DELETE as well.

Example 5-1 shows how you can use the REST architecture to design a web application that's usable through both a web browser and web services. In this case, the application is a Wiki, a simple set of user-editable linked web pages.

Example 5-1. wiki.py

import re, pickle, os from twisted.web import server, resource, http reWikiWord = re.compile(r"(([A-Z][a-z]+){2,})") class WikiData(object): "class for managing Wiki data" def _ _init_ _(self, filename): self.filename = filename if os.path.exists(filename): self.pages = pickle.load(file(filename, 'r+b')) else: self.pages = {'WikiHome': 'This is your Wiki home page.'} def save(self): pickle.dump(self.pages, file(self.filename, 'w+b')) def hasPage(self, path): return self.pages.has_key(path) def listPages(self): return self.pages.keys( ) def getPage(self, path): return self.pages.get(path, '') def getRenderedPage(self, path): pageData = self.getPage(path) "get content with linked WikiWords" wikiWords = [match[0] for match in reWikiWord.findall(pageData)] for word in wikiWords: pageData = pageData.replace( word, "<a href="%s">%s</a>" % (word, word)) return pageData def setPage(self, path, content): self.pages[path] = content self.save( ) def deletePage(self, path): del(self.pages[path]) class RootResource(resource.Resource): def _ _init_ _(self, wikiData): self.wikiData = wikiData resource.Resource._ _init_ _(self) self.putChild('edit', EditFactory(self.wikiData)) self.putChild('save', SavePage(self.wikiData)) self.putChild('index', IndexPage(self.wikiData)) def getChild(self, path, request): return WikiPage(self.wikiData, path or 'WikiHome') class EditFactory(resource.Resource): def _ _init_ _(self, wikiData): self.wikiData = wikiData resource.Resource._ _init_ _(self) def getChild(self, path, request): return EditPage(self.wikiData, path) class EditPage(resource.Resource): def _ _init_ _(self, wikiData, path): self.wikiData = wikiData self.path = path resource.Resource._ _init_ _(self) def render(self, request): return """

Editing: %(path)s

Editing: %(path)s

%(data)s

""" % {'path':self.path, 'data':self.wikiData.getPage(self.path)} class SavePage(resource.Resource): def _ _init_ _(self, wikiData): self.wikiData = wikiData resource.Resource._ _init_ _(self) def render_POST(self, request): data = request.args['data'][0] path = request.args['path'][0] self.wikiData.setPage(path, data) request.redirect('/' + path) return "" def render_GET(self, request): return """ To add a page to the Wiki, send data to this page using HTTP POST. Arguments:

""" class WikiPage(resource.Resource): def _ _init_ _(self, wikiData, path): self.wikiData = wikiData self.path = path resource.Resource._ _init_ _(self) def render_GET(self, request): if self.wikiData.hasPage(self.path): return """%s

<a href="/edit/%s">Edit Page</a> <a href="/index">Index</a> <a href="/">Home</a>

%s """ % (self.path, self.path, self.wikiData.getRenderedPage(self.path)) else: request.redirect('/edit/%s' % self.path) return "" def render_PUT(self, request): "accept a PUT request to set the contents of this page" if not self.wikiData.hasPage(self.path): request.setResponseCode(http.CREATED, "Created new page") else: request.setResponseCode(http.OK, "Modified existing page") request.content.seek(0) self.wikiData.setPage(self.path, request.content.read( )) return "" def render_DELETE(self, request): "accept a DELETE request to remove this page" if self.wikiData.hasPage(self.path): self.wikiData.deletePage(self.path) return "Deleted." else: request.setResponseCode(http.NOT_FOUND) return "No such page." class IndexPage(resource.Resource): def _ _init_ _(self, wikiData): self.wikiData = wikiData resource.Resource._ _init_ _(self) def render(self, request): pages = self.wikiData.listPages( ) pages.sort( ) links = ["