Home > General, Router > Converting JSON REST Requests to SOAP

Converting JSON REST Requests to SOAP

Summary

Today, we make a SOAP Web Service accessible as a JSON REST resource using Membrane ESB.

This can, for example, help to make your SOA available to mobile devices, as JSON processing usually uses less resources than fully-fledged SOAP.

The SOAP Web Service

The sample Web Service is called ArticleService and is used to maintain a list of Articles identified by their ID. Each article has a name, a description and a price (amount and currency).

Its WSDL contains, among other things, the list of the service’s operations:

  1. create: creates a new Article

    A new article is described using the following format:

    <article>
      <id>AR-00000</id>
      <name>?XXX?</name>
      <description>?XXX?</description>
      <price>
        <amount>?999.99?</amount>
        <!-- possible value: EUR, possible value: USD -->
        <currency>???</currency>
      </price>
    </article>
    

    An ID will be automatically generated for the new article and is returned in the SOAP response.

  2. get: retrieves information about one Article, given its ID.

    The ID is specified using the following format:

    <!-- Pattern: [A-Z]{2}-\d{5} -->
    <id>???</id>
    
  3. getAll: retrieves information about all articles.
  4. delete: deletes information of one Article, given its ID.

    The request format is the same as for the get operation.

Translation to REST

For the advertised functionality, we define the following HTTP methods, to manage the REST-style resources, “articles“.

  1. POST /articles

    Creates a new article. The message entity uses JSON encoding to describe the article:

    {
      "article": {
        "name":"Darjeeling",
        "description":"FTGFOP1, 1kg",
        "price": {"amount":21.70, "currency":"EUR"},
        "id":"AR-00000"
      }
    }

    HTTP status code “204 No Content” is returned and the URL of the new resource is returned in the “Location” HTTP header field.

  2. GET /articles/AR-00001

    Retrieves the article resource specified by the ID. HTTP status code “200 OK” and the JSON-encoded resource are returned. If no article with the given ID exists, HTTP status code “404 Not found” is returned.

  3. GET /articles

    Retrieves a list of all article resources. Returns HTTP status code “200 OK” and the JSON-encoded list.

  4. DELETE /articles/AR-00001

    Deletes the specified article. HTTP status code “204 No content” is returned, even if no article with the given ID existed.

Any HTTP status code >=400 indicates an error, as per the HTTP specification.

Using Membrane’s rest2Soap Feature

Membrane ESB offers out-of-the-box a rest2Soap feature with JSON support.

Monitoring the Progression of a Sample Request

For example, we send a REST request to Membrane to create an article. (A request of “REST Type 1”, which we just defined.)

POST /articles/ HTTP/1.1
User-Agent: curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1 OpenSSL/0.9.8r zlib/1.2.5
Host: localhost:2000
Accept: */*
Content-Type: application/json
Content-Length: 102
X-Forwarded-For: 127.0.0.1

{"article":{"name":"Test","description":"?","price":{"amount":1.00,"currency":"EUR"},"id":"AR-00000"}}

Membrane automatically translates the request into an XML format representing the HTTP request, called the “in-between XML document”:

<?xml version="1.0" ?>
<http:request xmlns:http="http://membrane-soa.org/schemas/http/v1/"
	method="POST" http-version="1.1">
	<uri value="/articles/">
		<path>
			<component>articles</component>
		</path>
	</uri>
	<headers>
		<header name="User-Agent">curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1
			OpenSSL/0.9.8r zlib/1.2.5</header>
		<header name="Host">localhost:2000</header>
		<header name="Accept">*/*</header>
		<header name="Content-Type">application/json</header>
		<header name="Content-Length">102</header>
		<header name="X-Forwarded-For">127.0.0.1</header>
	</headers>
	<body type="json">
		<root type="o">
			<article type="o">
				<name type="s">Test</name>
				<description type="s">?</description>
				<price type="o">
					<amount type="f">1.00</amount>
					<currency type="s">EUR</currency>
				</price>
				<id type="s">AR-00000</id>
			</article>
		</root>
	</body>
</http:request>

This document fully describes the request at the point in the diagram marked by the asterisk ‘*‘. As you can see, every piece of data from the HTTP request was mapped to some element or attribute. The JSON body of the request was also parsed and translated into XML. (The same also automagically works with XML bodies – although no “translation” is really needed in this case: The XML document from the request body would simply be inserted below the <body> node, with type="xml".)

This XML document is then used as input for the XSL Transformation. The fine-grained structure now makes it very easy to access specific data, which keeps the stylesheets very brief and concise, as well as easy to develop.

Configuring the XSLT component

At this point, to comply with the REST interface described above, we manually need to configure the XSLT component of the rest2soap feature, mapping REST requests to SOAP operations. We create a file “request.xsl” describing the translation of the “in-between XML document” to SOAP:

<pre><?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
							  xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/"
							  xmlns:as="http://predic8.com/wsdl/material/ArticleService/1/"
							  xmlns:http="http://membrane-soa.org/schemas/http/v1/">
							  
	<xsl:template match="/">
		<s11:Envelope >
			<s11:Body>
				<!-- switch over operations -->
				<xsl:choose>
					<xsl:when test="/*/@method = 'GET' and count(//path/component) = 1">
						<as:getAll />
					</xsl:when>
					<xsl:when test="/*/@method = 'GET'">
						<as:get>
							<id><xsl:value-of select="(//path/component)[last()]"/></id>
						</as:get>
					</xsl:when>
					<xsl:when test="/*/@method = 'DELETE'">
						<as:delete>
							<id><xsl:value-of select="(//path/component)[last()]"/></id>
						</as:delete>
					</xsl:when>
					<xsl:when test="/*/@method = 'POST'">
						<as:create>
							<xsl:apply-templates select="/*/body/root/*" />
						</as:create>
					</xsl:when>
				</xsl:choose>
			</s11:Body>
		</s11:Envelope>	
	</xsl:template>

	<!-- remove 'type' attribute: it's an artifact of the JSON2XML converter -->
	<xsl:template match="@type" />
	
	<!-- leave other elements alone -->
	<xsl:template match="*|@*|text()">
		<xsl:copy><xsl:apply-templates select="*|@*|text()"/></xsl:copy>
	</xsl:template>
	
