12. Advanced Topics¶
C# Client Compatibility¶
AMPS clients are available for many languages. Many AMPS customers write clients using a variety of languages, often both Java and C#. While Java and C# are fundamentally different languages, they share enough syntax that it can be straightforward to port code between the two, and especially from Java to C#.
To aid in conversion from Java to C# (and from C# to Java), the C# client has a number of features that make it a little easier to bring code from Java to C#:
getXXX()
/setXXX()
Java-style getters and setters are provided corresponding to properties on theMessage
class. For example, given a variable message of typeMessage
, the code:string userName = message.UserName
and
string userName = message.getUserName()
are equivalent.
C# Parameters that take lambda functions also take an interface type. The AMPS Java client defines interfaces such as
ClientMessageHandler
, which your application implements with a singleinvoke()
method that is called when an event occurs. In C#, the AMPS client uses lambda functions and delegates to provide equivalent functionality. However, the same*Handler
interfaces exist in C#, and instead of passing a lambda function, you may also implement these interfaces and pass in derived classes. While doing so would be inconvenient in C#, providing this symmetry allows your Java and C# to be ported interchangeably.Java-style method name conventions are used throughout AMPS. In .NET, method names often begin with a capitalized first letter (e.g.
Connect()
instead ofconnect()
). However, the C# AMPS client retains the capitalization style of the Java client where possible, making porting straightforward.
Rebuilding the Client¶
In the rare occasion that you need to make customizations to the AMPS
Java Client, the installation packages include the full source code for
the AMPS client. Rebuilding the client is straightforward using the provided
build.xml
, and the Apache ant
program. The build.xml
file is
located in the api/client/java/
directory of the root of your AMPS
installation. For example, if AMPS is installed in /opt/AMPS/
, then the
Java client’s build.xml
would be located in the
/opt/AMPS/api/client/java/
directory.
Assuming that a JDK version 1.6 or greater is installed and the Apache
ant
package has been installed, rebuilding the client is
as simple as typing:
ant
from the command line in the same directory where the build.xml
file
is located in the AMPS install. Upon successful completion, the
libraries will be located the api/client/java/dist/lib
directory.
Transport Filtering¶
The AMPS Java client offers the ability to filter incoming and outgoing
messages in the format they are sent and received on the network. This
allows you to inspect or modify outgoing messages before they are sent
to the network, and incoming messages as they arrive from the network.
To create a transport filter, you implement the interface
TransportFilter
, construct an instance of the filter class, and
install the filter with the setTransportFilter
method on the
transport.
The AMPS Java client does not validate any changes made by the transport filter. This interface is most useful for application debugging or transport development.
The client includes a sample filter, TransportTraceFilter
, that
simply writes incoming and outgoing buffers to an OutputStream
.
Notice that the transport filter function is called with the verbatim contents of data received from AMPS. This means that, for incoming data, the function may not be called precisely on message boundaries, and that the binary length encoding used by the client and server will be presented to the transport filter.
Advanced Memory Management Techniques¶
One of the most important features of the Java language and runtime is automatic memory management. One drawback of this approach is that garbage collection can introduce latency and provide an inconsistent response time for the application.
The AMPS Java client is designed to allow you to build an application that requires no memory allocation to process and consume messages during steady-state processing (that is, once the client is connected and messages are flowing to the application).
Use the following techniques to avoid memory allocation in your application:
Publish from a Byte Buffer¶
The publish()
and delta_publish()
methods provide overloads that
accept a byte array, starting position in the array, and a length rather
than a String
. These methods directly copy the
message bytes provided into the buffer that the transport uses to send
the message, without creating an intermediate copy or temporary object.
Avoid Publisher Contention or Use a synchronized Wrapper¶
The Java ReentrantLock
used to protect publishes can result in memory
allocation if the lock is contended. To avoid this allocation, either
avoid publishing messages using a single instance of the Client
from
more than one thread at the same time, or wrap calls that publish messages
in a method that is marked synchronized
to avoid contention in the Client
.
Use Asynchronous Message Processing¶
When a subscription uses asynchronous message processing, the Message
that
is provided to the MessageHandler
is allocated
once, per client. The contents of the message are references to an underlying
buffer that is reused for each message
Notice that synchronous message processing (the MessageStream
interface)
allocates a full copy of each message received, and should not be used in
environments where minimal memory allocation is a goal.
Use the Raw
Versions of Accessors¶
When retrieving information from an instance of the Message
class,
use the versions of accessors labeled with Raw
. These methods do not
allocate memory: instead, they return the Field
objects within the Message
. These objects, in turn, are references to an underlying buffer. For
Message
objects provided to a MessageHandler
, that underlying buffer
is the buffer that the Client
uses for reading from the socket.
To Process on Another Thread, Use an Object Pool and Fixed-Size Buffer¶
Many applications designed with low-latency in mind can perform the processing
that they need within the MessageHandler
. In many other cases, though,
it is necessary for one or more worker threads to process requests.
If your application needs to follow this pattern, three best practices apply:
- Create an object that holds the exact data necessary, without requiring allocation.
- Use an object pool to manage instances of that object and reuse those instances as necessary.
- Use a fixed-size data structure (such as a ring buffer) to pass references to those objects between the receive thread and the processing thread.
Consider Message Type and Parser Implementations¶
When designing a system that will minimize allocation, it is also important to consider the message type and parser that the system will use. Some popular parsers are not designed with minimal allocation in mind, and those parsers are not a good match for systems that need to control allocation.
The 60East BFlat parser implementation is an example of a parser
that is designed to allow an application to consume messages with
minimal memory allocation. The parser can operate on a buffer, and
the consumer can optionally reuse a single
BFlatValue
object that is owned by the parser, no matter how
many fields are produced or how many messages are parsed.
Working with Messages & Byte Buffers¶
The AMPS Java client allows you to publish messages that contain data from byte buffers. When working with byte buffers in AMPS, it’s best to follow these simple conventions.
AMPS provides overloaded publish()
methods that allow you to publish
messages from various formats. In this case, to publish a message using
byte buffers, the message data must be provided as a byte[]
. Along
with the message data, the message topic, to which the message will be
published to, must also be provided as a byte[]
.
The example below shows how to serialize an object into a byte buffer,
then publish the message to AMPS using the publish()
method.
...
// create the topic string
String topic = "messages";
// create the object data
Employee emp = new Employee();
// create the field for the payload of the message
Field data = new Field();
try {
// create streams used to serialize the object and
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
ObjectOutputStream oStream = new ObjectOutputStream(bStream);
// serialize the object to the ObjectOutputStream
oStream.writeObject(emp);
// set the byte of the message to a field
data.set(bStream.toByteArray());
}
catch (Exception e) {
e.printStackTrace();
}
//publish to the "messages" topic using byte buffers
client.publish(topic.getBytes(), 0, topic.getBytes().length, data.buffer, 0, data.buffer.length);
...
In addition to publishing messages, AMPS allows you to access the raw
bytes in the data part of a message. The method getDataRaw()
returns
a Field
that is composed of a byte buffer, position of the data in
the buffer, and the length of the data. This data can then be
deserialized and converted to an object for further use.
The example below shows how to access the raw bytes of a message, and then shows how to deserialize the bytes of that message to a Java object.
...
// access the message raw data
Field data = message.getDataRaw();
// construct a ByteArrayInputStream object,
// an ObjectInputStream object and a Field
// to deserialize the data
ByteArrayInputStream bais;
ObjectInputStream ois;
Field data;
// construct the payload object
Employee emp;
ByteArrayInputStream
try {
// deserialize the data using ObjectInputStream to an object
bais = new ByteArrayInputStream(data.buffer, data.position, data.buffer.length);
ois = new ObjectInputStream(bais);
emp = (Employee) ois.readObject();
}
catch(Exception e) {
...
}
Providing SSL Certificates to the AMPS Java Client¶
The AMPS Java client includes support for Secure Sockets Layer (SSL)
connections to AMPS. The client automatically attempts to make an SSL
connection when the transport in the connection string is set to
tcps
, as described in Chapter 3.
There is no other change required to application code to use an SSL connection. However, to successfully make a SSL connection to AMPS, the Java runtime requires:
A key to provide for the client connection, as stored in the keystore.
A trust store to use to validate the server certificate used for the connection.
Most often, this means that the server certificate for the connection must be signed by a trusted certificate, and that trusted certificate must be in either the default trust store, or a trust store provided to the JVM at runtime. This can also be accomplished by importing the server certificate into the default trust store or a trust store set by the application at run time.
The parameters that you need to provide depend on how you have configured your certificates. If the server certificate is in the default trust store, you only need to provide a key for the client connection. Otherwise, you need to provide both a client key and a trust store that can validate the server certificate.
There are three common methods of providing these certificates:
- Setting global parameters on the command line
- Setting global system properties
- Creating an SSL context object for a specific connection
These options are discussed in more detail below. In general, the first two methods are easiest for simple testing, and allow you to easily set the same options for all connections from the application. The third method is the most flexible, and lets an application use different options for different connections.
Java Virtual Machine implementations may differ somewhat in the
implementation details for javax.net.ssl
. For full details on
providing certificates to the Java runtime, see the documentation for
your JVM implementation. For example, documentation on the Oracle JVM
implementation of javax.net
is available at
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html Java
Secure Socket Extension (JSSE) Reference Guide, and this section focuses
on the Oracle JVM.
Setting Global Keystore and Truststore on the Command Line¶
For example, if you have a key installed into a keystore file and the server certificate is present in the default truststore or signed by a certificate in the default truststore, you might provide the client key to an application using the following command-line flags:
-Djavax.net.ssl.keyStore=<path to keystore file> \
-Djavax.net.ssl.keyStorePassword=<password>
If the server certificate is in a different truststore, you might provide the following set of flags to specify both the keystore and the truststore to use:
-Djavax.net.ssl.trustStore=<path to truststore> \
-Djavax.net.ssl.trustStorePassword=<truststore password> \
-Djavax.net.ssl.keyStore=<path to keystore file> \
-Djavax.net.ssl.keyStorePassword=<keystore password>
When you provide passwords on the command line, it is possible for other users on the system to see the password. This method is most often used during development and debugging.
Setting Global Keystore and Truststore Within the Application¶
Within an application, you can use the System.setProperty
method to
set flags before creating a Client
or HAClient
. For example, if
you have a key installed into a keystore file and the server certificate
is present in the default truststore, you might set these properties
when your application starts.
System.setProperty("javax.net.ssl.keyStore", "path to keystore file");
System.setProperty("javax.net.ssl.keyStorePassword", "keystore password");
This is equivalent to the first set of command line arguments above. As with the command line arguments above, you can use this method to set the truststore arguments as necessary.
System.setProperty("javax.net.ssl.keyStore", "path to keystore file");
System.setProperty("javax.net.ssl.keyStorePassword", "keystore password");
System.setProperty("javax.net.ssl.trustStore", "path to truststore file");
System.setProperty("javax.net.ssl.trustStorePassword", "truststore password");
Creating an SSL Context¶
Setting options on the command line or with the System.setProperty
method affects every connection made from the application. You can also
create your own SSLContext
for the application to use for the
tcps
transport. This method provides the most flexibility, and
allows you to use different information for different connections.
The snippet below has the same end result as the first command line
above for the AMPS connection (that is, it sets a keystore and uses the
default trust store). However, because this SSLContext
will only be
used by the AMPS client, you can use a keystore and certificates that
are only used for AMPS connections, rather than for the application as a
whole. This may be required if the application needs to use SSL to
connect to a different external system, and the keys or trust store used
for AMPS are different than the keys or trust store for the other
system.
// Snippet showing how to provide a keystore to the
// TCPSTransport for use of the AMPS Java client.
// Create a keystore. Assume that the keystore
// is in JKS format.
KeyStore ks = KeyStore.getInstance("JKS");
String password = ... ; // Obtain password for keystore
FileInputStream fis = null;
try {
fis = new java.io.FileInputStream(<path to keystore file>);
ks.load(fis, password.toCharArray());
}
finally {
if (fis !=null ) fis.close();
}
// Get the key manager factory, using the default
// algorithm.
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// Initialize the factory with the keystore.
kmf.init(ks, password.toCharArray());
// Get the SSL context
SSLContext context = SSLContext.getInstance("TLS");
// Use the key manager just constructed, with defaults
// for the trust manager and randomness source.
context.init(kmf.getKeyManagers(), null, null);
// Set the SSLContext for the TCPS transport
// to the context just set up with the keystore.
TCPSTransport.setDefaultSSLContext(context);
Troubleshooting SSL Connectivity to AMPS¶
To troubleshoot SSL connectivity, run the application with the following flag:
-Djavax.net.debug=all
With this flag set, the javax.net
library will produce detailed
information about the connection, including extensive information
designed to help identify and resolve certificate problems. In
particular, this flag will produce information on the certificates that
the application is using, whether the application can successfully load
those certificates, and whether the application can validate the
certificate provided by the AMPS server.