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
.
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 commandline 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
libary 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.