2008/12/31
31 Dec, 2008

Invoking Web Services from a Mashup

  • Kieth Chapman
  • - WSO2

Introduction

The objective of a mashup is to compose or mash information obtained from several sources. The WSO2 Mashup Server supports several ways and means of getting such information. Invoking external Web services (or mashups deployed on the same server) is a popular mechanism of obtaining such information.

Invoking Web services from your mashup

The mashup server supports three main ways of invoking Web services. They are,

  • Using a generated JavaScript stub
  • Using the WSRequest Host Object
  • Using the Dynamic version of the WSRequest Host Object

Each of these mechanisms have there pros and cons. The following table summarizers them:

 

Method Pros Cons
Generated Stub

Ease of use

No need to frame the message manually, the stub takes care of it

Ability to invoke services secured using Username Token (With ease)

No configuration needed (All such configurations are picked off the WSDL)

Ease of use depends on the schema of the messages used by the service. If the schemas are complex the stub might not be able to databind them effectively.

Not able to invoke all secured services

The service needs be described using WSDL (Either WSDL 1.1 or WSDL 2.0)

WSRequest Host Object

Offers greater flexibility

Ability to invoke services secured using Username Token

Users need to frame the request message manually

All options are needed to be configured manually

Dynamic version of the WSRequest Host Object

Ease of use

Ability to access secured services provided that the WSDL describes such security requirements using WS-Securoty Policy (Even if it employs complex security scenarios)

No configuration needed (All such configurations are picked off the WSDL)

Not too good when it comes to performance (As the WSDL needs to be parsed in order to configure the WSRequest Host Object)

Users need to frame the request message manually

The service needs to have a WSDL 1.1 description

There are certain features that are supported by all above methods. Some of them are,

  • Ability to invoke services in both synchronous and asynchronous manner
  • Ability to invoke services over several transports (Provided that these transports are configured in the Mashup Server)
  • Ability to invoke both REST and SOAP services

Let's now take a deeper look at each of these. We'll also go into examples that demonstrate how these features can be used.

For samples that do not involve security, I will be using the Currency Converter service that is available on webservicex.net. Details of this service can be found at https://www.webservicex.net/CurrencyConvertor.asmx. For others that involve security I will be using a couple of demo services that I've put up on mooshup.com (Which is a community site where users can upload there mashups to. This site is running an instance of the WSO2 Mashup Server).

Invoking Services using a generated stub

If you are invoking a service that runs on the WSO2 Mashup Server (local or remote), you should be able to get a stub for that service by visiting its home page. The home page of a mashup lists several versions of stubs that you could use. For the purpose of invoking external services from within a mashup the E4X version of the stub should be used (The DOM version of the stub would not work when used by a mashup, it is to be used in a browser environment). If the service you are trying to invoke resides on the same Mashup Server then it is advisable to use the localhost version of the stub.