</xsl:stylesheet>

Basically each type of REST request is mapped to a SOAP operation.

We then create a second file called “response.xsl” with the following content to handle SOAP responses:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
							  xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/"
							  xmlns:http="http://membrane-soa.org/schemas/http/v1/">
	
	<xsl:template match="/s11:Envelope">
		<http:response>
			<xsl:choose>

			<!-- status 500 on SOAP Faults -->
			<xsl:when test="/*/*/*[local-name() = 'Fault']">
				<status code="500">Internal SOAP Server Error</status>
				<body type="xml">
					<response><xsl:apply-templates select="/*/*/*/*" /></response>
				</body>
			</xsl:when>

			<xsl:when test="/*/*/*/*[local-name() = 'createResponse']">
				<status code="201">Created</status>
				<headers>
					<!-- use ID from SOAP response for HTTP "Location" header -->
					<header name="Location">/articles/<xsl:value-of select="/*/*/*/id" /></header>
					<header name="Content-Type" remove="true" />
				</headers>
				<body type="plain"></body>
			</xsl:when>

			<xsl:when test="//*[local-name() = 'deleteResponse']">
				<status code="204">No Content</status>
				<headers>
					<header name="Content-Type" remove="true" />
				</headers>
				<body type="plain"></body>
			</xsl:when>

			<xsl:when test="//*[local-name() = 'getResponse']">
				<!-- set status 404, if response is empty -->
				<xsl:if test="count(/*/*/*/*) = 0">
					<status code="404">Not found</status>
				</xsl:if>
				<body type="xml">
					<response><xsl:apply-templates select="/*/*/*/*" /></response>
				</body>
			</xsl:when>

			<xsl:otherwise>
				<!-- unwrap the SOAP message content -->
				<body type="xml">
					<response><xsl:apply-templates select="/*/*/*/*" /></response>
				</body>
			</xsl:otherwise>

			</xsl:choose>
		</http:response>
	</xsl:template>

	<!-- leave other elements alone -->
	<xsl:template match="*|@*|text()">
		<xsl:copy><xsl:apply-templates select="*|@*|text()"/></xsl:copy>
	</xsl:template>
	
</xsl:stylesheet>

Again, we basically just translated the rules we defined above (in the SOAP and REST section) concerning the HTTP responses (status codes, headers, etc.) into XSLT.

Download and set up Membrane ESB

We now download the Membrane ESB distribution (3.5.2 or later) from the website, and change conf/proxies.xml to

<proxies xmlns="http://membrane-soa.org/schemas/proxies/v1/"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://membrane-soa.org/schemas/proxies/v1/ http://membrane-soa.org/schemas/proxies/v1/proxies.xsd">
 
 	<serviceProxy port="2000">
 		<rest2Soap>
 			<mapping 
 				regex="/articles.*" 
 				soapURI="/material/ArticleService"
 				requestXSLT="conf/request.xsl"
 				responseXSLT="conf/response.xsl"
 				soapAction=""
 				responseType="json" />
 		</rest2Soap>
 		<target host="www.predic8.com" />
 	</serviceProxy>
 
</proxies>

We put request.xsl and response.xsl into the conf directory of the distribution.

We then start Membrane using memrouter.bat.

Done!

Testing it

Using the curl utility on Windows, we can issue the following commands:

C:\ > curl http://localhost:2000/articles/
{"response":[{"name":"Black Tea","description":"English Breakfast Black Tea","price":{"amount":4,"currency":"EUR"},"id":"AR-00001"},{"name": "Assam Tea","description":"Finest Assam Tea","price":{"amount":2.5,"currency":"EUR"},"id":"AR-00002"}]}

C:\ > curl --header "Content-Type: application/json" -d "{\"article\":{\"name\":\"Test\",\"description\":\"?\",\"price\":{\"amount\":1.00,\"currency\":\"EUR\"},\"id\":\"AR-00000\"}}" http://localhost:2000/articles/
{"response":{"id":"AR-00043"}}

C:\ > curl http://localhost:2000/articles/AR-00043
{"response":{"article":{"name":"Test","description":"?","price":{"amount":1.00,"currency":"EUR"},"id":"AR-00043"}}}

C:\ > curl -X DELETE http://localhost:2000/articles/AR-00043
C:\ >

Mapping REST to *Your* SOAP Web Service

To realize a REST interface for another SOAP web service, or to proxy other operations, some tweeks to the stylesheets above are needed.

A good way to develop the REST-to-SOAP and SOAP-to-REST XSLT documents, is to set

log4j.logger.com.predic8=debug, stdout

in “conf/log4j.properties“. This turns on debug output. (You need to restart Membrane for the setting to take effect.) It allows you to see the “in-between XML document” created internally: When passing the arrow marked with a star * in the diagram, this document will be written to the log (standard output in our case).

Advertisements
Categories: General, Router
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: