Custom Operation Timeouts for Apache CXF-Based SOAP Clients

This article describes how to configure custom operation timeouts for Apache CXF based SOAP clients using an Interceptor.

Frameworks and Versions

Apache CXF is a convenient services framework, e.g. for integrating SOAP-based webservices in a Java application. It offers SpringFramework integration, which together take away much complexity of using such services.

We’ve used the following framework versions. Future version provide alternative ways to configure custom timeouts, e.g. using HttpClientPolicy on a RequestContext, which is supported in Apache CXF 2.7.x. In my opinion an interceptor-based solution is less error-prone, as it avoids side effects that may occur when changing a RequestContext (which is per-instance or per-thread, instead of per-invocation).

FrameworkVersionDescription
Spring Framework2.5.6.SEC01DI framework
Apache CXF2.5.5Services framework

We have not used the most recent versions because it was part of a larger existing system. Using the latest Apache CXF version requires at least SpringFramework 3.0, and such an update was decided to have too much impact.

Why do we want custom operation timeouts?

For simple webservices a single global timeout configuration may be sufficient, but if a single webservice contains relatively fast calls (with low timeouts, e.g. used in UI calls) and relatively slow calls (with high timeouts, e.g. used in batch-processing), we need to use timeouts per operation. Using a single low timeout would prevent the slower calls from completing, using a single high timeout may cause clients waiting too long on faster calls in case of problems.

Global endpoint timeout configuration

Apache CFX allows to configure operation timeouts per endpoint. It is described here, which in a Spring context would look like this:

<http-conf:conduit name="http://localhost:8080/.*">
	<http-conf:client ReceiveTimeout="60000" />
</http-conf:conduit>

This configuration defines that all operations time out after 60 seconds.

Custom timeout configuration per operation

For custom timeout configuration per operation we will use an interceptor that sets a MessageProperty. It must be configured in the right Phase, otherwise the MessageProperty might not have effect. The interceptor, in its simplest form looks like this:

package com.trimplement.cxf;

import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.cxf.interceptor.MessageSenderInterceptor;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;

/**
* An {@link AbstractPhaseInterceptor} that applies a call-specific timeout based on the QName of the operation.
*/
public class CustomTimeoutInterceptor extends AbstractPhaseInterceptor<Message> {

	/** Keep all timeouts per OperationName in milliseconds */
	private Map<QName, Long> receiveTimeoutByOperationName = new HashMap<QName, Long>();

	public CustomTimeoutInterceptor(String phase) {
		super(phase);
		addBefore(MessageSenderInterceptor.class.getName());
	}

	public CustomTimeoutInterceptor() {
		this(Phase.PREPARE_SEND);
	}

	@Override
	public void handleMessage(Message message) {
		final QName operationName = message.getExchange().getBindingOperationInfo().getName();
		final Long receiveTimeout = receiveTimeoutByOperationName.get(operationName);
		if (receiveTimeout != null) {
			message.put(Message.RECEIVE_TIMEOUT, receiveTimeout);
		}
	}

	public void setReceiveTimeoutByOperationName(Map<QName, Long> receiveTimeoutByOperationName) {
		this.receiveTimeoutByOperationName = receiveTimeoutByOperationName;
	}
}

This implementation takes a Map of operation QNames and timeout-values. If it finds a match, it configures the timeout, otherwise it does nothing and the default timeout is used.

A typical Spring configuration using this interceptor could look like this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
		xmlns:util="http://www.springframework.org/schema/util"
		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:jaxws="http://cxf.apache.org/jaxws"
		xmlns:http="http://cxf.apache.org/transports/http/configuration"
		xmlns:cxf="http://cxf.apache.org/core"
		xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
			http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
			http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
			http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd
			http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">
	
	<bean id="someService" class="com.trimplement.cxf.SomeServiceWrapper">
		<property name="delegate">
			<jaxws:client serviceClass="com.trimplement.schema.SomePortType" address="http://localhost:8080/someEndPoint" >
				<jaxws:outInterceptors>
					<bean class="com.trimplement.cxf.CustomTimeoutInterceptor">
						<property name="receiveTimeoutByOperationName">
							<map key-type="javax.xml.namespace.QName" value-type="java.lang.Long">
								<entry value="120000">
									<key>
										<bean class="org.springframework.xml.namespace.QNameUtils" factory-method="parseQNameString">
											<constructor-arg value="{http://trimplement.com/someService}someMethod" />
										</bean>
									</key>
								</entry>
							</map>
						</property>
					</bean>
				</jaxws:outInterceptors>
			</jaxws:client>
		</property>
	</bean>
	
</beans>

This configuration defines that the operation with QName {http://trimplement.com/someService}someMethodtimes out after 120 seconds, and all other operations will use the default time out (e.g. 60 seconds).

Conclusion

The interceptor-based solution provides a very flexible way of configuring timeouts for Apache CXF based SOAP clients, as it can use any logic to determine the timeout for an operation. Since the timeout is configured on the Message, it is guaranteed not to have any side-effects on other calls, so there is no need to ‘reset’ the timeout configuration after the invocation.

Thijs Reus

Thijs Reus is one of the co-founders and managing directors of trimplement. He is a full-stack software engineer, with a Master’s degree in Computer Science. Thijs looks back on a successful professional career in consulting and financial software before he launched trimplement in 2010. With precision, Thijs meets the most intricate challenges in transaction processing, just to run a marathon the next day – for relaxation.

Leave a Reply

Your email address will not be published. Required fields are marked *