ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Playing Together Nicely: Getting REST and SOAP to Share Each Other's Toys
Pages: 1, 2, 3, 4

Code for the Resource

The straightforward code for the GET method is shown below--the basic process is to retrieve the basket name (or reference) from the servlet request path info, and then loop through the matching basket object (if found), updating the price and quantity fields. The object is then marshalled to XML using JAXB, to be written back to the client.



def doGet(self, request, response):
    response.setContentType('text/xml')
    w = response.getWriter()
    
    basketref = request.getPathInfo()[1:]
    
    if not baskets.has_key(basketref):
        response.sendError(HttpServletResponse.SC_NOT_FOUND,
                           'basket reference "%s" was not found' % basketref)
        return
    
    # loop through the items in the basket, and update the price and total fields
    # with the price retrieved from the NZ stock exchange
    itr = baskets[basketref].getItem().iterator()
    while itr.hasNext():
        item = itr.next()
        item.setPrice(BigDecimal(get_stock_price(item.getCode())))
        item.setTotal(BigDecimal(item.getPrice().doubleValue() *
                                   item.getQuantity().intValue()))
        
    # write back the xml for the basket, using jaxb to marshal the object
    w.write(pyutils.marshal('testbeans', baskets[basketref]))
    
    w.close()

The code for the doPut method is also fairly simple, with the following process:

  1. Get the basket name from the servlet path info.
  2. Unmarshal the XML in the body of the PUT request into a BasketRQ Java object using JAXB.
  3. Create a BasketRS Java object and copy the contents of the BasketRQ into it (necessary so we don't need to do this for every GET request).
  4. Save the BasketRS to the "datastore," which for the purposes of this example is just a map of objects in local memory.

There is an additional, and rather important, HTTP method the REST resource will implement: doOptions. The code for this method is shown below:

def doOptions(self, request, response):
    response.setHeader('Allow', 'GET,OPTIONS,PUT,DELETE')
    response.setHeader('Content-Type', '0')
    response.setHeader('X-xmlschema',
        'http://%s:%s%s/schemas/Basket.xsd' % (request.getServerName(), 
            request.getServerPort(), request.getContextPath()))
    response.setHeader('X-PUT-request-node', 'BasketRQ')
    response.setHeader('X-GET-response-node', 'BasketRS')
    response.setStatus(HttpServletResponse.SC_OK)

According to the RFC for HTTP, the OPTIONS method is used to:

represent a request for information about the communication options available on the request/response chain identified by the Request-URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.

So in the example above, I use an Allow header to specify which HTTP methods are supported, and then custom headers (prefixed with X-*) to present more detail about the schema used by the resource, and which nodes are used for the requests and responses to particular methods. All of this allows a client process to interrogate the resource to find out what its requirements are, which will become important shortly.

SOAP Interface #1: Discovering the REST Resources

The SOAP interface is basically a servlet pretending to be any number of SOAP services. The interface must first figure out:

  1. What resources it needs to mimic.
  2. What HTTP methods each resource implements.
  3. What translations need to be performed in order for a SOAP client to talk to the SOAP interface, and then for the SOAP interface to talk to the REST resource.

REST doesn't necessarily constrain you to any particular form of web service contract or the publication of service definitions to a centralized directory--there is plenty of discussion and debate on the Web, from both the REST and SOAP camps (and within those camps as well) over the benefits of either approach from a discovery point of view. Without getting into any debate over discovery methods, the REST resource in this example is published in a rather obvious and simple place: http://localhost:8080/test/discovery.html:

<html>
<head>
<title>Rest Services Discovery</title>
</head>
<body>
<ul id="rest-services">
  <li><a href="http://localhost:8080/test/basket"
    title="basket web service">basket</a></li>
</ul>
</body>
</html>

This has the advantage of being both easily machine-parsable and human-readable. The approach the SOAP interface takes to discovering what REST resources it needs to map is:

  1. Parse the discovery HTML file.
  2. Loop through the unordered list of resources (in this case, only one).
  3. Call the OPTIONS method for each anchor href to retrieve descriptive information about the resource.
  4. For each method the resource supports, create a mapping with the method and resource name (thus an order resource, supporting the GET method, would create a mapping for GetOrder).

The initialization code to map this data is shown here:

(status, reason, headers, html) = pyutils.httprequest('localhost:8080', 'GET', 
            '/test/discovery.html', '', {})

# parse the discovery html file into a dom
builder = factory.newDocumentBuilder()
doc = builder.parse(InputSource(StringReader(html)))
doc.normalize()

# get the unorder list of services from the dom
ul = doc.getElementsByTagName('ul').item(0).getChildNodes()

# loop through the items
for x in xrange(0, ul.getLength()):
    elem = ul.item(x)
    if elem.getNodeType() == Node.ELEMENT_NODE:
        name = elem.getFirstChild().getFirstChild().getNodeValue()
        url = elem.getFirstChild().getAttributes().getNamedItem('href').getNodeValue()
        
        # use a regex  to split the service uri into host and 
        # resource name components
        mat = http_re.match(url)
        if mat:
             host = mat.group(1)
             resource = mat.group(2)
             
             # lookup the OPTIONS supported by the resource, by 
             # calling the OPTIONS http method
             (status, reason, headers, html) = pyutils.httprequest(host, 
                    'OPTIONS', resource, '', {})
             allow = headers['Allow'].split(',')
             for a in allow:
                 if a == 'OPTIONS':
                     continue

                 # the soap method is the http verb plus 
                 # the name of the resource
                 soapmethod = a.capitalize() + name.capitalize()
                 
                 sw = SoapWrapper(name, a)
                 
                 # get the schema header if present, otherwise 
                 # default to Common.xsd
                 if headers.has_key('X-xmlschema'):
                     (sw.schema_uri, sw.schema) = split_schema(headers['X-xmlschema'])
                 else:
                     (sw.schema_uri, sw.schema) = ('http://%s:%s%s/schemas' % 
                            (request.getServerName(), request.getServerPort(), 
                            request.getContextPath()), 'Common.xsd')

                 # get the request node for the http verb if present, otherwise
                 # default to GenericRQ
                 key = 'X-%s-request-node' % a
                 if headers.has_key(key):
                     sw.request_node = headers[key]
                 else:
                     sw.request_node = 'GenericRQ'

                 # get the response node for the http verb if present, 
                 # otherwise default to GenericRS
                 key = 'X-%s-response-node' % a
                 if headers.has_key(key):
                     sw.response_node = headers[key]
                 else:
                     sw.response_node = 'GenericRS'
                 
                 # add the info for this soap service to the services map
                 soapservices[soapmethod] = sw

Pages: 1, 2, 3, 4

Next Pagearrow