Skip to content

Repository files navigation

Realtime Pub/Sub Client for Java

The realtime-pubsub-client is a Java client library for interacting with Realtime Pub/Sub applications. It enables developers to manage real-time WebSocket connections, handle subscriptions, and process messages efficiently. The library provides a simple and flexible API to interact with realtime applications, supporting features like publishing/sending messages, subscribing to topics, handling acknowledgments, and waiting for replies with timeout support.

Features

  • WebSocket Connection Management: Seamlessly connect and disconnect from the Realtime Pub/Sub service with automatic reconnection support.
  • Topic Subscription: Subscribe and unsubscribe to topics for receiving messages.
  • Topic Publishing: Publish messages to specific topics with optional message types and compression.
  • Message Sending: Send messages to backend applications with optional message types and compression.
  • Event Handling: Handle incoming messages with custom event listeners.
  • Acknowledgments and Replies: Wait for gateway acknowledgments or replies to messages with timeout support.
  • Error Handling: Robust error handling and logging capabilities.
  • Strongly Typed Classes: Provides strongly typed classes for a better development experience.

Installation

Add the realtime-pubsub-client library to your project via Maven or Gradle:

Maven

Add the following dependency to your pom.xml:

<dependency>
<groupId>de.backendstack21</groupId>
<artifactId>realtime-pubsub</artifactId>
<version>1.0.0</version>
</dependency>

Note: Use the version number that corresponds to the latest release.

Gradle

Add the following to your build.gradle dependencies:

implementation 'de.backendstack21:realtime-pubsub:1.0.0'

Dependencies:

  • Java WebSocket API:

    dependencies {
    // https://mvnrepository.com/artifact/jakarta.websocket/jakarta.websocket-api
    compileOnly("jakarta.websocket:jakarta.websocket-api:2.2.0")
    // https://mvnrepository.com/artifact/org.glassfish.tyrus.bundles/tyrus-standalone-client
    implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client:2.2.0")
    }
  • Jackson JSON Processor:

    dependencies { // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
    implementation("com.fasterxml.jackson.core:jackson-core:2.18.1")
    }
  • Logging Framework:

    Use Java's built-in logging or include a logging framework of your choice.

Getting Started

Connecting to the Server

First, import the RealtimeClient class and create a new instance with the required configuration:

importde.backendstack21.realtime.pubsub.RealtimeClient;
importde.backendstack21.realtime.pubsub.RealtimeClientConfig;
importde.backendstack21.realtime.pubsub.ConnectionInfo;
publicclassClientDemo {
publicstaticvoidmain(String[] args) {
// Retrieve environment variablesStringAPP_ID = System.getenv("APP_ID");
StringACCESS_TOKEN = System.getenv("ACCESS_TOKEN");
if (APP_ID == null || ACCESS_TOKEN == null) {
System.err.println("APP_ID and ACCESS_TOKEN environment variables must be set.");
return;
}
// Create the configurationRealtimeClientConfigconfig = newRealtimeClientConfig(() -> {
// Construct the WebSocket URL with the access token and app IDreturnString.format("wss://genesis.r7.21no.de/apps/%s?access_token=%s", APP_ID, ACCESS_TOKEN);
});
// Initialize the RealtimeClient with the configurationRealtimeClientclient = newRealtimeClient(config);
// Register event listener for session startedclient.on("session.started", (Object... eventArgs) -> {
ConnectionInfoconnectionInfo = (ConnectionInfo) eventArgs[0];
System.out.println("Connection ID: " + connectionInfo.getId());
// Subscribe to topics heretry {
client.subscribeRemoteTopic("topic1");
client.subscribeRemoteTopic("topic2");
// ...
} catch (Exceptione) {
e.printStackTrace();
}
});
// Connect to the WebSocket serverclient.connect();
// Wait for the session.started eventtry {
client.waitFor("session.started", 10).get();
} catch (InterruptedException | ExecutionExceptione) {
e.printStackTrace();
}
}
}

Subscribing to Incoming Messages

You can handle messages for specific topics and message types:

Note: The topic and message type are concatenated with a dot (.) in the event name.

client.on("topic1.action1", (Object... eventArgs) -> {
IncomingMessagemessage = (IncomingMessage) eventArgs[0];
// Message handling logic hereSystem.out.println("Received message: " + message.get("data"));
});

Wildcard subscriptions are also supported:

client.on("topic1.*", (Object... eventArgs) -> {
// Handle all messages in topic1
});

Concurrency Support

The underlying EventEmitter implementation uses a single thread for event handling. If you need to handle events concurrently, or block the active thread, DO use a thread pool or executor service to process events in a separate thread.

For example, you can access the client ExecutorService instance by calling the getExecutorService() method:

client.on("secure/inbound.gettime", (Object... eventArgs) -> {
varreplyFn = (ReplyFunction) eventArgs[1];
logger.info("Responding to gettime request on a separate thread...");
client.getExecutorService().submit(() -> {
try {
varresponse = Map.of("time", newDate());
// Send a reply and wait for acknowledgmentreplyFn.reply(response, "ok", false).waitForAck().get();
logger.info("Response delivered!");
} catch (Exceptione) {
logger.log(Level.SEVERE, "Failed to send reply", e);
}
});
});

Publishing Messages

Publish messages to a topic:

client.publish("topic1", "Hello, world!", "text-message");

Sending messages to your backend services:

client.send(Map.of("name", "Realtime Pub/Sub", "type": "saas"), "create");

Responding to Incoming Messages

Set up event listeners to handle incoming messages and send replies:

