Creating a Transport
All HydraExpress transports derive from the class
rwsf::TransportImp. This is an abstract base class with five pure virtual functions:
virtual bool isConnected() const = 0;
virtual void doInvoke(rwsf::CallInfo& callInfo) = 0;
virtual void doConnect() = 0;
virtual void doDisconnect() = 0;
virtual TransportImp* clone() const = 0;
To create your own transport, you must derive from
rwsf::TransportImp and implement at least the five pure virtual functions. Below is a combination of pseudo-code and example code taken from the implementation of
rwsf::HttpTransport. This sample code is intended to give you some idea of how these methods need to be implemented. Keep in mind that this code is merely for guidance. It shows only the very basic functionality that needs to be part of almost any transport. Your code will certainly have additional functionality, and the details of your code for the functionality shown below may differ.
bool
MyTransport::isConnected() const
{
// Return true if connected, false otherwise.
}
void
MyTransport::doInvoke(rwsf::CallInfo& info)
{
// Verify that there is a connection and connect if not.
if(!isConnected()) {
doConnect();
}
MyTransportMessage request; // Object representing your
// message format
// Determine what request is being made -- that is, what
// service operation method is being called -- and add the
// data to your message.
std::string operation = callInfo.getStringProperty("action");
request.setOperationName(operation);
// Do whatever is required by your message format, such as
// adding message headers.
request.setHeader("headerName","headerValue");
...
// Add any transport headers that have been passed in through
// the rwsf::CallInfo object.
rwsf::Enumeration<std::string> transportHeaders =
callInfo.getRequestTransportHeaderNames();
while(transportHeaders.hasMoreElements()) {
std::string name = transportHeaders.nextElement();
std::string value =
callInfo.getRequestTransportHeaderValue(name);
// Add this header to your message.
request.addHeader(name,value);
}
// Send the request to the server, however that is done
// for this transport.
// Write code to handle the response when there is one.
if(callInfo.hasResponse()) {
MyTransportMessage response = // read the response from
// the server
// Maybe capture and preserve the status message:
std::string statusMessage = response.getStatusMessage();
callInfo.setStringProperty("rwsf:StatusMessage",statusMessage);
// Place the response into a CallInfo object.
callInfo.setResponse(response);
// Other processing, such as capturing transport headers
// that arrived with the response and placing them into
// the CallInfo object.
}
}
void
MyTransport::doConnect()
{
// Do whatever is necessary to connect to your server.
}
void
MyTransport::doDisconnect()
{
// Do whatever is necessary to disconnect from your server.
}
TransportImp*
MyTransport::clone() const
{
MyTransport* cloned = new MyTransport();
// Duplicate all internal data from the current transport
// in the clone to be returned by this method.
cloned->initParams_ = initParams_;
cloned->scheme_ = scheme_;
...
return cloned;
}
The source code for your transport needs to provide an entry point, which it does by defining the following macro:
RWSF_DEFINE_MESSAGE_HANDLER (MyTransport)
This macro expands to a function with
"C" linkage that returns a pointer to a new instance of the named transport class. At runtime, the client uses the
client-transports.xml file (
“Transport and Listener Configuration Files” ) to determine the name of the DLL or shared library that contains the transport, and the name of the function to invoke to create the transport.
The completed transport code is compiled into a DLL, which will be deployed where the service expects it, at <installdir>\apps-bin (Windows) or <installdir>\apps-lib (UNIX/Linux). (Note that the service descriptor and other configuration files are deployed to <installdir>\apps\servlets\<servicecontextname>). The final step is to define the transport in the transport configuration files.
<rwsf:transport name="MyTransport" uri="..." scheme="mytransport"
class="MyTransportDll.createMyTransport">
<!-- rwsf:property elements -->
</rwsf:transport>
Note that the use of a custom transport requires either a custom connector or listener that knows how to process incoming messages from that transport. Use a connector for the common request-response message pattern as well as for one-way messages, i.e. those processed by the Agent, and use a listener for standalone servers and for the notification or solicit-response message patterns. (This chapter does not discuss connectors; rather, see Chapter 5, “Connectors,” in the HydraExpress User Guide.)
Listeners are derived from
rwsf::MessageListenerImp. Naturally, the functionality of the listener closely mirrors the functionality in the transport. Like the transport, the entry point for the listener is defined through the macro
RWSF_DEFINE_MESSAGE_HANDLER (MyListener), and the listener is defined through entries in the transport configuration files:
<rwsf:listener name="MyTransport" uri="..." scheme="mytransport"
default="true"
class="MyTransportDLL.createMyListener">
<!-- rwsf:property elements -->
</rwsf:listener>
A Note on Multithread Safety
To support multithread safety, the
rwsf::TransportImp base class
invoke() method
creates a multithread guard and then calls
doInvoke() in derived classes. This design provides thread safety during asynchronous processing and allows your application to share transports safely. If you know you will not be using asynchronous processing with your transport and you want to avoid using the mutex guard, you can reimplement
invoke() in your derived class to not use the mutex guard.