QServer as client
/2019-08-29/
Q: I need to send a message out the door to the TCP/Client that is connected to this server.
A:
Despite the fact that QServer is a server from a TCP/IP standpoint, it can be a client at the application level.
sequenceDiagram participant Acquirer participant QServer Acquirer->>QServer: Acquirer initiates TCP/IP connection QServer->>Acquirer: ISO-8583 request Acquirer->>QServer: sends ISO-8583 response
This can be easily achieved by adding an <in>
and <out>
queue to the QServer configuration, in the same way you’d use it in a ChannelAdaptor
, i.e.:
<server class="org.jpos.q2.iso.QServer" logger="Q2" name="xml-server">
<attr name="port" type="java.lang.Integer">8000</attr>
<channel class="org.jpos.iso.channel.XMLChannel"
logger="Q2" packager="org.jpos.iso.packager.XMLPackager">
<property name="timeout" value="3600000" />
</channel>
<in>remote-send</in>
<out>remote-receive</out>
</server>
Those in/out
queues can be connected to a QMUX, i.e.:
<mux class="org.jpos.q2.iso.QMUX" logger="Q2" name="isobridge">
<in>remote-receive</in>
<out>remote-send</out>
</mux>
(Quick reminder: in/out
queues are seen from the component’s perspective, when you want to “send” something to say a ChannelAdapter, you push the ISOMsg to its in
queue. Whatever the ChannelAdaptor receives on its TCP/IP ISO-8583 side, it will place it in its out
queue - same goes for the QServer, and QMUX).
If in addition to sending messages to the remote TCP/IP client you want to receive messages coming from it, you can use an ISORequestListener. The typical use has the ISORequestListeners in the QServer, but in this configuration, you need to move them to the QMUX.
So the QServer would just use the in/out
queues to communicate with other components, and the QMUX would take care of handling outgoing as well as incoming messages.
graph LR Client --> QServer QServer --> QMUX QMUX --> QServer QMUX --> ISORequestListener ISORequestListener --> QServer
Now, this is important: when client code uses the QMUX to send out a request
to the remote host, by placing it in the QServer’s in
queue, QServer will send it out through the last connected ISOChannel (there’s a handy QServer.getLastConnectedISOChannel()
method for that).
When QServer listens on a given port, many clients can connect:
graph LR Client1 --> QServer Client2 --> QServer Client3 --> QServer QServer --> QMUX
If the connections come in order (client 1, client 2, client 3), if you call mux.request
, QServer will send it through the socket connection to client 3.
If this is your intended use-case, you can stop here. But there are some other use cases, for example, when you want to initiate a network management message upon connection:
sequenceDiagram participant Acquirer participant QServer Acquirer->>QServer: Acquirer initiates TCP/IP connection QServer->>Acquirer: 0800 Acquirer->>QServer: 0810 Acquirer->>QServer: 0200 QServer->>Acquirer: 0210
In this situation, one typically want to send a 0800 message on ALL connections to QServer.
In addition to the request-listener
that one can add to the QServer configuration (or QMUX), there’s also a connection-listener
that gets called once QServer accepts a connection and can be used to trigger a 0800, or to register the new channel with a custom connection manager.
The configuration would look like this:
<server class="org.jpos.q2.iso.QServer" logger="Q2" name="xml-server-8000" realm="xml-server-8000">
<attr name="port" type="java.lang.Integer">8000</attr>
<channel class="org.jpos.iso.channel.XMLChannel"
packager="org.jpos.iso.packager.XMLPackager"
type="server"
logger="Q2"
realm="xml-server-8000">
<property name="timeout" value="180000"/>
</channel>
<request-listener class="org.jpos.iso.IncomingListener" logger="Q2" realm="incoming-request-listener">
<property name="queue" value="TXNMGR" />
<property name="ctx.DESTINATION" value="jPOS-AUTORESPONDER" />
</request-listener>
<connection-listener class="com.my.custom.Logon" />
</server>
The Logon
class can look like this:
import org.jpos.iso.*;
import org.jpos.util.Log;
import java.util.Date;
import java.util.EventObject;
import java.util.concurrent.atomic.AtomicLong;
public class Logon extends Log implements ISOServerEventListener {
AtomicLong stan = new AtomicLong();
@Override
public void handleISOServerEvent(EventObject event) {
if (event instanceof ISOServerAcceptEvent) {
ISOServer server = (ISOServer) event.getSource();
ISOChannel channel = (ISOChannel) event.getISOChannel();
try {
channel.send(createMsg("001"));
} catch (Exception e) {
error(e);
}
}
}
private ISOMsg createMsg (String msgType) throws ISOException {
ISOMsg m = new ISOMsg ("0800");
m.set (7, ISODate.getDateTime (new Date()));
m.set (11, ISOUtil.zeropad (Long.toString (stan.incrementAndGet()), 6));
m.set (70, msgType);
return m;
}
}
This simple example blindly sends a 0800 message on the newly created channel, but users may want to register the channel internally so that network management messages can be regularly sent.
WARNING: If you keep a reference to channel in your ISOServerEventListener callback, we suggest to use a WeakReference so that the channel doesn’t create a memory leak once disconnected.
In addition to registering channels at connection time using a connection listener, QServer has a handy getISOChannelNames()
method that returns a space-separated list of channel names. That list can be split and iterated using the getISOChannel(String)
method to get a reference to a connected channel, and that channel can be used to regularly fire network management messages.