If you are doing SOAP with Java, you probably are using the JAX-WS standard.
One implementation of it, the reference implementation, is JAX-WS RI (duh). One of its features is that it generates a WSDL dynamically upon request.
This is convenient if you have separated development, test, accept and production environments: the WSDL will automatically detect on which domain name it is running and publish the endpoints using that domain name. It is less convenient if it detects it wrong…
Looking at the source of JAX-WS RI, you’ll find that HttpAdapter
is responsible for handling HTTP requests and serving out the HTML description of the web services as well as the actual WSDL. To generate the Port descriptions it ultimately calls ServletConnectionImpl.getBaseAddress(HttpServletRequest request)
:
static @NotNull String getBaseAddress(HttpServletRequest request) { StringBuilder buf = new StringBuilder(); buf.append(request.getScheme()); buf.append("://"); buf.append(request.getServerName()); buf.append(':'); buf.append(request.getServerPort()); buf.append(request.getContextPath()); return buf.toString(); }
This means that the Port definition it generates depends on the actual request being done. So, for example, if you ask for the WSDL on your development machine using something like http://127.0.0.1:8080/services?WSDL
, you’ll get a WSDL with Port bindings on 127.0.0.1
. If you instead ask it using http://localhost:8080/services?WSDL
, you’ll get different Port bindings: now they are published on localhost
. This might seem ok, but might lead to unexpected problems in real life situations.
One such real life situation is if your production website is behind a load balancer which does HTTPS offloading. In this case, the client application will connect over HTTPS to your load balancer. The loadbalancer handles all the HTTPS stuff and forwards it to your application using HTTP. Now, JAX-WS RI will generate a WSDL with port bindings on HTTP instead of HTTPS.
Although JAX-WS RI claims to be very pluggable, I haven’t been able to find a simple solution for this. Instead, I ended up writing a Servlet Filter which wraps the HttpServletRequests and returns the original domains.
public class JaxWsLoadBalancerFixFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { LoadBalancerRequest actualRequest = new LoadBalancerRequest((HttpServletRequest) request); chain.doFilter(actualRequest, response); } /** * Wrapper around httpServletRequest which overrides server name & port. */ private static class LoadBalancerRequest extends HttpServletRequestWrapper { private static final int HTTPS_PORT = 443; private HttpServletRequest originalRequest; public LoadBalancerRequest(HttpServletRequest request) { super(request); this.originalRequest = request; } @Override public String getScheme() { if (shouldRewrite()) { return "https"; } else { return originalRequest.getScheme(); } } private boolean shouldRewrite() { // create your own strict rules here } @Override public int getServerPort() { if (shouldRewrite()) { return HTTPS_PORT; } else { return originalRequest.getServerPort(); } } } @Override public void destroy() { } @Override public void init(FilterConfig filterConfig) throws ServletException { } }
Depending on your needs, you can rewrite all requests or have it depend on configuration. Some load balancers actually give you an HTTP header indicating if the original request was secure so you might be able to use that.