by Jason R. Briggs It's tremendously difficult to argue a RESTful approach to a service-oriented architecture (SOA), when the corporate mindshare is SOAP--where project stakeholders tout the SOA buzzword, nod their heads sagely when you say SOAP, nod their heads again when you say XML-RPC, and then look blankly when you mention REST. At an official level, it seems that for the IBMs, Suns, Microsofts, and Oracles (et al) of this world, REST isn't even on the radar; perhaps more because they would find it difficult to build a commercial strategy around something that is based on simplicity and standards (like HTTP) that have been around for years, than from a true lack of visibility at the coalface. So when the push from on high is for SOAP web services and associated technologies, and your business partners and colleagues have been drinking the Sun/MS/IBM/etc. Kool-Aid, you're generally fighting a losing battle if you're promoting alternatives. While you might usually end up stuck in a buzzword-compliance nightmare, with packets of WSDLs, BPELs, and SOAPs flying around left, right, and center, there are occasions where it may be possible to push through a REST-style, resource-centric approach; where there is no official strategic direction for SOA already in place, where there is a reasonable amount of flexibility and imagination on the part of the project owners, and perhaps with a bit of technical enlightening on the part of the technical lead(s). But it does still come back to the fact that a service-oriented architecture, in particular, is not a simple, disconnected, independent application, and that many of the organizations wishing to interact with the system will be expecting the (in some cases, de facto) standard mechanisms for interaction, and won't be happy unless your software is emitting buzzwords and acronyms like an engine with a split gasket spits oil. The best method I've come up with so far to get around this issue is to deliver both. Design the core of your system with REST resources, and subsequently plug in some kind of interfacing or adapter to the REST components, in order to deliver SOAP messaging for those who require it. This article discusses one approach to a SOAP interface for REST services. However, one thing we aren't going to do is resolve issues of context between REST's coarser-grained idea of a resource, and SOAP's fine-grained RPC methods. There is guaranteed to be a significant chunk of the REST crowd who think, "Why bother? A REST resource doesn't map to SOAP calls and it shouldn't, anyway." That's fine, and agreed, to a point--however, this article is aimed at those fighting the good fight in corporate IT, wanting to design REST systems, but stuck delivering SOAP messages because it's the standard, and what everyone expects you to deliver. A side note: there are, of course, SOAP web methods, which provide an HTTP verb-style interface to a standard SOAP web service--which would be a more direct mapping to a REST resource. But if you're using a form of orchestration, pulling a number of services and processes together with something like BPEL, you are potentially going to be constrained (at the very least by convention, but perhaps also by technical considerations of the orchestration and choreography tools) to using The REST Resource DefinitionThe place I'll start is with the REST services, or resources (in REST terminology). For the purposes of this discussion, I've (RAD) prototyped all of the code in Jython, which gives me access to all of the Java libraries I need to use, and also allows me to throw components together without too much infrastructure (and without too much in the way of recompilation and preparation). Jython also has the advantage of being able to (generally) do more in less space than Java, so from the point of view of explanation, it's quite a useful tool. That said, it's worthwhile to note that in a production setting, I would re-implement certain segments of the code, where performance would be an issue (the interfacing specifically), in "native" Java, due to Jython's performance limitations. To get the directory structure out of the way first, I've set up a directory in my servlet container's webapps directory with the basic layout in Figure 1.
Working servlet files (in this case, Jython scripts) will be placed in the root test folder, while source code obviously goes in the source directory and is compiled to classes--pretty much as you might expect. I'll come to the generated directory shortly. To test the services for yourself, extract the contents of the sample code rest+soap.zip file, found in the Resources section, into the webapps directory of your servlet container (tested on Jetty and Tomcat 5). The various .jar files required for WEB-INF/lib are detailed in the readme.txt file located in that directory. Source code can be compiled using Ant and the build.xml file located in source. For this example, my REST service will be a "shopping basket" of stocks, supporting <xs:element name="BasketRQ">
<xs:annotation>
<xs:documentation>a basket of stocks</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Reference" type="Reference" minOccurs="0" />
<xs:element name="Items" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>an item in the basket</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Code" type="xs:string" />
<xs:element name="Quantity" type="xs:positiveInteger" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
The next schema excerpt shows the basket response ( <xs:element name="BasketRS">
<xs:annotation>
<xs:documentation>a basket of stocks</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Reference" type="Reference" minOccurs="0" />
<xs:element name="Items" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>an item in the basket</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Code" type="xs:string" />
<xs:element name="Quantity" type="xs:positiveInteger" />
<xs:element name="Price" type="Money" />
<xs:element name="Total" type="Money" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
These two elements are used in the Code for the ResourceThe straightforward code for the 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
There is an additional, and rather important, HTTP method the REST resource will implement: 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 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 SOAP Interface #1: Discovering the REST ResourcesThe SOAP interface is basically a servlet pretending to be any number of SOAP services. The interface must first figure out:
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:
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
SOAP Interface #2: Missing SchemasIn creating the SOAP "mapping," we need to take into account some fundamental characteristics that will be generally true across most REST resources:
For the purposes of this article, neither my That aside, the main difficulty we have mapping a SOAP call to a REST resource method is that of data identification (or naming). A REST resource is identified by its URI: Consequently, the definitions of <xs:complexType name="Reference">
<xs:sequence>
<xs:element name="Name" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value='1'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Parameter" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Param" type="xs:string" />
<xs:element name="Value" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:element name="GenericRQ">
<xs:complexType>
<xs:sequence>
<xs:element name="Reference" type="Reference" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="GenericRS">
<xs:complexType>
<xs:sequence>
<xs:element name="Reference" type="Reference" />
<xs:element name="Status" type="xs:positiveInteger" />
<xs:element name="Message" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
A The def doPost(self, request, response):
path = request.getPathInfo()[1:]
# use jaxb to parse the soap content
s = pyutils.read(request)
soapobj = pyutils.unmarshal('testbeans', s)
obj = soapobj.getBody().getAny().get(0)
sw = soapservices[path]
body = ''
# build the URI from the context path, service name
uri = '%s/%s' % (request.getContextPath(), sw.name)
ref = obj.getReference()
# if the reference has a name element then add it to the uri
if ref.getName():
uri = uri + '/' + obj.getReference().getName()
# if the reference has parameters, add them as the query string
if ref.getParameter().size() > 0:
uri = uri + '?'
length = ref.getParameter().size()
for x in xrange(0, length):
p = ref.getParameter().get(x)
uri = uri + p.getParam() + '=' + p.getValue()
if x < length-1:
uri = uri + '&'
# if the REST method is a PUT, turn the data obj back into xml format
if sw.httpmethod == 'PUT':
body = pyutils.marshal('testbeans', obj)
# send the request to the REST resource, using the correct HTTP method
(status, reason, headers, content) = pyutils.httprequest('%s:%s'
% (request.getServerName(), request.getServerPort()),
sw.httpmethod, uri, body, {})
response.setContentType('text/xml')
w = response.getWriter()
if status == 200 and content != '':
# return successful results
content = xmldecl_re.sub('', content)
w.write(soapcontent % content)
else:
# errors are returned as a GenericRS
rs = objfactory.createGenericRS()
rs.setReference(obj.getReference())
rs.setMessage(urllib.unquote_plus(reason))
rs.setStatus(BigInteger('%s' % status))
xml = pyutils.marshal('testbeans', rs)
xml = xmldecl_re.sub('', xml)
w.write(soapcontent % xml)
As you can see, the basic process for this method is:
What is perhaps not immediately obvious from this code is the fact that JAXB is unmarshalling the SOAP envelope ( SOAP Interface #3: Buzzword ComplianceThe Because both my definitions and the responses from a REST request are generally quite limited, this WSDL file is not as complicated as it could be. Although the rather pedantic nature of validating WSDLs (missing "/" anyone?) means slight changes can have significant impacts on validation, and I'm only guessing (because I have yet to fully test this final feature) as to its ultimate usability.
Running/Testing the ServicesIn the resource file for this article, I've included a number of Jython scripts for testing the various services, which can be found in the source directory. The scripts test-put.py, test-get.py, test-options.py, and test-delete.py can all be used for calling the REST resource directly, while soaptest-PutBasket.py, soaptest-GetBasket.py, soaptest-GetBasket-invalid.py, and soaptest-DeleteBasket.py can be used for the SOAP services. To run a test, from a command prompt change to the WEB-INF/source directory for the project and execute
ConclusionIn this article, I've presented one method of creating a SOAP service that can communicate with REST resources, for client applications that are unable (or unwilling) to talk to those resources directly. However, this is really only a starting point: it didn't discuss security (authentication, authorization, securing transmissions, and so on), which is a major component of any web services application, but is in this case, of course, very dependent upon what approach you take when developing the RESTful components. This functionality needs to be addressed for the SOAP interface to be a valid solution. My initial thoughts are that, at least for the authentication functionality, a pluggable structure will be required for the SOAP interface to handle different authentication protocols (at least where this is driven by the client/caller of the service). Other features (how many billion WS-* specifications are there now?) may also be client-driven, or dictated by the business, and affect the extent to which you can successfully design and implement your SOA as a useful and flexible collection of REST resources. In the end, a RESTful design differs significantly enough from SOAP that these requirements may force you down the SOAP avenue, whether you intend it or not. For those with a choice, hopefully this provides a starting point. Resources
|
Playing Together Nicely: Getting REST and SOAP to Share Each Other's Toys



