Today I saw two things I cannot recall having seen before:
The first thing was a WSDL for a web service in which the target namespace and the service address were identical. As far as I know, there is nothing that disallows this, it is only peculiar – at least to me.
The second thing was that when I put a Mule web service proxy in front of the web service exposing the WSDL mentioned above, not only the port address of the web service endpoint were rewritten, but also the target namespace.
If I expected you to believe my word, then this blog post would end here. However, I do not expect you to believe my word and I wanted to examine this behaviour more closely.
Before we look at some hard proof, I just want to mention that I have used Mule CE 3.4.0 when implementing the example program in this post.
Preparations
In your favourite Eclipse-based IDE with the MuleStudio plug-in installed, create a Mule project. In this Mule project, create two Mule flows; one named “helloserviceflow” and another named “wsproxybug”.
In the flow “helloserviceflow”, paste the following Mule flow configuration:
<?xml version="1.0" encoding="UTF-8"?> <mule xmlns:cxf="http://www.mulesoft.org/schema/mule/cxf" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:test="http://www.mulesoft.org/schema/mule/test" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.mulesoft.org/schema/mule/cxf http://www.mulesoft.org/schema/mule/cxf/current/mule-cxf.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd http://www.mulesoft.org/schema/mule/test http://www.mulesoft.org/schema/mule/test/current/mule-test.xsd"> <spring:beans> <spring:bean id="helloService" class="se.ivankrizsan.mule.wsproxybug.HelloService"/> </spring:beans> <flow name="HelloServiceFlow"> <inbound-endpoint address="http://localhost:8182/services/GreetingService" exchange-pattern="request-response"/> <cxf:jaxws-service serviceClass="se.ivankrizsan.mule.wsproxybug.HelloService"/> <component> <spring-object bean="helloService"/> </component> </flow> </mule>
Create a Java-class that implements the HelloService:
package se.ivankrizsan.restexample; import java.util.Date; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; /** * SOAP web service endpoint implementation class that implements a service that extends greetings. * * @author Ivan Krizsan */ @WebService(targetNamespace = "http://localhost:8182/services/GreetingService") public class HelloService { /** * Greets the person with the supplied name. * * @param inName Name of person to greet. * @return Greeting. */ @WebResult(name = "greeting") public String greet(@WebParam(name = "inName") final String inName) { return "Hello " + inName + ", the time is now " + new Date(); } }
Note that in the @WebService annotation I have specified the target namespace of the web service to a URL that is identical to the address of the inbound endpoint in the HelloServiceFlow.
The final part of the preparations consist of installing the HttpRequester add-on in Mozilla Firefox. This add-on makes it possible to send different kinds of HTTP requests (GET, POST, PUT etc) and examine the result from within Firefox.
Update: The HttpRequester add-on is no longer available. Instead I recommend using Postman.
Proxy the Webservice
In the “wsproxybug” flow, paste the following Mule configuration:
<?xml version="1.0" encoding="UTF-8"?> <mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:pattern="http://www.mulesoft.org/schema/mule/pattern" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:spring="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="CE-3.4.0" xsi:schemaLocation=" http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.mulesoft.org/schema/mule/pattern http://www.mulesoft.org/schema/mule/pattern/current/mule-pattern.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd"> <pattern:web-service-proxy name="WSProxyBugFlow"> <http:inbound-endpoint address="http://localhost:8190/proxiedservices/GreetingService" /> <http:outbound-endpoint address="http://localhost:8182/services/GreetingService" /> </pattern:web-service-proxy> </mule>
Run the Example Program
We are now ready to run the example program. Right-click on the Mule project in Eclipse and select Run As -> Mule Application.
After some time, there should be output in the console indicating that the Mule application was started successfully.
Examine the HelloService
As a first step, we take a look at the HelloService to learn what data it presents about itself:
In Firefox, open the HttpRequester add-on and paste the URL http://localhost:8182/services/GreetingService?wsdl in the upper left part of the dialog. Then click the GET button (third from the left under the URL field). The result should look like this:
Using HttpRequester we can see, among other things that:
- The request took 47 ms to complete.
This is in my environment – the time may vary depending on your environment. - The content type of the response is “text/xml”.
- The namespace in the WSDL is the same that we specified in the @WebService annotation in the HelloService class earlier:
<?xml version='1.0' encoding='UTF-8'?> <wsdl:definitions name="HelloServiceService" targetNamespace="http://localhost:8182/services/GreetingService" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://localhost:8182/services/GreetingService" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- The port of the service, where the endpoint address of the service is specified, looks like this:
<wsdl:port binding="tns:HelloServiceServiceSoapBinding" name="HelloServicePort"> <soap:address location="http://localhost:8182/services/GreetingService"/> </wsdl:port>
Examine the Service Proxy
Let’s look at the proxied version of the HelloService.
Repeat the exercise of sending a GET request for a WSDL in HttpRequester, but this time use this URL: http://localhost:8190/proxiedservices/GreetingService?wsdl
The result should look something like this:
Examining the result more closely, we see that:
- It took more than three times as long to complete the request through the proxy.
In my case 175 ms through the proxy compared to 47 ms direct access. Perhaps it would be more just to say that the proxy added about 125 ms overhead to a request. - The content type of the response returned by the proxy is “text/plain”.
- The target namespace of the WSDL has changed:
“Computer” is the name of my computer and the proxy has apparently replaced “localhost” with the resolved name of my computer’s address.
This was not quite expected behaviour of the proxy.
<?xml version='1.0' encoding='UTF-8'?> <wsdl:definitions name="HelloServiceService" targetNamespace="http://Computer:8190/proxiedservices/GreetingService" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Computer:8190/proxiedservices/GreetingService" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
- The port of the service looks like this:
The proxy did this part well.
<wsdl:port binding="tns:HelloServiceServiceSoapBinding" name="HelloServicePort"> <soap:address location="http://Computer:8190/proxiedservices/GreetingService"/> </wsdl:port>
The Mule WSProxy Class
The web service proxy in Mule is implemented by the class org.mule.module.ws.construct.WSProxy, which in turn contains a number of inner classes. One of the inner classes contain a method named modifyServiceAddress, which is responsible for rewriting the service address in a WSDL. Unfortunately, it uses the method replaceAll on the String class which result in the behaviour we have seen.
What is even more unfortunate is the design of the WSProxy class with private inner classes which, although there is a constructor taking an instance of AbstractProxyRequestProcessor as a parameter, I cannot see how to create a subclass that resides outside of the WSProxy class of the AbstractProxyRequestProcessor class.
Alternatives?
Well, are there any alternatives?
Of course you can implement something of your own using Mule, while you wait for this issue to be fixed: https://www.mulesoft.org/jira/browse/MULE-7466
How about non-Mule alternatives?
I will leave pure HTTP proxy implementations of various kinds out of the discussion since they will not be able to rewrite service addresses in WSDLs. However, it may be worthwhile to ask yourself if you really need a web service proxy that is able to rewrite service addresses in WSDLs.
So, lets assume that you must have a web service proxy that is able to rewrite service addresses. What to do?
Apache Camel
If alternatives that do not use Mule may be considered then Apache Camel is definitely a candidate. There is even an example that implements a web service proxy. In the 2.13.0 distribution, it is located in the directory “camel-example-cxf-proxy” in the “examples” directory.
I took the liberty of modifying the proxy example slightly. First of all, I wanted pure proxying without any modifications to requests. Second I adapted the proxy to HelloService. The result looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:camel="http://camel.apache.org/schema/spring"
xmlns:cxf="http://camel.apache.org/schema/cxf"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd">
<!--
Endpoint of the web service proxy which:
Has the address specified by the address attribute.
Exposes the port specified by the endpointName attribute in the
service specified by the serviceName attribute from the WSDL
specified by the wsdlURL attribute.
Note how the namespace of the WSDL is specified and its namespace
prefix is used in the port and service names.
-->
<cxf:cxfEndpoint id="proxyFrontEnd"
address="http://localhost:8290/proxiedservices/GreetingService"
endpointName="gs:HelloServicePort"
serviceName="gs:HelloServiceService"
wsdlURL="http://localhost:8182/services/GreetingService?wsdl"
xmlns:gs="http://localhost:8182/services/GreetingService" />
<!--
Apache Camel route that proxies a web service and logs requests
and responses to the proxy.
-->
<camelContext xmlns="http://camel.apache.org/schema/spring">
<endpoint id="callRealWebService" uri="http://localhost:8182/services/GreetingService" />
<route>
<from uri="cxf:bean:proxyFrontEnd?dataFormat=MESSAGE" />
<!-- Log incoming request. -->
<to uri="log:input" />
<!-- Invoke the web service that is being proxied. -->
<to ref="callRealWebService" />
<!-- Log outgoing response. -->
<to uri="log:output" />
</route>
</camelContext>
</beans>
Note that when starting the Apache Camel proxy, the HelloService must be up and running.
So does it work?
As can be seen from the above picture, it does work. The content type is text/xml, the target namespace of the WSDL is unchanged and, as can be seen below, the service endpoint address has been rewritten. The overhead added by the Camel proxy is about the same as that of the Mule proxy.
<wsdl:port binding="tns:HelloServiceServiceSoapBinding" name="HelloServicePort"> <soap:address location="http://Computer:8190/proxiedservices/GreetingService"/> </wsdl:port>
Membrane
If there are a lot of web services that are to be proxied and/or configuration of web service proxies in a GUI is preferred, then the Membrane Service Proxy may be an option.