Saturday, August 29, 2009

Apache XML RPC

XML RPC
In one of my projects I used Apache commons xml rpc library. I faced some issues that I would like to discuss and provide solutions that can be helpful to others.

1- Date format which is ISO 8601 as mandated by Xml RPC Specification. But since ISO 8601 does not specify one concrete format so there are confusions in implementations by clients/servers reference http://www.cookcomputing.com/blog/archives/000009.html.

In my case xml rpc server was using yyyyMMdd'T'HH:mm:ssZ, so needed to create a CustomFormat class like:

class CustomTypeFactory extends TypeFactoryImpl {
public CustomTypeFactory(XmlRpcController pController) {
super(pController);
}

private static final String ISO_FORMAT = "yyyyMMdd'T'HH:mm:ssZ";

public TypeSerializer getSerializer(XmlRpcStreamConfig pConfig,
Object pObject) throws SAXException {
if (pObject instanceof Date) {
return new DateSerializer(new SimpleDateFormat(ISO_FORMAT));
} else {
return super.getSerializer(pConfig, pObject);
}
}
}

And on org.apache.xmlrpc.client.XmlRpcClient:
client.setTypeFactory(new CustomTypeFactory(client));

2- Various transport factories (LiteHttp, SunTransport,CommonsTransport) provided with apache xml rpc library have trade-offs. Lets read my misery story step by step:
2.1. I first used SunTransport which is the default but every other day or so I found application hanged while communicating over with xml rpc server (Ctrl+Break was not much helpful).
2.2. I then moved to LiteHttp but the same behavior continued.
2.3. I then tried CommonsTransport which used apache commons http client behind the scene. But un-understandably commons client tried reaching internet to download the xml rpc schema which was mentioned in rpc server’s response. Since server’s response was not under my control and commons client did not provide any way to disable schema validation. I had to quit.
2.4- I then went on to studying XmlRpcClient code and JDK’s default http implementation. Notably I found that in JDK 1.5 reply time out can be set on URLConnection and this feature was not present in JDK 1.4. This feature was added to avoid application hanging, while reading from sockets blocked in deadlock, which is very common in distributed environment with firewalls and network glitches etc. You would observe the same in case of JDBC API which provides similar bounded blocking I/O. XmlRpcClient was not utilizing this feature instead it had same implementation of sendRequest() in base class XmlRpcSunHttpTransport for both JDK 1.5 and 1.4. So, here you go

Override sendRequest() in org.apache.xmlrpc.client. XmlRpcSun15HttpTransportFactory and enable bounded blocking read.

public Object sendRequest(XmlRpcRequest pRequest) throws XmlRpcException {
httpConfig = (XmlRpcHttpClientConfig)pRequest.getConfig();
try {
final URLConnection c =
conn = newURLConnection(httpConfig.getServerURL());
c.setUseCaches(false);
c.setDoInput(true);
c.setDoOutput(true);
//Must be used with JDK 1.5
c.setReadTimeout(httpConfig.getReplyTimeout());
c.setConnectTimeout(httpConfig.getConnectionTimeout());
} catch (IOException e) {
throw new XmlRpcException("Failed to create URLConnection: " +
e.getMessage(), e);
}
return super.sendRequest(pRequest);
}

For new ones here is the code to use apache xml rpc as a client.

private XmlRpcClient client = new XmlRpcClient();
private XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
config.setConnectionTimeout(CONNECTION_TIMEOUT);
config.setReplyTimeout(REPLY_TIMEOUT);
config.setBasicEncoding(null); //UTF-8
config.setBasicUserName(getUser());
config.setBasicPassword(getPassword());
config.setAuthScopeRealm(getRealm());
config.setUserAgent(getUserAgent());
client.setConfig(config);
client.setTypeFactory(new CustomTypeFactory(client));

No comments: