Creating a Custom Connector
To create a custom connector, you need to:
2. Implement a method for handling requests, and some kind of threading framework for the connector to run in.
3. Override the pure virtual methods init(), start(), and stop().
4. Configure the connector.
The following sections present code showing how to create a socket connector. Most of the code is pseudo code that will not compile without some additional elements.
Request Handling and Threading
This is the code to create a custom connector class:
#include <iostream>
#include <stdlib.h>
#include <rwsf/core/Config.h> //1
#include <rwsf/handlers/AgentContext.h> //2
#include <rwsf/handlers/ConnectorImp.h>
MyConnector : public rwsf::ConnectorImp
{
public:
virtual void init(const rwsf::Config& config,
const rwsf::AgentContext& context);
virtual void start();
virtual void stop();
private:
example::Socket serverSocket;
};
If you are most interested in learning more about the virtual method implementations for
init(),
start() and
stop(), skip to
“Implementing the Virtual Methods.”. The remainder of this section discusses the message handling aspects of the connector.
The following lines are pseudo code for creating a threading framework. This framework is needed so the start() method can be implemented as non-blocking.
namespace example
{
class SocketThread : public Thread
{
public:
SocketThread(example::Socket socket); //1
void run( void ); //2
private:
example::Socket socket;
};
}
void SocketThread::run( void );
{
while( not end ) //3
{
clientRequest = socket.accept(); //4
RequestHandler(clientRequest).start(); //5
}
}
A connector needs to do the following work:
• Collect incoming requests off the wire and place data into an
rwsf::MessageInfo instance. See the description of this class for information on the data structures and methods it provides. At a minimum, the connector should save any transport-level data in another
rwsf::MessageInfo instance, and the message payload in an
std::string instance.
• Pass the
rwsf::MessageInfo instance to a handler chain. Usually this will be the chain configured in
rwagent.xml. However, the connector can retrieve and invoke an alternative chain, which must include the
servlet handler that knows how to dispatch the service request to the Agent servlet container.
This sample code declares the RequestHandler class:
class RequestHandler : public Thread
{
public:
RequestHandler(clientRequest);
void run();
private:
clientRequest;
}
Here is the implementation for the run() method that actually processes the request:
#include <rwsf/handlers/MessageInfo.h>
#include <rwsf/handlers/MessageInfoHandlerChain.h>
void RequestHandler::run()
{
std::string payload = //1
while( (c = clientRequest.read()) != end of data )
payload += c;
rwsf::MessageInfo message;
message.set<std::string>("rwsf/request/payload", payload); //2
rwsf::MessageInfoHandlerChain chain = getHandlerChain(); //3
chain.invoke(message); //4
std::responseMsg = message.get<std::string>("rwsf/response/payload"); //5
clientRequest.write(responseMsg); //6
}
When the invoke has completed, the
rwsf::MessageInfo 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 connector instance.
RWSF_DEFINE_CONNECTOR(MyConnector);
The following table shows some of the main data structures in
rwsf::MessageInfo.
Table 9 – Data structures in rwsf::MessageInfo
Name | Type | Description |
rwsf/request/payload | std::string | The request payload of this message. |
rwsf/response/payload | std::string | The response payload of this message. |
rwsf/response/transport/headers | | Data specific to transport headers. |
Implementing the Virtual Methods
The following code shows the
init() method for a connector that watches a directory. The
init() method is called during Agent initialization, and the name of the directory is passed to the connector in an
rwsf::Config instance.
void
MyConnector::init(const rwsf::Config& config,
const rwsf::AgentContext& context)
{
ConnectorImp::init(config, context);
host_ = config.getInitParameter("host");
port_ = config.getInitParameter("port");
... // other needed initiation code
}
The derived
init() method should invoke the
init() method of its parent so the parent class can also process any information from the
rwsf::Config instance.
The start() method 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. The Agent invokes the start() method after it has initialized all connectors and handlers.
void
MyConnector::start()
{
address = // get address
serverSocket_ = new Socket();
serverSocket_.bind(address);
socketThread_ = new SocketThread(serverSocket);
socketThread_.start();
...
}
The stop() method should be implemented as a blocking call. The stop() method first releases any resources the connector owns and then stops the connector. The Agent invokes the stop() method when the Agent receives a signal to shut down.
void
MyConnector::stop()
{
socketThread_.shutdown();
socketThread_.join();
...
}
Configuring the Connector
Compile your new connector into a shared library (DLL) and place it in the bin directory of your HydraExpress installation. You are now set to load the connector into the Agent. You do this by creating a new rwsf:connector element in rwagent.xml with the appropriate configuration information. For example:
<rwsf:connector name="My_New_Connector"
class="myconnector.createMyConnector" handlerChain="http">
<rwsf:property name="host" value="<host-name>"/>
<rwsf:property name="port" value="<port-value>"/>
...
</rwsf:connector>
The name attribute is the string that represents the connector to the rest of the system. This string must be unique in the system.
The class attribute defines how to create an instance of this connector. In this example, myconnector is the name of the shared library that contains the connector code. The string createMyConnector is the name of the method that was created from the RWSF_DEFINE_CONNECTOR macro described above.
The handlerChain attribute specifies the name of the handler chain that this connector is tied to. The handler chain must be declared in rwagent.xml before the connector is declared. The example uses the handler chain that the HTTP/1.1 connector is using.