Saturday, March 12, 2011

Handling slow SOAP responses in the client

Real world is different from the developer machine and testing tools must be used if you want to avoid code failures in real life situations.

Regardless the programming language you use a TCP proxy with delay capability is the tool you need to look for. Axis (not Axis2) comes with such a tool named TcpMon. The tool is maintained our of the Axis project nowadays. You can download it from here.

As the tool is developed with Java you can run it in any OS. Below is a picture of it configured to wait 30 seconds after each chunk of 10KB transmitted, listening in localhost port 4640 and intercepting request and responses going to and from 192.168.0.13 internal IP.


Below is a sample screenshot showing the request and the response of a SOAP call done to Advent Geneva SOAP Server:

The Problem

This post came as a result of some hours debugging slow responses that my SOAP was experimenting.

I am using Axis2 ServiceClient for that project and I thought slow responses could be handled by the timeout configurations:
//options.setProperty(HTTPConstants.SO_TIMEOUT, new Integer(timeOutInMilliSeconds));
//options.setProperty(HTTPConstants.CONNECTION_TIMEOUT, new Integer(timeOutInMilliSeconds));

//or

options.setTimeOutInMilliSeconds(timeOutInMilliSeconds);

The above does not address the problem of slow packets or servers just performing to many operations before sending back the whole SOAP response. Ideally there would be a way to tell the client just to timeout if the whole operation takes too much time.

I thought this could be achieved accessing HttpURLConnection.setReadTimeout() and that made me post the question in the Axis2 Forum.

After seeing some people visiting the post but actually not replying I felt like I had to hack deeper in the code and so I did.

Axis2 uses Commons Http Client library and that one just has support for socket and connection timeouts. This is probably right approach if we talk about TCP but I think when it comes to HTTP, handling timeouts when data takes too long over the wire is a must have.

This as a common problem to be solved: You do not want your client side to hang waiting for slow server responses. Ideally the client should timeout in circumstances like slow network throughput, high latency and so on.

A Solution

I ended up using concurrency to solve this issue. A Callable task does the job and if the task does not finish within certain amount of time the Callable task is killed. This is easily done through the use of an ExecutorService.

Here is the client code before:
OMElement resultOfOperation = setupServiceClient().sendReceive(operation.get(null));

Here is the client code after:
SendReceiveTask sendReceiveTask = getSendReceiveTask( operation, null );
OMElement resultOfOperation = sendReceiveTask.resultOfOperation;

Below are the snippets you need for the code above to run. They are an extract of real code and so there are special components related to the specifics of the particular SOAP Service like for example the session but you should get the idea on how to achive the same in your code:
    class SendReceiveTask implements Callable {
        
        SoapOperation soapOperation;
        OMElement resultOfOperation;
        Exception exception;
        Session session;
        
        SendReceiveTask(SoapOperation soapOperation, Session session) {
            this.soapOperation = soapOperation;
            this.session = session;
        }
        
        public String call() {
            try {
                resultOfOperation = setupServiceClient().sendReceive(soapOperation.get(session));
            } catch (Exception e) {
                exception = e;
            } 
            
            return null;
        }
    }
    
    private SendReceiveTask getSendReceiveTask( SoapOperation operation, Session session ) throws InterruptedException {
        SendReceiveTask sendReceiveTask = new SendReceiveTask(operation, session);
        ExecutorService executor = Executors.newSingleThreadExecutor();
        int timeOutInMilliSeconds = genevaSettings.getTimeoutMilliseconds();
        executor.invokeAll(Arrays.asList(sendReceiveTask), timeOutInMilliSeconds, TimeUnit.MILLISECONDS);
        boolean taskFinished = sendReceiveTask.exception != null || sendReceiveTask.resultOfOperation != null;
        if( !taskFinished ) {
            throw new RuntimeException("Response Timeout. It took more than " + timeOutInMilliSeconds + " milliseconds.");
        }
        executor.shutdown();

        if(sendReceiveTask.exception != null) {
            throw new RuntimeException(sendReceiveTask.exception);
        }
        return sendReceiveTask;
    }
    

No comments:

Followers