问题
I'm working with a bit of a legacy component in which we interact with a SOAP web service (a technology which I absolutely, positively abhor) using some client code built using the JAXRPC-RI (reference implementation) library.
I'm interested in being able to set a timeout with the stubs so that in case the web services server does not reply within X seconds, the application isn't setting there forever waiting for a response.
I'm using to working with clients/stubs generated by Apache Axis, in which you can simply use org.apache.axis.client.Stub.setTimeout()
to set a timeout.
For the life of me I can't figure out how to set a timeout when using Stubs created with JAXRPC-RI:
- The port class I am instantiating extends
com.sun.xml.rpc.client.StubBase
and implementsjavax.xml.rpc.Stub
andcom.sun.xml.rpc.spi.runtime.StubBase
. - The JavaDocs for none of these classes mention any sort of timeout or method to do this.
- Trying code like
stub._setProperty("axis.connection.timeout", 1000);
results in an exception at runtime:javax.xml.rpc.JAXRPCException: Stub does not recognize property: axis.connection.timeout
Does anyone have any ideas on how to set/enforce a timeout when using a JAXRPC-RI client? Is it even possible?
回答1:
You may have luck setting properties such as sun.net.client.defaultConnectTimeout
or sun.net.client.defaultReadTimeout
, though that would then introduce a system-wide timeout.
In code the properties values are set using Strings:
System.setProperty("sun.net.client.defaultConnectTimeout", "1000");
System.setProperty("sun.net.client.defaultReadTimeout", "1000");
For a quick test, it might be easier to set the environment variable JAVA_OPTS
or use the command line:
java -Dsun.net.client.defaultConnectTimeout="1000" ...
回答2:
I am not sure why this particular JAXRPC implementation goes out of its way to make it difficult to set the timeout. Perhaps, there's a good reason for it. Other implementations such as Axis and, I believe JAX-WS, let you simply call the setTimeout() method on the Stub. After some pains, I was able to come up with this solution. Hopefully, it will be helpful. You will need to do the following in order to set the timeout on the underlying URLConnection:
- Create a new ClientTransport class. It should extend the com.sun.xml.rpc.client.http.HttpClientTransport class. Override the createHttpConnection(String, SOAPMessageContext) method. Call super.createHttpConnection() which will return HttpURLConnection object. On the HttpURLConnection object you can call setReadTimeout() which will force client-side timeout in X number of milliseconds. Once that's done, return the modified HttpURLConnection object.
- Extend the client-side Stub class with your own. Override the _getTransport() method to return a new instance of the ClientTransport class that you created in step (1).
- Extend the client-side _Impl class with your own. Override the get...Port() method so that it uses the Stub you created in step (2) instead of the generated one.
- Modify the client code you wrote to call the Web Service so that it uses the Stub you created in step (2) and the _Impl that you created in step (3).
Note: Make sure that whatever it is that you're calling to generate the Stubs via wscompile (Ant script?) does not overwrite the 3 Java classes you just created/modified. It probably makes sense to move them to a different package so that they don't end up being overwritten.
回答3:
Yevgeniy Treyvus answer is very good, but results in a new HTTPUrlConnection being created for every SOAP call. I found out while testing his approach, that one can set a ClientTransportFactory. I now use a CustomClientTransportFactory and set it on the Stub ( casting it to StubBase ). Then one doesn't need step 2 and 3.
In step 4 one then sets his new ClientTransportFactory like so: ((StubBase) myPort)._setTransportFactory(new CustomClientTransportFactory());
回答4:
I know that you are using Axis but I struggled to find the same answer for Weblogic and since your question title and tags are generic, here is my solution.
In my client class that implements the generated MyObject interface, I have adapted the getServicePort() as follows (note that I also have security here).
protected MyObject getServicePort() throws java.rmi.RemoteException {
if (port == null) {
synchronized(this) {
if (port == null) {
try {
final MyObject_Impl service = new MyObject_Impl(wsdlURL);
// using a local variable until the object is completelly initialized
final MyObject localPort = service.getMyObjectPort();
// if username and password are provided: building a client which will include the
// username and token
if (username != null && pwd != null) {
Stub localStub = ((Stub) localPort);
// We have UsernameToken Authentication too
localStub._setProperty(
WSSecurityContext.CREDENTIAL_PROVIDER_LIST,
Collections.singletonList(
new ClientUNTCredentialProvider(username.getBytes(), pwd.getBytes())));
if (timeout != null) {
log.debug("Setting timeout to " + timeout + " milliseconds");
localStub._setProperty("weblogic.wsee.transport.read.timeout", timeout);
localStub._setProperty("weblogic.wsee.transport.connection.timeout", timeout);
}
}
port = localPort;
} catch (ServiceException e) {
throw new RemoteException("Could not initialize client to MyObject service", e);
}
}
}
}
return port;
}
The Oracle documentation is here: http://docs.oracle.com/cd/E12840_01/wls/docs103/webserv_rpc/client.html#wp241849 but it incorrectly states that the timeout is in seconds. It actually is in milliseconds !
回答5:
I'm just going to try to reap the bounty, so don't shoot me if I am completely on the wrong track :) If your application is "setting there forever waiting for a response" than you could do the request in a separate thread and after spawning your requestThread
you could just say requestThread.join(max_time_to_wait);
the next call would check if the requestThread is still alive and if it this then try to kill it.
You application will move on after a time-out and it's not the most inelegant solution...
回答6:
Ray,
Thanks for your input. Sounds like your approach is superior since it will avoid a couple of extraneous steps. I will have to take a look. However, unless I am missing something I do not believe an additional HttpConnection is being created as the YourClientTransport.createHttpConnection(String s, SOAPMessageContext ctx) should override the HttpClientTransport one:
public class YourClientTransport extends HttpClientTransport {
@Override
protected HttpUrlConnection createHttpConnection(String s, SOAPMessageContext ctx) throws IOException {
HttpURLConnection httpURLConnection = super.createHttpConnection(s, ctx);
httpURLConnection.setReadTimeout(1000);
httpURLConnection.setConnectTimeout(1000);
return httpURLConnection;
}
}
回答7:
Maybe a little bit late for this question but I came to this problem today. Based on a solution proposed by Yevgeniy Treyvus (thank for it!) I came up with the following:
((com.sun.xml.rpc.client.StubBase)myRemoteStub)._setTransportFactory(new ClientTransportFactory() {
@Override
public ClientTransport create() {
return new HttpClientTransport() {
@Override
protected HttpURLConnection createHttpConnection(String endpoint, SOAPMessageContext context) throws IOException {
HttpURLConnection conn = super.createHttpConnection(endpoint, context);
conn.setConnectTimeout(2000);
conn.setReadTimeout(2000);
return conn;
}
};
}
});
It is basically the same as Yevgeniy proposed but with a little less overriding (with the help of a connection factory).
回答8:
You should use:
import javax.xml.rpc.Stub;
...
int timeout = <number of milliseconds>;
((Stub) port)._setProperty(
"axis.connection.timeout",
timeout);
来源:https://stackoverflow.com/questions/808487/how-to-set-a-connection-timeout-when-using-jaxrpc-ri-web-services-client