io_nats_client_utils - RichardHightower/jnats GitHub Wiki

CoverageServerPool
The CoverageServerPool class is a concrete implementation of the ServerPool interface. It is used for testing purposes to set properties and call the builder in the Options class.
CloseOnUpgradeAttempt
This CloseOnUpgradeAttempt class is a public class that extends the SocketDataPort class. It is designed to handle the closing of a socket connection when an upgrade attempt is made.
RunProxy
The RunProxy class is a public class that implements the Runnable interface. It is a quick proxy server implementation used for testing purposes.
public void run()
@Override
public void run() {
try {
runImpl();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}The run method in the io.nats.client.utils.RunProxy class can be described as follows:
- This method is an implementation of the
runmethod from theRunnableinterface and is marked with the@Overrideannotation. - The method begins by defining a
tryblock. - Inside the
tryblock, therunImplmethod is called. It is assumed that this method contains the actual logic that needs to be executed. - If an
IOExceptionis thrown during the execution of therunImplmethod, the method catches it. - Inside the
catchblock, a newUncheckedIOExceptionis created with the caughtIOExceptionas the cause of the exception. - Finally, the
UncheckedIOExceptionis thrown, indicating that an exception occurred during the execution of therunImplmethod.
The run method in the RunProxy class of the io.nats.client.utils package is an overridden method that implements the Runnable interface. This method internally calls the runImpl method, which performs the actual logic of the execution. If any IOException occurs during the execution, it catches the exception and throws it as an UncheckedIOException. Overall, the run method is responsible for executing a specific set of instructions and handling any potential IO errors that may occur.

private void runImpl() throws IOException {
while (true) {
Socket client = server.accept();
executor.submit(() -> proxy(client));
}
}The runImpl method in the RunProxy class is used to continuously accept new client connections and spawn a separate thread to handle each connection. Here is a step-by-step description of what the method is doing:
- The method sets up an infinite loop using the
while (true)construct. - Inside the loop, it waits for a new client connection by calling the
accept()method on theserversocket object. This method blocks until a client connection is established. - Once a client connection is established, the method proceeds to the next line.
- It creates a new
Socketobject namedclientto represent the established client connection. - The method then uses an
executorobject, which is likely an instance of theExecutorServiceinterface, to submit a new task to be executed in a separate thread. - The submitted task is a lambda expression defined as
() -> proxy(client). This lambda expression represents the code to be executed in the separate thread. It invokes theproxymethod, passing theclientsocket object as an argument. - The
executoris responsible for managing threads and executing tasks concurrently. It will ensure that the submitted task is executed asynchronously while therunImplmethod continues to the next iteration of the loop. - Once the task is submitted, the loop repeats, waiting for the next client connection.
In summary, each time the runImpl method is called, it sets up a loop that continuously accepts client connections. It spawns a new thread to handle each connection by submitting a task to an executor. This design allows the server to handle multiple client connections concurrently.
The runImpl method defined in the io.nats.client.utils.RunProxy class is responsible for running a server that accepts client connections and handles them concurrently.
Here's what the method does:
- The method starts an infinite loop using the
while (true)construct. - Within the loop, it waits for a client to connect to the server by calling the
acceptmethod on theserverinstance ofSocket. This method will block until a client connection is made. - Once a client connection is established, a new thread is created and submitted to an executor using the
executor.submit()method. This allows multiple client connections to be handled simultaneously. - The submitted task is a lambda expression that invokes the
proxymethod, passing the connectedclientsocket as an argument. Theproxymethod likely handles the actual communication with the client. - The loop continues to accept new client connections, repeating the process.
In summary, the runImpl method sets up a server to listen for client connections, and for each connection, it spawns a new thread to handle the client's requests. This enables concurrent processing of multiple client connections.

private void proxy(Socket client) {
Socket remote;
try {
remote = handshake(client);
if (null == remote) {
return;
}
} catch (Exception ex) {
System.err.println("Failure to establish a proxy:");
ex.printStackTrace(System.err);
try {
client.close();
} catch (Exception ignored) {
}
return;
}
executor.submit(() -> transferTo(remote, client, "remote -> client"));
executor.submit(() -> transferTo(client, remote, "client -> remote"));
}The proxy method is a private method defined in the io.nats.client.utils.RunProxy class. This method takes a Socket object named client as a parameter.
Here is a step-by-step description of what this method is doing:
-
The method starts by declaring a
Socketobject namedremote. -
Inside a try-catch block, a handshake operation is performed by calling the
handshakemethod and passing theclientsocket as a parameter. The result of the handshake operation is assigned to theremotevariable. If theremotevariable isnull, the method returns, indicating a failure to establish a proxy. -
If an exception occurs during the handshake operation, an error message is printed to the standard error stream and the exception stack trace is also printed. Then, the
clientsocket is closed, and the method returns. -
If the handshake is successful and the
remotevariable is notnull, the method continues execution. -
The method then submits two tasks to an executor. These tasks are implemented as lambda expressions and use the
transferTomethod to transfer data between theremoteandclientsockets. -
The first task transfers data from the
remotesocket to theclientsocket. The method provides a description of this transfer as "remote -> client". -
The second task transfers data from the
clientsocket to theremotesocket. The method provides a description of this transfer as "client -> remote". -
The method does not wait for the completion of these tasks and returns immediately.
In summary, the proxy method establishes a proxy connection by performing a handshake operation between the client socket and a remote socket. If the handshake is successful, it submits two tasks to an executor to transfer data between the two sockets. The method does not wait for the completion of these tasks and returns.
The proxy method is a private method defined in the RunProxy class, belonging to the io.nats.client.utils package.
This method takes a Socket object named client as its parameter.
The purpose of this method is to establish a proxy connection between a client and a remote server. It starts by performing a handshake with the client using the handshake method, which returns a Socket object named remote.
If the remote socket is null, indicating a failure in the handshake process, the method returns and terminates. Otherwise, it submits two tasks to an executor (implemented as a thread pool) - one for transferring data from the remote server to the client, and another for transferring data from the client to the remote server.
These tasks are executed concurrently and run in separate threads, allowing bi-directional communication between the client and the remote server.
If an exception occurs during the handshake process or while transferring data, an error message is printed to the error stream, and the client socket is closed.

private void transferTo(Socket from, Socket to, String description) {
try {
InputStream in = from.getInputStream();
OutputStream out = to.getOutputStream();
// Really inefficient!
while (true) {
int ch = in.read();
if (ch < 0) {
return;
}
out.write(ch);
}
} catch (Exception ex) {
System.err.println("Failure to forward data (" + description + "):");
ex.printStackTrace(System.err);
}
}The transferTo method in the RunProxy class is used to transfer data from one socket to another. This method takes in three parameters: from, to, and description.
- The
fromparameter represents the socket from which the data will be read. - The
toparameter represents the socket to which the data will be written. - The
descriptionparameter is an optional string that describes the purpose of the transfer.
The method starts by creating an input stream (in) from the from socket and an output stream (out) from the to socket.
Next, there is a while loop that continues indefinitely.
Inside the loop, it reads a single character (ch) from the input stream. If the value of ch is less than 0, it means the end of the stream has been reached, so the method returns, indicating that the transfer is complete.
Otherwise, it writes the character ch to the output stream.
If any exception occurs during the transfer, it is caught and handled. The method prints an error message indicating the failure to forward the data and prints the stack trace of the exception.
Overall, the transferTo method is a simple and potentially inefficient way to transfer data from one socket to another. It reads a single character at a time from the from socket and writes it to the to socket until the end of the stream is reached.
The transferTo method, defined in the RunProxy class of the io.nats.client.utils package, is responsible for transferring data between two sockets (from and to). The method takes three parameters: from (the source socket), to (the destination socket), and description (a string describing the operation).
Inside the method, it creates input and output streams to read from the source socket and write to the destination socket, respectively. It then starts a loop to read data from the input stream byte by byte. If the read byte is less than zero, it means there is no more data to read, so the loop is exited.
Each byte read is immediately written to the output stream, effectively transferring the data from the source socket to the destination socket. This transfer process continues until there is no more data to read.
If any exception occurs during the transfer process, an error message is printed to the standard error stream, indicating the failure to forward data along with the provided description. The stack trace of the exception is also printed for debugging purposes.

private Socket handshake(Socket client) throws IOException {
String line = getLatin1Line(client.getInputStream());
Matcher matcher = REQUEST_LINE_PATTERN.matcher(line);
if (!matcher.matches()) {
System.err.println("proxy received unexpected request line=" + line);
return null;
}
if (!"CONNECT".equalsIgnoreCase(matcher.group(1))) {
System.err.println("Unsupported method='" + matcher.group(1) + "'");
client.getOutputStream().write("HTTP/1.0 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n".getBytes(UTF_8));
return null;
}
while (true) {
line = getLatin1Line(client.getInputStream());
if (null == line) {
System.err.println("Premature close...");
return null;
}
if ("".equals(line)) {
break;
}
}
String remoteHost = matcher.group(2);
int remotePort = Integer.parseInt(matcher.group(3));
Socket remote;
try {
remote = new Socket();
remote.setTcpNoDelay(true);
remote.setReceiveBufferSize(2 * 1024 * 1024);
remote.setSendBufferSize(2 * 1024 * 1024);
remote.connect(new InetSocketAddress(remoteHost, remotePort));
remote = new Socket(remoteHost, remotePort);
} catch (Exception ex) {
System.err.println("Remote connect to " + remoteHost + ":" + remotePort + " failed:");
ex.printStackTrace(System.err);
client.getOutputStream().write("HTTP/1.0 400 Bad Connection\r\nContent-Length: 0\r\n\r\n".getBytes(UTF_8));
return null;
}
client.getOutputStream().write("HTTP/1.0 200 OK\r\n\r\n".getBytes(UTF_8));
return remote;
}The handshake() method in the RunProxy class is used to establish a connection between the client and the remote server using the HTTP CONNECT method.
- The method starts by reading a line from the input stream of the
clientsocket using thegetLatin1Line()method. - It then creates a
Matcherobject by matching the regular expression pattern defined in theREQUEST_LINE_PATTERNagainst the line obtained in the previous step. - If the line does not match the pattern, an error message is printed and
nullis returned. - If the method in the HTTP request line is not "CONNECT", an error message is printed, a response with HTTP status code 405 (Method Not Allowed) is sent back to the client, and
nullis returned. - The method then enters a loop to read lines from the input stream until a blank line is encountered. These lines are not processed, they are simply skipped.
- The remote host and port are extracted from the matched groups in the regular expression pattern.
- A new
Socketobject is created and configured with TCP options and buffer sizes. - An attempt is made to connect the remote socket to the remote host and port specified.
- If the connection is unsuccessful, an error message is printed, a response with HTTP status code 400 (Bad Connection) is sent back to the client, and
nullis returned. - If the connection is successful, a response with HTTP status code 200 (OK) is sent back to the client, and the remote socket is returned.
The handshake method in the RunProxy class is a private method that takes a Socket object as input and performs a handshake process with the client.
Here is a breakdown of what the method does:
- Reads the request line from the client's input stream using the
getLatin1Linemethod. - Checks if the request line matches the expected pattern defined in the
REQUEST_LINE_PATTERN. - If the request line does not match the expected pattern, it prints an error message and returns
null. - If the request line matches but the method is not
CONNECT, it prints an error message, sends an HTTP response with status code 405 (Method Not Allowed), and returnsnull. - Reads additional lines from the client's input stream until an empty line is encountered.
- Extracts the remote host and port from the request line.
- Attempts to connect to the remote host and port using a new
Socketobject. - If the connection fails, it prints an error message, sends an HTTP response with status code 400 (Bad Connection), and returns
null. - If the connection succeeds, it sends an HTTP response with status code 200 (OK) to the client's output stream and returns the remote
Socketobject.
Note that there are several error messages logged to System.err and HTTP responses written to the client's output stream indicating the status of the handshake process.

ResourceUtils
The ResourceUtils class is an abstract class that provides utility methods for working with resources in software development. This class serves as a helper for handling various types of resources, such as files, URLs, and more. It is designed to be extended by other classes to implement specific resource-related functionality.
public static List<String> resourceAsLines(String fileName) {
try {
ClassLoader classLoader = ResourceUtils.class.getClassLoader();
File file = new File(classLoader.getResource(fileName).getFile());
return Files.readAllLines(file.toPath());
} catch (Exception e) {
throw new RuntimeException(e);
}
}This method is used to read a resource file and returns its contents as a list of strings.
- fileName: A string representing the name of the resource file to be read.
- List<String>: A list of strings containing the lines of the resource file.
-
Get the class loader of the ResourceUtils class by calling
ResourceUtils.class.getClassLoader()and assign it to a variable calledclassLoader. -
Create a new File object by calling
new File(classLoader.getResource(fileName).getFile())and assign it to a variable calledfile. This retrieves the file from the resource using the class loader. -
Call
Files.readAllLines(file.toPath())to read all the lines of the file and return them as a list of strings. -
If any exception occurs during the process, catch it and throw a new RuntimeException, wrapping the original exception.
The resourceAsLines method in the ResourceUtils class is a utility method that enables a software engineer to read the contents of a file as a list of strings.
Here's how it works:
- The method receives a
fileNameparameter, which specifies the name of the file to be read. - It obtains the
ClassLoaderfor theResourceUtilsclass. - Using the
ClassLoader, it retrieves the resource (file) with the specifiedfileName. - It creates a
Fileobject from the resource URL. - It uses the
Files.readAllLines()method to read all the lines of the file into aList<String>. - Finally, it returns the list of strings containing the lines of the file.
If the file cannot be read or if an exception occurs during the process, the method throws a RuntimeException with the appropriate error message.

public static String resourceAsString(String fileName) {
try {
ClassLoader classLoader = ResourceUtils.class.getClassLoader();
File file = new File(classLoader.getResource(fileName).getFile());
return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException(e);
}
}The method resourceAsString is defined in the class io.nats.client.utils.ResourceUtils and is responsible for converting a resource file to a string.
-
fileName: A String representing the name of the file to be converted to a string.
- Returns a String containing the contents of the resource file.
- Get the class loader of the
ResourceUtilsclass usingResourceUtils.class.getClassLoader(). - Create a
Fileobject usingclassLoader.getResource(fileName).getFile(). This will retrieve the URL of the resource file and convert it to a file path. - Read all the bytes from the file using
Files.readAllBytes(file.toPath()). TheFilesclass is part of thejava.nio.filepackage and provides methods for file I/O operations. - Convert the bytes to a string using
new String(bytes, StandardCharsets.UTF_8). TheStandardCharsets.UTF_8specifies the character encoding. - Return the string containing the contents of the resource file.
- If any exception occurs during the execution of the method, a
RuntimeExceptionis thrown with the exception as the cause.
The resourceAsString method in the ResourceUtils class is used to read the contents of a resource file as a string.
It takes a fileName parameter, which represents the name of the resource file to be read.
First, it obtains the ClassLoader for the ResourceUtils class. Then, it creates a File object by getting the resource URL using the ClassLoader and converting it to a file path.
Next, it reads all the bytes from the file path using the Files.readAllBytes() method, and converts the bytes into a string using the UTF-8 character encoding. Finally, it returns the string representation of the resource file contents.
If any exception occurs during the process, it throws a RuntimeException.
