Andrew Evers wrote:
I've been looking into this myself. I've finished integratingI absolutely agree with your goal to separate HTTP from XML-RPC. I have a couple concerns.
and testing the pre 1.2 release of Apache XML-RPC with parts of
our own software (we use a home-grown HTTP implementation for
various reasons). It works nicely with our C client. I now need
to write a Java client to talk to our Java server and a C server.
But, I need to be able to do XML-RPC as well as other HTTP PUT
and GET request on the same connection to the server (ie. the
same socket).
So, I would like to rearrange the client classes so that XML-RPC
encoding and decoding was separated from the HTTP send and
receive. This should simplify client implementation for the
three clients: Lite, Current, and Jakarta Commons.
As I see it, there are the steps involved in making an XML-RPC
request, in the case of a persistent HTTP connection:
1. Create and configure the default options for a HTTP connection.
2. Make the socket connection to the server.
3. Prepare an XML-RPC request (an actual ASCII byte stream)
4. Set any per-request HTTP options. Send the HTTP request
(POST), headers and terminating newline.
5. Send the XML-RPC request byte stream.
6. Receive the HTTP response headers.
7. Receive the XML-RPC response.
8. Parse the XML-RPC response.
9. If the XML-RPC succeded: return the received object,
otherwise: throw the received exception.
Further requests on a persistent HTTP connection start at 3.
I would like to extract code to perform step 3 into:
XmlRpcClientRequestProcessor
void encodeRequest(XmlRpcRequest req, String encoding, OutputStream out)
throws XmlRpcClientException
byte [] encodeRequestBytes(XmlRpcRequest req, String encoding)
throws XmlRpcClientException
And step 7 and 8 into:
XmlRpcClientResponseProcessor (extends XmlRpc)
// Step 7: Decode a response, return an Object or an Exception
Object decodeResponse(InputStream in, int contentLength)
throws XmlRpcClientException
// Step 7 & 8: Decode a response. If the response is an exception,
// throw it as an XmlRpcException, otherwise return it.
Object handleResponse(InputStream in, int contentLength)
throws XmlRpcException, XmlRpcClientException
also, {decode|handle}RequestBytes(byte []) that take a byte[]
instead of an InputStream.
Then, based on a similar refactoring to the one performed on the
server, have an abstract XmlRpcClient that manages a thread pool.
The main entry points are the execute and executeAsync methods:
Object execute(XmlRpcRequest request, XmlRpcClientContext context)
throws XmlRpcClientContext, XmlRpcException
this method gets a worker (or creates one), then calls the worker's
execute method. It then returns the result in the current thread.
void executeAsync(XmlRpcRequest request, XmlRpcClientContext context) throws XmlRpcClientContext, XmlRpcException
this returns immediately after enqueuing the request. The response
will be returned in a separate thread via the XmlRpcClientContext
(see later).
The XmlRpcClient also has an abstract factory method:
XmlRpcClientWorker createWorker().
that returns an appropriately configured XmlRpcClientWorker.
XmlRpcClientWorker has as its major method:
Object execute(XmlRpcRequest request, XmlRpcClientContext context)
throws XmlRpcClientException, XmlRpcException
This method is responsible for doing steps 3-8 (with the help of
the XmlRpcClient*Processor classes). The context object can be
used by the worker in a multitude of ways to handle connection
persistence, multiplexing, HTTP headers, encryption, drug
dealing and the like.
The XmlRpcClientContext object has at least the following methods:
? getHTTPHeaders()
returns the HTTP headers for this request.
This would probably return String [], maybe Vector?
String getUsername()
String getPassword()
- possibly, probably just part of getHTTPHeaders
AsyncCallback getCallback()
returns the callback to respond to - null means discard the
response. This is only called after the response has been
received, but may be called before or after it has been
decoded.
Workers are free to cast the XmlRpcContext to some other class
or interface that may define extra methods.
The XmlRpcClientException is an exception to indicate that something
went wrong in the processing of the XML-RPC as part of client
processing. eg. an invalid encoding was specified, the specified
host could not be contacted, etc. It has a Throwable getCause()
method in the Java 1.4 style, with appropriate constructors.
I am naturally willing to code this all up, test it and put it
as a patch in bugzilla for general consumption. I'm 90% sure it
can be done without too much breakage from the 1.1 API. I have
time available to work on this now and in the near future. Let me
know your opinions ;)
Andrew.
One reason I wanted to integrate the Commons HttpClient was for the functionality it provided with cookies and HTTP 1.1 compatibility. These are features that will not be present in the Lite version for sure, and possibly not your HTTP library. A user of this library who wants to use HTTP 1.1 would need a way to specify this.
Furthermore, the Commons HTTPClient provides several features that simplify dealing with things like Basic Authentication. We should pass these conveniences on to the library user instead of making them handle raw HTTP headers in all cases.
The possibility of a persistent HTTP connection is very cool. This would be possible to do with the Commons HTTPClient as well. I might change your process a little to generalize for non-persistent HTTP connections as well:
1. Initialize HTTP Connection if necessary (including opening a persistent connection)
2. Prepare XML-RPC request
3. Set request level HTTP options. (Do we need this? What options are we setting here that shouldn't be set on the persistent connection?)
4. Send the XML-RPC request on the HTTP connection
5. Receive the XML-RPC response from the HTTP connection
6. Parse the XML-RPC response
7. Return the received object or throw an exception.
As you can see this process can run 1-7 for every request whether it has a persistent connection or a new one each time.
Instead of an XmlRpcClientContext class, I would instead suggest writing an XmlRpcTransport interface that is then implemented by each of our HTTPTransport classes:
public interface XmlRpcTransport() {
public InputStream executeRequest(URL url, byte[] requestBody);
}
XmlRpcClient would then get a new method:
public void setTransport(XmlRpcTransport transport)
and we could add additional features to our specific implementations of XmlRpcTransport.
This would give the benefit of removing setBasicAuthentication() from XmlRpcClient and completely eliminating XmlRpcClientLite in favor of an HttpTransportLite class that implements XmlRpcTransport. (Please correct me here if necessary John).
This framework encourages the separation of code related to HTTP specific requirements from code related to XML-RPC processing. The client programmer can create a PersistentHTTPTransport and hold onto it as long as they like, or alternately a JakartaCommonsHTTPTransport and use HTTP 1.1 or NTLM authentication transparently.
If you like I will be happy to work with you on this as it will help me wrap up my Commons HttpClient integration which has been going on for months :)
--
Ryan Hoegg
ISIS Networks