Java web service client generated in Netbeans - getting Http Status Code 307

ATDeveloper picture ATDeveloper · May 26, 2011 · Viewed 7k times · Source

I use Netbeans to generate web service client code, client-style JAX-WS, so i can invoke a web service API.

However, when I invoke the web service API, I get the exception: com.sun.xml.internal.ws.client.ClientTransportException: The server sent HTTP status code 307: Temporary Redirect

Why do I get this? What is the workaround? I know the problem isn't with the web service itself, because I can get responses fine via soapUI and .Net.

Answer

Yuriy Nakonechnyy picture Yuriy Nakonechnyy · Mar 19, 2012

Faced the same problem about a month ago.

Web service client classes were generated using Apache CXF and web service returned HTTP status 307, which led to the same exception.

Invocation of the same web service method using soapUI with property Follow Redirects set to true was successful and returned needed data.

After googling awhile, it looked like there is no property to enable following redirects in the JAX-WS for this.

So, below is the code which is currently working, though I'm not sure it is compliant with any standards:

Supposing generated client classes looks like:

// generated service class
public class MyWebServiceClient extends javax.xml.ws.Service {
    // ...
    private final QName portName = "...";
    // ...
    public RetrieveMyObjects getRetrieveMyObjects() {
        return super.getPort(portName, RetrieveMyObject.class);
    }
    // ...
}

// generated port interface
// annotations here
public interface RetrieveMyObjects {

    // annotations here
    List<MyObject> getAll();

}

Now, upon executing following code:

MyWebServiceClient wsClient = new MyWebServiceClient("wsdl/location/url/here.wsdl");
RetrieveMyObjectsPort retrieveMyObjectsPort = wsClient.getRetrieveMyObjects();

wsClient should return instance which is both instance of RetrieveMyObjects & javax.xml.ws.BindingProvider interfaces. It is not stated anywhere on the surface of JAX-WS, but it seems that a lot of code is based on that fact. One can re-assure him\herself by executing something like:

if(!(retrieveMyObjectsPort instanceof javax.xml.ws.BindingProvider)) {
    throw new RuntimeException("retrieveMyObjectsPort is not instance of " + BindingProvider.class + ". Redirect following as well as authentication is not possible");
}

Now, when we are sure that retrieveMyObjectsPort is instance of javax.xml.ws.BindingProvider we can send plain HTTP POST request to it, simulating SOAP request (though it looks incredibly incorrect & ugly, but this works in my case and I didn't find anything better while googling) and check whether web service will send redirect status as a response:

// defined somewhere before
private static void checkRedirect(final Logger logger, final BindingProvider bindingProvider) {
    try {
        final URL url = new URL((String) bindingProvider.getRequestContext().get(ENDPOINT_ADDRESS_PROPERTY));
        logger.trace("Checking WS redirect: sending plain POST request to {}", url);
        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setInstanceFollowRedirects(true);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "text/html; charset='UTF-8'");
        connection.setDoOutput(true);

        if(connection.getResponseCode() == 307) {
            final String redirectToUrl = connection.getHeaderField("location");
            logger.trace("Checking WS redirect: setting new endpoint url, plain POST request was redirected with status {} to {}", connection.getResponseCode(), redirectToUrl);
            bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, redirectToUrl);
        }
   } catch(final Exception e) {
       logger.warn("Checking WS redirect: failed", e);
   }
}

// somewhere at the application start
checkRedirect(logger, (BindingProvider) retrieveMyObjectsPort);

Now, what this method does is: it takes BindingProvider.ENDPOINT_ACCESS_PROPERTY of retrieveMyObjectsPort i.e. the url to which this port method will be sending SOAP requests and sends plain HTTP POST request as described above. Then it checks whether response status is 307 - Temporary Redirect (other statuses like 302 or 301 may also be included) and if it is, gets the URL to which web service is redirecting and sets new endpoint for the specified port.

In my case this checkRedirect method is called once for each web service port interface and then everything seems to work fine:

  1. Redirect is checked on url like http://example.com:50678/restOfUrl
  2. Web service redirects to url like https://example.com:43578/restOfUrl (please note that web service client authentication is present) - endpoint of a port is set to that url
  3. Next web service requests executed via that port are successful

Disclaimer: I'm quite new to webservices and this is what I managed to achieve due to the lack of solutions for this questions, so please correct me if something is wrong here.

Hope this helps