client.on("topic1.text-message", (Object... eventArgs) -> {
IncomingMessagemessage = (IncomingMessage) eventArgs[0];
ReplyFunctionreplyFn = (ReplyFunction) eventArgs[1];
// Message handling logic hereSystem.out.println("Received message: " + message.get("data"));
try {
replyFn.reply("Message received!", "ok", false);
} catch (Exceptione) {
e.printStackTrace();
}
});

Waiting for Acknowledgments and Replies

You can wait for acknowledgments or replies when publishing or sending messages.

  • waitForAck(int timeoutSeconds): Waits for an acknowledgment of the message, with an optional timeout in seconds.
  • waitForReply(int timeoutSeconds): Waits for a reply to the message, with an optional timeout in seconds.

Wait for the Realtime Gateway acknowledgment after publishing a message:

try {
WaitForwaitFor = client.publish("secure/peer-to-peer1", "Hi", "greeting");
waitFor.waitForAck(5).get(); // Wait for up to 5 seconds
} catch (Exceptione) {
e.printStackTrace();
}

Wait for the Realtime Gateway acknowledgment after sending a message:

try {
WaitForwaitFor = client.send("Your Message", "create");
waitFor.waitForAck(5).get();
} catch (Exceptione) {
e.printStackTrace();
}

Wait for a reply with a timeout:

try {
WaitForwaitFor = client.send(Map.of("name", "Realtime Pub/Sub", "type": "saas"), "create");
ResponseMessageresponse = (ResponseMessage) waitFor.waitForReply(5).get()[0]; // Wait for up to 5 secondsSystem.out.println("Received reply: " + response);
} catch (Exceptione) {
e.printStackTrace();
}

Error Handling

Handle errors and disconnections:

client.on("error", (Object... eventArgs) -> {
Exceptionerror = (Exception) eventArgs[0];
System.err.println("WebSocket error: " + error.getMessage());
});
client.on("close", (Object... eventArgs) -> {
CloseReasoncloseReason = (CloseReason) eventArgs[0];
System.out.println("WebSocket closed: " + closeReason.getReasonPhrase());
});

API Reference

RealtimeClient

Constructor

publicRealtimeClient(RealtimeClientConfigconfig);

Creates a new RealtimeClient instance.

  • config: Configuration options for the client encapsulated in RealtimeClientConfig.

Methods

  • getExecutorService(): Returns the ExecutorService instance used by the client.

    publicExecutorServicegetExecutorService();
  • connect(): Connects the client to the WebSocket Messaging Gateway.

    publicvoidconnect();
  • disconnect(): Terminates the WebSocket connection.

    publicvoiddisconnect();
  • subscribeRemoteTopic(String topic): Subscribes the connection to a remote topic.

    publicvoidsubscribeRemoteTopic(Stringtopic) throwsException;
  • unsubscribeRemoteTopic(String topic): Unsubscribes the connection from a remote topic.

    publicvoidunsubscribeRemoteTopic(Stringtopic) throwsException;
  • publish(String topic, Object payload, String messageType, boolean compress, String messageId): Publishes a message to a topic.

    publicWaitForpublish(Stringtopic, Objectpayload, StringmessageType, booleancompress, StringmessageId) throwsException;

    Returns a WaitFor instance to wait for acknowledgments or replies.

  • send(Object payload, String messageType, boolean compress, String messageId): Sends a message to the server.

    publicWaitForsend(Objectpayload, StringmessageType, booleancompress, StringmessageId) throwsException;

    Returns a WaitFor instance to wait for acknowledgments or replies.

  • waitFor(String eventName, int timeoutSeconds): Waits for a specific event to occur within a timeout period.

    publicCompletableFuture<Object[]> waitFor(StringeventName, inttimeoutSeconds);

    Returns a CompletableFuture that completes with the event data.

Events

  • "session.started": Emitted when the session starts.

    client.on("session.started", (Object... eventArgs) -> {
    ConnectionInfoconnectionInfo = (ConnectionInfo) eventArgs[0];
    // Handle session started
    });
  • "error": Emitted on WebSocket errors.

    client.on("error", (Object... eventArgs) -> {
    Exceptionerror = (Exception) eventArgs[0];
    // Handle error
    });
  • "close": Emitted when the WebSocket connection closes.

    client.on("close", (Object... eventArgs) -> {
    CloseReasoncloseReason = (CloseReason) eventArgs[0];
    // Handle close
    });
  • Custom Events: Handle custom events based on topic and message type.

    client.on("TOPIC_NAME.MESSAGE_TYPE", (Object... eventArgs) -> {
    IncomingMessagemessage = (IncomingMessage) eventArgs[0];
    ReplyFunctionreplyFn = (ReplyFunction) eventArgs[1];
    // Handle message and possibly send a reply
    });

    Note: Wildcard subscriptions are also supported.

License

This library is licensed under the MIT License.


For more detailed examples and advanced configurations, please refer to the documentation.

Notes

  • Ensure that you have an account and an app set up with Realtime Pub/Sub.
  • Customize the WebSocketUrlProvider function to retrieve the access token for connecting to your realtime application.
  • Implement any authentication mechanism as required by your application.
  • Optionally use a custom logger or integrate with your application's logging system.
  • Handle errors and disconnections gracefully to improve the robustness of your application.
  • Make sure to handle timeouts when waiting for replies to avoid hanging operations.
  • Include necessary dependencies in your project's build configuration.

Feel free to contribute to this project by submitting issues or pull requests on GitHub.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages