Web Service Development Guide : PART IV Extending your Applications : Chapter 11 Custom Transports and Listeners : Creating a Listener
Creating a Listener
All custom listeners derive from the class rwsf::MessageListenerImp.
To create a custom Listener:
1. As with transports, extend this class and implement at least these five pure virtual functions:
 
virtual void doInit(const rwsf::Config& initParams) = 0;
virtual void initServer() = 0;
virtual void doStart() = 0;
virtual void doStop() = 0;
virtual void doReset() = 0;
2. Implement a method for handling requests, and some kind of threading framework for the listener to run in.
3. Define the macro RWSF_DEFINE_MESSAGE_HANDLER(NAME) in the implementation file.
4. Configure the listener in the configuration file client-transports.xml.
“Request Handling and Threading” discusses code for handling incoming messages and creating the threading mechanism. For specifics on implementing the five virtual functions, see “Implementing the Virtual Methods” .
Request Handling and Threading
This code creates a custom listener class:
 
#include <rwsf/core/Config.h> //1
#include <rwsf/webservice/listener/MessageListenerImp.h>
 
 
class MyListener : public rwsf::MessageListenerImp {
 
public:
MyListener();
 
protected:
virtual void doInit(const rwsf::Config& initParams);
virtual void initServer();
virtual void doStart();
virtual void doStop();
virtual void doReset();
 
private:
int port_;
client_; //2
example::SocketThread mainThread_;
};
//1 The rwsf::Config class holds initialization parameters passed to the listener from the listener configuration file, client-transports.xml. For more information on the data and methods available through this class, see its class description in the HydraExpress C++ API Reference Guide.
//2 Note that client_ and SocketThread are not HydraExpress classes but are just used for illustration purposes in this example.
NOTE >> For threading and network capabilities in HydraExpress services, you can use the network and threading classes of Rogue Wave’s SourcePro.
The following lines are pseudo code for creating a threading framework. This framework is needed so the doStart() method can be implemented as non-blocking.
 
namespace example {
class SocketThread : public Thread {
public:
SocketThread(Socket socket, MyListener listener); //1
void run(); //2
 
private:
Socket socket_;
MyListener listener_;
};
}
 
void SocketThread::run()
{
while( not end ) { //3
client = socket.accept(); //4
RequestHandler(client, listener).start(); //5
}
}
//1 Create a socket thread based on the supplied socket.
//2 Define the main listener thread.
//3 The run() method must test some condition that lets it know when to return.
//4 The socket accepts the message off the line and stores it in some kind of object.
//5 The message off the socket is executed in a RequestHandler thread. The start() method starts the thread and then calls the run() method of RequestHandler. See below for a pseudo code implementation of this class.
A listener needs to do the following work:
Collect incoming requests off the wire and place data into an rwsf::CallInfo instance. See the description of this class for information on the data structures and methods it provides.
Invoke the service implementation for handling the request.
This sample code declares the RequestHandler class:
 
class RequestHandler : public Thread {
public:
RequestHandler(client, MyListener listener);
void run();
 
private:
client_;
MyListener listener_;
};
 
void RequestHandler::run()
{
listener_.handleRequest(client_);
}
The RequestHandler’s run() method invokes the listener’s handleRequest() method, which actually processes the request:
 
void MyListener::handleRequest(client)
{
std::string payload = client.getPayload(); //1
rwsf::CallInfo callInfo;
callInfo.setRequest(payload); //2
 
invoke(callInfo); //3
 
std::string response = callInfo.getResponse(); //4
client.write(response); //5
}
//1 Reads the data taken off the line by the socket into a string.
//2 Sets the request payload on an rwsf::CallInfo object.
//3 Invokes the service implementation.
//4 Gets the response from the rwsf::CallInfo object.
//5 Writes the response message to the socket.
When the invoke has completed, the rwsf::CallInfo should contain any information needed to send back a response. This information might include a response payload, headers, a status code, and so on. You need to implement code for writing that information to a location where the client can retrieve it.
Finally, use the following convenience macro that generates code to create a listener instance.
 
RWSF_DEFINE_RWSF_LISTENER(MyListener)
Implementing the Virtual Methods
The following code shows the init() method for a listener that watches a directory. The init() method is called during Agent initialization, and the name of the directory is passed to the listener in an rwsf::Config instance.
These are the implemented virtual functions:
The doInit() method initializes the listener based on initialization values it gets from the rwsf::Config instance.
 
void MyListener::doInit(const rwsf::Config& initParams)
{
port_ = initParams.getInitParamater("port");
// get more initialization parameters...
...
}
The initServer() method needs to perform any setup needed by the listener before it is started. In the example below, note that the Socket class is for illustration only; it is not a HydraExpress class.
 
void MyListener::initServer()
{
// Instantiate and bind the socket...
Socket socket = new Socket();
socket.bind(port_);
mainThread_ = new SocketThread(socket, this);
}
The method doStart() kicks off the thread to start the listener. It should be implemented as a non-blocking method, and should prepare the system that waits for events and accepts requests for the rest of the system. In most cases, the start() method spawns at least one thread that listens for requests.
 
void MyListener::doStart()
{
mainThread_.start();
}
 
The method doStop() shuts down the thread, stopping the listener. The stop() method first releases any resources the listener owns and then stops the listener.
 
void MyListener::doStop()
{
mainThread_.shutdown();
mainThread_.join();
}
Use doReset() to reset the listener to the state it was in before it started.
 
void MyListener::doReset()
{
if( mainThread_.isRunning() )
doStop();
 
delete mainThread_;
}
Configuring the Listener
Compile your new listener into a shared library (DLL) and place it in the bin (Windows) or lib (UNIX/Linux) directory of your HydraExpress installation. You are now set to load the listener into the Agent.
The client must have access to this listener’s configuration in the file client-transports.xml, as discussed in “Transport and Listener Configuration Files” and “Transport and Listener Properties” . On the server side, the listener is configured in the transports.xml file deployed with the service for use by HydraExpress services.
For an example of how to use a listener from client code, see “Using an Autoconfigured Listener.” While that section discusses using a provided default HTTP listener, any custom listener could also be loaded from client code in this same way.