Exposing a SOAP service as a Managed REST API
This article will focus on how you can create a REST APIs to expose your underlying SOAP based web services using the WSO2 API Cloud.
Introduction
First let’s take a look at the difference between SOAP and REST. SOAP is more of a protocol, whereas REST is an architectural style hence we cannot really compare them one on one. As per my experience they both work well in distributed enterprise environments and are language- and platform-independent.
However, in SOAP (Simple Object Access Protocol), changes are made by accessing server objects remotely, while REST(Representational State Transfer) focuses on representing server objects on the client side (which is the Representational in REST). Furthermore REST, as an architectural style. It is simple to understand and closer to the web itself in its design. This is the major challenge users with SOAP services try to overcome. SOAP is comparatively complicated and not entirely user friendly and not always self intuitive. This is where REST comes in handy. In this post I will be discussing how we can wrap the complicated soap services and expose them as a simple self intuitive REST API with the WSO2 API Cloud. Let’s get started.
Use Case
- We have a SOAP service exposed by the url. http://ws.cdyne.com/phoneverify/phoneverify.asmx?wsdl
- This SOAP web service exposes an operation to check if a given phone number if valid. However the request which this web service expects is something similar to the below.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:quer="http://ws.cdyne.com/PhoneVerify/query">
<soapenv:Header/>
<soapenv:Body>
<quer:CheckPhoneNumber>
<!--Optional:-->
<quer:PhoneNumber>1223334477888</quer:PhoneNumber>
<!--Optional:-->
<quer:LicenseKey>0</quer:LicenseKey>
</quer:CheckPhoneNumber>
</soapenv:Body>
</soapenv:Envelope>
In addition to that we need to optionally pass the operation as well.
SOAPAction: "http://ws.cdyne.com/PhoneVerify/query/CheckPhoneNumber"
Passing such a complicated payload is not entirely user friendly. And there are several mistakes which users can make when requesting the endpoint with such a complex payload. The only values we need from end users are the Phone Number and LicenseKey so why not only get those values from our REST API?
Let’s see how we can wrap this complicated backend SOAP web service and expose it as a REST Service which takes in parameters in a restful manner. Here are some examples of how input parameters can be given in designing this REST API.
- As a JSON string with key value pairs.
- As single inputs for the required parameters.
In this post I will be designing it with single inputs for the required parameters.
Tutorial
Log in to the WSO2 Cloud
First log into the WSO2 Cloud and navigate to the API Cloud. You can read about the offerings of WSO2 Cloud in this post.
Design your Managed REST API
In the API cloud select the option to add a new API. We will be creating an API to demonstrate an invocation to the backend soap service as explained above. http://ws.cdyne.com/phoneverify/phoneverify.asmx?wsdl
Give a name, context and version to the API and add a resource name with a GET Method.
Note: By default SOAP backends expect the HTTP verb of the resource to be a POST resource. However with the API Cloud we can even hide this and expose our API in the most simplest form using a GET HTTP verb.
The resource name can be anything which you like since we will invoke the actual service using a custom sequence and the resource name would be ignored in the actual call to the underlying soap service.
Mention the URI template as indicated in the below screenshot. Include the two parameters below. Since we need this information from the end users to pass to our SOAP service.
- PhoneNumber
- LicenseKey
Map the endpoint to your API
Next go to the implement tab. And select the endpoint type as HTTP/SOAP Endpoint and specify the endpoint as http://ws.cdyne.com/phoneverify/phoneverify.asmx
There is an important step we need to do here. We need to set the SOAP version for this request. In order to do that we need to select the advanced option for the endpoint. You can find the icon to configure advanced options below.
Click on the advanced option and set the format as the SOAP version we need to set. In this case we will set it as SOAP 1.1. You can set the SOAP version which your endpoint expects here.
Create the protocol transformation (from REST to SOAP)
In the next step we need to add a custom sequence which transforms this RESTcall to a SOAP call and sends it to our SOAP backend. So we need to create a sequence similar to the one below and save it as an xml file. I have saved my sequence as PhoneVerifyInSequence.xml. You can even download this from here. I will explain what each step does.
<?xml version="1.0" encoding="UTF-8"?>
<sequence xmlns="http://ws.apache.org/ns/synapse" name="PhoneVerifyInSequence" ><!-- This is the SOAP action which the backend SOAP operation expects, we set it in a header mediator --><header description="SOAPAction" name="SOAPAction" scope="transport" value="http://ws.cdyne.com/PhoneVerify/query/CheckPhoneNumber"/><!-- We are storing the input values which the end users input for these values into these two properties --><property name="uri.var.phoneNumber" expression="$url:PhoneNumber"/>
<property name="uri.var.licenseKey" expression="$url:LicenseKey"/><!-- Since we do not want the URL pattern we mentioned to be sent to the backend we need to add the below property to remove it --><property name="REST_URL_POSTFIX" scope="axis2" action="remove"/><!-- Now we need to create the actual payload which the backend requires. For that we use the payload factory mediator --><payloadFactory description="transform" media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:quer="http://ws.cdyne.com/PhoneVerify/query">
<soapenv:Header/>
<soapenv:Body>
<quer:CheckPhoneNumber>
<quer:PhoneNumber>$1</quer:PhoneNumber>
<quer:LicenseKey>$2</quer:LicenseKey>
</quer:CheckPhoneNumber></soapenv:Body>
</soapenv:Envelope>
</format>
<args>
<arg expression="get-property(‘uri.var.phoneNumber’)"/>
<arg expression="get-property(‘uri.var.licenseKey’)"/>
</args>
</payloadFactory><!-- Here we are setting the content type which the web service expects --><property description="messageProperty" name="messageType" scope="axis2" type="STRING" value="application/soap+xml"/>
</sequence>
Next we need to include an out sequence which transforms the response as a JSON object and sends it back to the API as the response in JSON format. There is an out of the box sequence in the API Cloud as xml_to_json_out_message. I will explain that sequence here. This is what we will be using for the response transformation.
<?xml version="1.0" encoding="UTF-8"?>
<sequence xmlns="http://ws.apache.org/ns/synapse" name="xml_to_json_out_message" ><!-- Transforms the response to a JSON --><property description="message" name="messageType" scope="axis2"
type="STRING" value="application/json"/></sequence>
Upload the PhoneVerifyInSequence.xml as the insequence and select the xml_to_json_out_message as the out sequence from the available sequences in the sequence drop down. Refer the below image on what the Message Mediation Policies section should look like after this change.
In the manage tab select a tier as Gold and publish the API.
Next navigate to the API in the store and subscribe to it with the tier you selected when designing your API and generate the tokens for the subscription. Learn how to subscribe to an API here.
Next we can now invoke the API by just simply providing the phone number and the license key as the input. As you can see the response was returned in JSON format.
Congratulations! You have successfully exposed your SOAP web service as a simple REST API.
How does this work?
This phone number and license key is captured into a variable and then using this the request payload will be constructed.
Using the header mediator we can then set the SOAP action and the constructed request payload will be sent to the soap backend. The soap backend will accept this and return a xml payload. But due to the transformation in the out sequence it will return it as a JSON object. This is what happens in the transformation.
Contact us
If you have further questions or need clarifications reach out to us at cloud@wso2.com.