If the service been invoked is not running on a WSO2 Mashup Server you may first need to generate a stub for the service. This can be done by using the stub generation tool that ships with the WSO2 Mashup Server (It is available at https://localhost:7762/stub_gen.jsp). Please make sure that you use the E4X version of the stub.

When stubs are used to invoke a service, you should save the stub inside the resources folder of your mashup (Even though the Mashup Server allows you to import a stub for a mashup using relative URIs, it is not advisable to do so, as the Mashup Server does not handle inter-service dependencies) and import it from there. Stubs can be imported using the “system.include()” function. E.g. Assuming the stub was saved in the resources folder as “stub.js” this could be included as “system.include(“stub.js”);”

For more details on how the stubs generated could be used, please refer the “Using stubs” section of the Mashup Server documentation.

Now that we've seen how a stub can be generated for a service and imported to a mashup, it is time to see how this stub can be used. For your convenience each stub generated would contain a section at the top (which is commented out) and shows how the stub could be used to invoke the service. Let's get into some code samples now.

Invoking a service in a synchronous manner

Invoking a service in a synchronous manner using a stub is just a matter of calling a simple JavaScript function passing in the arguments accepted by the service. The stub would take care of creating the payload using the arguments passed into it as well as unwrapping the response for you. The stub will also configure the WSRequest host object with details needed to invoke the service. These details include the service EPR (Endpoint reference), the SOAP action required and so on.

system.include("currencyConvertorStub.js"); 

convertionRateUsingStub.inputTypes={"fromCurrency" : "string", "toCurrency" : "string"}; 
convertionRateUsingStub.outputType="string"; 
function convertionRateUsingStub(fromCurrency, toCurrency){ 
    var rate =  CurrencyConvertor.ConversionRate(fromCurrency, toCurrency); 
    return "The conversion rate is " + rate; 
}

Invoking a service in a asynchronous manner

In order to invoke a service in a asynchronous manner using a stub the user needs to set a callback function. The stub uses this as a hint to decide whether the call it makes should be synchronous or asynchronous. Alternatively users could set a function as the onError property which would be triggered of a fault is received as the response. In my example I define two function “success” and “failure” and set them as the callback and onError functions. (I've set the visible property of the “success” and “failure” functions to false so that these function would not appear as function of this mashup, they are kept for private use).

convertionRateUsingStubAsync.inputTypes={"fromCurrency" : "string", "toCurrency" : "string"}; 
convertionRateUsingStubAsync.outputType="string"; 
function convertionRateUsingStubAsync(fromCurrency, toCurrency){ 
    CurrencyConvertor.ConversionRate.callback = success; 
    CurrencyConvertor.ConversionRate.onError = failure; 
    CurrencyConvertor.ConversionRate(fromCurrency, toCurrency); 
    return "Invoked the ConversionRate operation in a async manner"; 
} 

success.visible=false; 
function success(ConversionRateResponse) { 
    system.log("The response of ConversionRate was : " + ConversionRateResponse); 
} 

failure.visible=false; 
function failure(error) { 
    system.log("Error occured while calling ConversionRate, Reason is : " + error.reason); 
}

Note: By default the stub would use the SOAP 1.2 endpoint to invoke the service (If the service does not contain a SOAP 1.2 endpoint if will look for a SOAP 1.1 endpoint. If that fails it uses the first endpoint defined in the WSDL). If users wish to change the endpoint, they could change it by setting the “endpoint” property of the service.

Users could also change the endpoint address of a specific endpoint by using the setAddress function in the stub. The syntax is “{service}.setAddress(endpoint, url)”.

 

Invoking a service secured using Username Token authentication

A cool feature of the JavaScript stubs generated by the Mashup Server is that it has the ability to invoke services secured using Username Token authentication. To invoke such a service users can set the Username and password using a couple of JavaScript properties on the stub. Namely “{service}.username” and “{service}.password”. For this example, let's use the usernameTokenService service which is deployed on mooshup.com. It has been secured such that any registered user on mooshup.com can access this service.

Save the stub for the usernameTokenService (https://mooshup.com/services/keith/usernameTokenService?stub&lang=e4x&content-type=text/plain) in the resources folder of your mashup.

system.include("usernameTokenServiceStub.js");

invokeSecuredService.inputTypes={"firstParam" : "string" , "secondParam" : "string"};
invokeSecuredService.outputType="string";
function invokeSecuredService(firstParam, secondParam) {
    usernameTokenService.username = "yourUsername";
    usernameTokenService.password = "yourPassword";
    return usernameTokenService.demo(firstParam, secondParam);
}

Note : Please replace “yourUsername” and “yourPassword” with your credentials before running this example.

 

Invoking services using the WSRequest Host Object

When using the WSRequest Host Object to invoke a service the user is expected to provide all details needed to invoke the service. The user also has to provide the payload needed to invoke the service. This requires the user to have some knowledge about the service (In order to know what the payload should look like, this could be inferred by looking at the WSDL of the service).

 

Invoking a service is a synchronous manner

Invoking a service in a synchronous manner is straightforward and can be done in the following manner:

convertionRateUsingWSRequest.inputTypes={"fromCurrency" : "string", "toCurrency" : "string"}; 
convertionRateUsingWSRequest.outputType="string"; 
function convertionRateUsingWSRequest(fromCurrency, toCurrency){ 
    var request = new WSRequest();
    var options = new Array();
    options.useSOAP = 1.1;
    options.useWSA = false;
    options.action = "https://www.webserviceX.NET/ConversionRate";
    var payload = {fromCurrency}{toCurrency};
    var result;
    try {
        request.open(options,"https://www.webservicex.net/CurrencyConvertor.asmx", false);
        request.send(payload);
        var response = request.responseE4X;
        var ns = new Namespace('https://www.webserviceX.NET/');
        result = response.ns::["ConversionRateResult"].toString();
    } catch (e) {
        system.log(e.toString(),"error");
        return result = e.toString(); 
    }
    return result;
}

Invoking a service is a asynchronous manner

When a service is invoked in a asynchronous manner, the user needs to set a function that will be called when the state of the invocation changes. These states are identical to the readyStates found when using the XMLHttpRequest object in a browser enviorenment. readyState 4 states that the invocation was completed succesfully.

convertionRateUsingWSRequestAsync.inputTypes={"fromCurrency" : "string", "toCurrency" : "string"}; 
convertionRateUsingWSRequestAsync.outputType="string"; 
function convertionRateUsingWSRequestAsync(fromCurrency, toCurrency){ 
    var request = new WSRequest();
    var options = new Array();
    options.useSOAP = 1.1;
    options.useWSA = false;
    options.action = "https://www.webserviceX.NET/ConversionRate";
    request.onreadystatechange = function() {
         handleResponse(request);
    };
    var payload = {fromCurrency}{toCurrency};
    var result;
    try {
        request.open(options,"https://www.webservicex.net/CurrencyConvertor.asmx", true);
        request.send(payload); 
        result = "Invoked the CurrencyConvertor service";        
    } catch (e) {
        system.log(e.toString(),"error");
        return result = e.toString(); 
    }
    return result;
}

handleResponse.visible=false;
function handleResponse(request){
    if (request.readyState == 4) {
        var response = request.responseE4X;
        var ns = new Namespace('https://www.webserviceX.NET/');
        var result = response.ns::["ConversionRateResult"].toString();
	system.log(result);
    }
}

Invoking a service secured using Username Token authentication

When invoking a service secured using Username Token authentication, the user needs to set a few properties on the options object. They are the "username" property and the "password" property. The user also needs to set the "useWSS" property to true. This instructs the WSRequest Host object to use the username and password provided as security tokens. If the "useWSS" property is not set to true the username and password provided will be used as credentials for HTTP Basic Authentication.

invokeSecuredService.inputTypes={"firstParam" : "string" , "secondParam" : "string"};
invokeSecuredService.outputType="string";
function invokeSecuredService(firstParam, secondParam) {
    var request = new WSRequest();
    var options = new Array();
    options.useSOAP = 1.2;
    options.useWSA = true;
    options.action = "https://services.mashup.wso2.org/usernameTokenService/ServiceInterface/demoRequest";
    options.username = "yourUsername";
    options.password = "yourPassword";
    options.useWSS = true;
    var payload = {firstParam}{secondParam};
    var result;
    try {
        request.open(options,"https://mooshup.com/services/keith/usernameTokenService", false);
        request.send(payload);
        var response = request.responseE4X;
        result = response["return"].toString();
    } catch (e) {
        system.log(e.toString(),"error");
        return result = e.toString(); 
    }
    return result;
}

Invoking services using the dynamic version of the WSRequest Host Object

Using the dynamic version of the WSRequest Host Object is much simpler than using the plain WSRequest Host Object. This is because the user needs not configure the WSRequest Host Object, the WSO2 Mashup Server will do it according to the details presented in the WSDL. Another important feature of the dynamic version of the WSRequest Host Object is that it can be used to invoke services which are secured using complex security scenarios with ease.

As with the plain version of the WSRequest Host Object, users need to provide the payload needed to access the service.

Invoking a service is a synchronous manner

Invoking a service in a synchronous manner is straightforward and can be done in the following manner.

convertionRateUsingDynamicWSRequest.inputTypes={"fromCurrency" : "string", "toCurrency" : "string"}; 
convertionRateUsingDynamicWSRequest.outputType="string"; 
function convertionRateUsingDynamicWSRequest(fromCurrency, toCurrency){ 
    var request = new WSRequest();
    var payload = {fromCurrency}{toCurrency};
    var result;
    try {
        var service = new QName("https://www.webserviceX.NET/", "CurrencyConvertor");
        request.openWSDL("https://www.webservicex.net/CurrencyConvertor.asmx?WSDL", false, new Array(), service, "CurrencyConvertorSoap");
        request.send("ConversionRate", payload);
        var response = request.responseE4X;
        var ns = new Namespace('https://www.webserviceX.NET/');
        result = response.ns::["ConversionRateResult"].toString();
    } catch (e) {
        system.log(e.toString(),"error");
        return result = e.toString(); 
    }
    return result;
}

Invoking a service is a asynchronous manner

This is similar to using the plain version of the WSRequset Host Object.

convertionRateUsingDynamicWSRequestAsync.inputTypes={"fromCurrency" : "string", "toCurrency" : "string"}; 
convertionRateUsingDynamicWSRequestAsync.outputType="string"; 
function convertionRateUsingDynamicWSRequestAsync(fromCurrency, toCurrency){ 
    var request = new WSRequest();
    request.onreadystatechange = function() {
         handleResponse(request);
    };
    var payload = {fromCurrency}{toCurrency};
    var result;
    try {
        var service = new QName("https://www.webserviceX.NET/", "CurrencyConvertor");
        request.openWSDL("https://www.webservicex.net/CurrencyConvertor.asmx?WSDL", true, new Array(), service, "CurrencyConvertorSoap");
        request.send("ConversionRate", payload);
        result = "Invoked the CurrencyConvertor service";        
    } catch (e) {
        system.log(e.toString(),"error");
        return result = e.toString(); 
    }
    return result;
}

handleResponse.visible=false;
function handleResponse(request){
    if (request.readyState == 4) {
        var response = request.responseE4X;
        var ns = new Namespace('https://www.webserviceX.NET/');
        var result = response.ns::["ConversionRateResult"].toString();
	system.log(result);
    }
}

Invoking a secured service

Provided the service you are invoking expresses its security requirements using WS-Security policy in the WSDL, the dynamic version of the WSRequest Host Object will help you access that service with ease. In order to access secured services you will need to set up the certificates needed (depending on the manner the third party service is secured).

For this example, let's access a service which is secured using security scenario 7 (Encrypt only - Username Token Authentication) in the WSO2 Mashup Server. This scenario expects users to send there username and password when accessing the service, they also need to encrypt the payload sent. The service securedService which is deployed on mooshup.com has been secured using scenario 7.

Setting up the certificates

Before securing a service or invoking a secured service, it is important to set up your personal keystore. Details on how this could be done can be found in the Mashup Server documentation. Refer the "Keystore Management" section.

Note : When creating the keystore please make sure that you use the “-keyalg RSA” option as well. This is needed for the encryption scenarios to work out of the box. e.g “keytool -genkey -alias keith -keystore keith.jks -keyalg RSA”.

In order to communicate with a service which requires your payload to be encrypted, you need to have the public key of that service. In our example, the secured service up on mooshup.com is secured using my keystore. Hence, you will need my public key to access this service. You can get it from here.

Note : In order to get a public key of a keystore you could use the keytool as follows,

keytool -export -keystore keith.jks -alias keith > cert.der

cert.der would be the public key of this keystore.

Once you obtain this public key you will need to upload it to your keystore (In order to encrypt the request this public key is needed hence it should be available in your keystore). Please refer the "Keystore Management" section in the Mashup Server documentation for instructions on how this could be done. Lets assume that you uploaded it with the alias “keithspublickey”. Now we are all set to access this secured service.

invokeSecuredService.inputTypes={"firstParam" : "string" , "secondParam" : "string"};
invokeSecuredService.outputType="string";
function invokeSecuredService(firstParam, secondParam) {
    var request = new WSRequest();
    var options = new Array();
    options.encryptionUser = "keithspublickey";
    options.username = "yourUsername";
    options.password = "yourPassword";
    var payload = {firstParam}{secondParam};
    var result;
    try {
        var service = new QName("https://services.mashup.wso2.org/securedService", "securedService");
        request.openWSDL("https://mooshup.com/services/keith/securedService?wsdl", false, options, service, "SOAP12Endpoint");
        request.send("demo", payload);
        var response = request.responseE4X;
        result = response["return"].toString();
    } catch (e) {
        system.log(e.toString(),"error");
        return result = e.toString(); 
    }
    return result;
}

Future Directions

Although the WSO2 Mashup Server allows users to access external Web services with ease there are a few more improvements that could be done. These improvements would be available in future releases.

  • Make changes to the WSRequest Host Object so that it will allow users to set a custom Security policy.
  • Allow users to provide and overide additional options when the dynamic version of the WSRequest Host Object is used. (For e.g ability to instract the use of HTTP Basic Authentication).

Conclusion

This tutorial shows various ways and means the WSO2 Mashup Server provides access to external Web services. It also explains the pros and cons of each mechanism and gives code samples on how these mechanisms could be used effectively.

Author

Keith Chapman, Senior Software Engineer, WSO2 Inc. keith at wso2 dot com

 

About Author

  • Kieth Chapman
  • WSO2 Inc.