| | | | Browse by category |
Article ID: 1449
Last updated: 06 Nov, 2009
Contents
Rogue Wave ® Hydra allows use of nearly any transport via custom connectors and any data protocol via custom handlers. A Hydra connector is the module that attaches the server to the data network (whether it be socket-based, queue-based, etc.). It is responsible for retrieving data off the network and passing it on to the handler chain. The protocol handler is responsible for extracting the data and processing it into a form the agent can use. For SOAP, this means parsing the envelope and pulling the data from the payload. For other protocols, the behavior may vary widely, but with all connector-protocol handler pairs, the data must be available as a Hydra service variable at the end.
In this example, devices contact a service to signal current states. These devices call an operation,
SignalOperation
. It receives the DEVICE_ID
and a NAME/VALUE
pair describing the device state, and returns an acknowledgement message, ACK
.Hydra generates a default service that uses a SOAP payload carried over HTTP. Requests and responses to this service are a combination of HTTP headers and the SOAP payload. This sample request shows the HTTP header in blue and the SOAP in red.
POST /rwsf/SignalService/SignalService HTTP/1.1
Host: 127.0.0.1:332
Content-Type: text/xml; charset=utf-8
Content-Length: 437
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: IBM Web Services Explorer
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: "SignalServiceOperation"
Connection: close
<soapenv:Envelope xmlns:soapenv=http://schemas.xmlsoap.org/soap/envelope/
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<ns0:SignalOperation>
<DEVICE_ID>device1</DEVICE_ID>
<NAME>level</NAME>
<VALUE>high</VALUE>
</ns0:SignalOperation>
</soapenv:Body>
</soapenv:Envelope>
Our goal is to use a custom protocol, as well as a custom transport. To that end, we will use a basic TCP/IP Socket as a transport, and a space-delimitated payload:
device name value
or
printer status ready
The service returns an acknowledgment message,
ACK
, in the form of a simple string.The printer has sent the message, status is ready.
The implementation consists of a service, a custom connector for the transport and a custom handler for the space-delimited string payload. We also must reconfigure the Agent to recognize the new connector and the new handler.
The connector listens to the TCP/IP Socket. It receives the call, extracts the payload and passes it to the system for processing. Finally, it publishes the response payload back to the socket.
When a message comes in through the connector, the handler parses the payload, and transforms it into something Hydra can understand, a
MessageServiceVariable
. When the response is ready, the handler serializes the response back to a corresponding response payload.Connectors and handlers are usually compiled into different libraries, but for simplicity, the connector,
SocketConnector
, and the handler, NameValuePairHandler
, in this example are in the same library, ConnecotorHandler12d.dll
(or .so
). The service is in a separate library, SignalService12d
(or .so
). This separation is necessary because the two libraries are deployed to different locations.Let’s begin by configuring the handler and connector in the Agent. The custom handler handles two types of messages, requests and responses. The new
<rwsf:handler-chain>
, NameValueChain
, chains together a handler for each type of message; along with the handler-chain, agentHandler
; and a catch-all handler. The new chain is added immediately under the agentHandler
handler-chain.<rwsf:handler-chain name="NameValueChain">
<rwsf:handler name="namevalue-processor-server-response"
.createNameValuePairHandler"
class="ConnectorHandler
type="response">
</rwsf:handler>
<rwsf:handler name="namevalue-processor-server-request"
class="ConnectorHandler.createNameValuePairHandler"
type="request">
</rwsf:handler>
<rwsf:handler-chain-ref name="agentHandler"/>
<rwsf:handler name="catchAll"
class="rwsf_message43.createCatchAllHandlerImp"
type="request"/>
</rwsf:handler-chain>
The class attribute names the library (
ConnectorHandler12d.dll
), and the class (NameValuePairHandler
) that we use for both requests and responses. The chain also specifies that once these handlers have completed, the requests and responses are passed on to the standard Hydra chain, agentHandler
.The custom
<rwsf:connector>
element is defined in rwagent.xml
immediately under <rwsf:agent>
.<rwsf:connector class="ConnectorHandler.createSocketConnector"
handlerChain="NameValueChain"
name="SocketConnector">
<rwsf:property name="service"
value="http://localhost:8090/rwsf/SignalService/SignalService"/>
<rwsf:property name="operation" value="SignalOperation"/>
<rwsf:property name="portNumber" value="3333"/>
</rwsf:connector>
Again, the class value specifies the library (
ConnectorHandler12d.dll
) and the class (SocketConnector
). When the Agent starts it loads the connector’s library and calls SocketConnector::init()
. The handlerChain
value tells the Agent to pass any messages received on this connector to the NameValueChain we defined earlier. The connector configuration also defines three properties: service
, operation
and portNumber
. The connector listens on port 3333 for incoming messages and calls /rwsf/SignalService/SignalService/SignalOperation
for any requests received.Messages sent using the HTTP/SOAP combination contain the service location in the HTTP request and the operation either in the HTTP header or in the SOAP payload.
POST /rwsf/SignalService/SignalService HTTP/1.1
Host: 127.0.0.1:332
Content-Type: text/xml; charset=utf-8
Content-Length: 437
Accept: application/soap+xml, application/dime, multipart/related, text/*
User-Agent: IBM Web Services Explorer
Cache-Control: no-cache
Pragma: no-cache
SOAPAction: "SignalServiceOperation"
Connection: close
Since the new socket transport doesn’t use HTTP or SOAP, however, we need to supply this information in some other manner. We could try to alter our transport specification to send the location and operation, or even hard code it in the connector directly. Specifying service location and the operation in the rwagent is a little more flexible without creating too much complication, and allows us to reuse this connector for other services. We could configure in
rwagent.xml
another instance of SocketConnector
with a different port parameter. A second service and operation combination could then use the same socket transport.The Hydra service wizard in Eclipse (which ships with Hydra), builds and deploys a service in a few minutes. For more on this step, see http://roguewave.com/downloads/white-papers/guide-to-creating-cpp-web-services.pdf. The wizard creates a project and an implementation class. The project file associated with this document contains the example project,
SignalService
. The implementation class, SignalServiceService.cpp
, is simple and independent of the connector/handler type.std::string SignalServiceService::SignalOperation(
const std::string& NAME_in,
const std::string& VALUE_in,
const std::string& DEVICE_ID_in )
{
std::string returnVal;
returnVal = "The " + DEVICE_ID_in + " has sent the message, " +
NAME_in + " is " + VALUE_in + ".";
getLogger().info("Service is returning " + returnVal);
return returnVal;
}
Since the service doesn’t care about the transport and protocol, we can use Web Services Explorer, a tool within Eclipse, to initially test the service. Web Services Explorer calls the service using SOAP over HTTP. For more on how to test a service with the Web Services Explorer, see the Hydra Services Guide, Working with C++ Services.
Let’s next inspect the small amount of code required in
SocketConnector
. We derive from the base class, rwsf::ConnectorImp
, and implement the methods init()
, reinit()
, start()
, and stop()
. virtual void init(const rwsf::Config& config,
const rwsf::AgentContext& context);
virtual void reinit(const rwsf::Config& config,
const rwsf::AgentContext& context);
virtual void start();
virtual void stop();
The virtual
init()
method accesses the properties portNumber
, service
and operation
defined in the connector configuration in rwagent.xml
. Using the rwsf::Config
parameter to the init()
method we can get these values and save them in the SocketConnector
class for later use.The
reinit()
and stop()
implementations are empty in this example.The Agent invokes the
start()
method after all the connectors and handlers have been initialized. The start()
method calls a private method, listen()
, that does the majority of the connector’s work. We could have used the WinSock API to implement the socket but Rogue Wave’s ® SourcePro framework supplies platform independent networking libraries, which provide portability to Linux, UNIX, and Windows.void SocketConnector::listen(RWCString server)
{
RWSockAddr addr(server);
RWSocketListener listener(addr);
RWSocket socket = listener.getSocket();
addr = socket.getsockname();
while (1)
{
try
{
RRWSocketPortal portal = listener();
RWTimedPortal tPortal(portal, networkMaxWait);
RWPortalIStream pistrm(tPortal);
RWPortalOStream postrm(tPortal);
RWCString line("");
while (line.readLine (pistrm)) //1
{
rwsf::MessageInfo messageInfo; //2
rwsf::ServiceIdentifier serviceID(service_, operation_); //3
messageInfo.setServiceIdentifier(serviceID);
messageInfo.set<std::string>(RWSF_KEY_REQUEST + "/" +
RWSF_KEY_PAYLOAD, line.data()); //4
getHandlerChain().invoke(messageInfo); //5
if (messageInfo.contains(RWSF_KEY_RESPONSE + "/" +
RWSF_KEY_PAYLOAD)) //6
{
std::string payload = messageInfo.get<std::string>
(RWSF_KEY_RESPONSE + "/" + RWSF_KEY_PAYLOAD);
}
} // end while line.readLine
} // end try
catch (const RWNetOperationTimeoutError& x) {
context_.getLogger().info("RWNetOperationTimeoutError thrown");
context_.getLogger().info(x.why());
}
catch (const RWSocketError& x) {
context_.getLogger().info("RWSocketError thrown");
context_.getLogger().info(x.why());
}
catch (...) {
context_.getLogger().info("Exception thrown");
}
} // end while(1)
}
RWSF_DEFINE_CONNECTOR(SocketConnector); //7
// 1: Get the string coming in on the socket.
// 2: Create a MessageInfo object (the structure passed down the handler chain).
// 3: Create a ServiceIdentifier object, and add it to MessageInfo. The Agent determines which service/operation combination to call based on the information supplied by the ServiceIdentifier. Remember, the rwagent.xml configuration file specified the service and operation names in the properties for the connector.
// 4: Add the payload to the same MessageInfo structure.
// 5: Pass this MessageInfo structure to the handler chain.
// 6: Check if a response came back and allow the MessageInfo to be passed back to the socket.
// 7: This macro enables the dynamic creation of the connector.
The CustomHandler (NameValuePairHandler.cpp)
We have covered how the connector listens to the transport, extracts a payload (regardless of its content) and passes it to the handler for processing. The handler parses an incoming request and passes it on to the service. Once the service produces a response, the handler serializes the result and passes it back to the connector.
Implementing a handler starts with deriving from the abstract base class
rwsf::MessageInfoHandlerImp
, and implementing two of its methods, init()
and invoke()
. The invoke()
method either parses the message if it is on the way in (a request) or serializes the message if it is on the way out (a response).virtual void init(const rwsf::Config& config,
const rwsf::AgentContext& context);
virtual bool invoke(rwsf::MessageInfo& message);
The handler’s parseMessage function processes the request.
bool NameValuePairHandler::parseMessage(rwsf::MessageInfo& message)
{
const std::string prefix = RWSF_KEY_REQUEST + "/";
const std::string payload = message.get<std::string>
(prefix + RWSF_KEY_PAYLOAD); //1
rwsf::MessageServiceVariablePtr messageSVPtr =
new rwsf::MessageServiceVariable();
rwsf::SimpleServiceVariablePtr deviceIDPtr =
new rwsf::SimpleServiceVariable();
rwsf::SimpleServiceVariablePtr namePtr = new rwsf::SimpleServiceVariable();
rwsf::SimpleServiceVariablePtr valuePtr = new rwsf::SimpleServiceVariable();
RWCTokenizer next(payload);
RWCString deviceID("");
RWCString name("");
RWCString value("");
if(!(deviceID = next()).isNull()) //2
{
deviceIDPtr->setString(deviceID.data());
messageSVPtr->setPart("DEVICE_ID", deviceIDPtr);
if(!(name = next()).isNull())
{
namePtr->setString(name.data());
messageSVPtr->setPart("NAME", namePtr);
if(!(value = next()).isNull())
{
valuePtr->setString(value.data());
messageSVPtr->setPart("VALUE", valuePtr);
}
}
}
message.set<rwsf::ServiceVariablePtr>(prefix + RWSF_KEY_INPUT_VARIABLE,
messageSVPtr); //3
return true;
}
// 1: Retrieve the payload string passed in by the connector.
// 2: Parse the input string into its separate tokens.
// 3: Build the ServiceVariable using the values from the string and add it to the MessageInfo object.
For simplicity, Ant build files have been included in the project. The build files have targets for easily building and deploying the project. They also contain a target,
setup-agents
, which creates a temporary agent instance, SignalService/agents/sample
, by copying the default agent. The target overwrites the rwagent.xml
file in the copy of the agent providing the configuration for the connector and handler we have created.The
deploy
target contains an important step in using the connector and handler. The service is deployed to the agent instance, but the connector/handler library must be deployed to <Hydra install>/apps-bin
. Precise instructions for running the example are in the
readme.txt
included in the project.You will enter a telnet command to send the request message “printer status ready”. The result appears on the command prompt.
The printer has sent the message, status is ready.
This demonstrates that both our connector and handler have done their jobs. It is interesting to note that you can also use the Web Services Explorer included with Eclipse to test the same service through a standard web service interface. The same code base for the service is called using two different transports/protocols, and is transparent to the Hydra service engine.
Hydra Agent Runtime Guide for information on connectors and handler chains.
Hydra Services Guide for information on creating a Hydra service.
SourcePro Essential Networking Module User’s Guide for information on sockets.
SourcePro Essential Tools Module User’s Guide for information on the String Tokenizer.
This article was:
Helpful |
Not helpful
Report an issue
Article ID: 1449
Last updated: 06 Nov, 2009
Revision: 1
Views: 2271
Posted: 06 Nov, 2009 by
--
Updated: 06 Nov, 2009 by
Also listed in
Others in this category
Powered by KBPublisher (Knowledge base software)