It allows HTTP clients to create channels and send push messages to WebSocket clients that are subscribed to the same channel.
Introduction • Setup • Usage • Dependencies • License
A push architecture is used in applications in which the server has to send messages / data to the client without the client having to request it. This is particularly useful for applications that require real-time data, such as chat applications or stock market applications.
However, it is difficult to achieve this with HTTP for web applications as it is primarily a request-response protocol.
WebSocket connections are a solution to this problem. They allow a persistent connection between client and server, which can be used to send messages in both directions.
This project implements a Push-Server that uses WebSockets to create a push architecture that can be used for an unlimited amount of projects.
HTTP clients - let's call them publishers - can create channels where they can later send messages in. One channel should only be used for one project, so that the messages are only sent to the clients of this project.
WebSocket clients - also called subscribers - can subscribe (or unsubscribe) to (from) an unlimited amount of channels once they've established a WebSocket connection. They'll receive all messages that are sent to the channels they're subscribed to - and only those. Publishers can also address a message to a specific list of subscribers. Only those subscribers will receive the message - and only if they've subscribed to the channel.
For further information on how to use the Push-Server, see Usage.
This project is dockerized so you can easily run it in a container. It's recommended to use the pre-built image from the GitHub container registry - however you are also free to build the image yourself. Please follow the instructions below to get started.
- Clone the repository on the target environment
git clone https://github.com/JensOstertag/WS-PushServer.git pushserver
- Configure the
docker-compose.yml
file- Change the ports if you want to use different ones from
5222
and5223
- Àdd a custom network mode (depending on your existing setup)
- Change the ports if you want to use different ones from
- Start the container with docker compose
docker compose up -d
For the following examples, the server should be reachable on localhost
, the WebSocket service should be available on port 5222
and the HTTP service should be available on port 5223
.
You can change these ports when starting the docker container, in which case you have to adjust the parameters that are used in the examples below.
Publishers can create channels and send messages to the subscribers of these channels.
Create channels
You can create a channel by sending a POST
request to the /channel/create
route:
POST /channel/create HTTP/1.1
Host: localhost:5223
{
"messageType": "SERVER_ACTION",
"action": "CREATE_CHANNEL",
"data": {
"channel": "channelName"
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that you want to create |
Response - Success
You'll receive a JSON response when the channel was created successfully:
{
"messageType": "SERVER_ACK",
"code": 200,
"message": "Created",
"data": {
"channel": "channelName",
"channelToken": "CHANNEL_TOKEN"
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that was created |
data.channelToken |
The token that has to be used to send messages to the channel's subscribers |
The channel token needs to be saved in a secure place. You'll need it every time you want to perform an action on the channel, such as sending a message to its subscribers.
Response - Error
If there was an error whilst creating the channel, you'll receive the following response:
{
"messageType": "ERROR",
"code": 409,
"message": "Message",
"data": {
"errorDetails": "Details"
}
}
Field | Description |
---|---|
message |
Short description of what happened |
data.errorDetails |
Details about the error that occurred |
Ping channels
Channels can be pinged to check whether they exist or to get information about it.
POST /channel/ping HTTP/1.1
Host: localhost:5223
{
"messageType": "SERVER_ACTION",
"action": "PING_CHANNEL",
"data": {
"channel": "channelName",
"channelToken": "CHANNEL_TOKEN"
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that you want to ping |
data.channelToken |
The channel token or null if you only want to know whether the channel exists |
Response - Success
If the channel exists and the specified channel token is correct, you'll receive a response which looks like this:
{
"messageType": "SERVER_ACK",
"code": 200,
"message": "OK",
"data": {
"channel": "channelName",
"channelToken": null
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that was pinged |
data.channelToken |
Always null (it's only present to comply with the JSON schema) |
Response - Error
If there was an error whilst pinging the channel, the response will look like this:
{
"messageType": "ERROR",
"code": 0,
"message": "Message",
"data": {
"errorDetails": "Details"
}
}
Field | Description |
---|---|
message |
Short description of what happened |
data.errorDetails |
Details about the error that occurred |
Send messages
Messages can be sent to all subscribers of a channel or to a list of specific subscribers.
POST /push HTTP/1.1
Host: localhost:5223
{
"messageType": "SERVER_ACTION",
"action": "PUSH_MESSAGE",
"data": {
"channel": "channelName",
"channelToken": "channelToken",
"recipients": [],
"message": "message"
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that you want to send the message in |
data.channelToken |
The channel token that you received when creating the channel |
data.recipients |
A list of UUIDs of recipients that should receive the message If this list is empty, the message will be sent to all subscribers of the channel |
data.message |
The message that should be sent |
The recipients can be specified in order to create a private messaging system. As this requires specification of the client's UUID, clients have to send an API call (or something comparable) to the backend of your application with their UUID upon subscribing to a channel.
Response - Success
In case of a successful message push, you'll receive the following response:
{
"messageType": "SERVER_ACK",
"code": 200,
"message": "Sent",
"data": {
"channel": "channelName",
"channelToken": null
}
}
Field | Description |
---|---|
data.channel |
The name of the channel in which the message was sent |
data.channelToken |
Always null (it's only present to comply with the JSON schema) |
Response - Error
If there was an error whilst sending the message, the response will look like this:
{
"messageType": "ERROR",
"code": 0,
"message": "Message",
"data": {
"errorDetails": "Details"
}
}
Field | Description |
---|---|
message |
Short description of what happened |
data.errorDetails |
Details about the error that occurred |
Delete channels
It's a good practice to delete channels that are no longer in use.
This can be done by sending a POST
request to the /channel/delete
route:
POST /channel/delete HTTP/1.1
{
"messageType": "SERVER_ACTION",
"action": "DELETE_CHANNEL",
"data": {
"channel": "channelName",
"channelToken": "channelToken"
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that you want to delete |
data.channelToken |
The channel token that you received when creating the channel |
Response - Success
If the channel was deleted successfully, you'll receive the following response:
{
"messageType": "SERVER_ACK",
"code": 200,
"message": "Deleted",
"data": {
"channel": "channelName",
"channelToken": null
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that was deleted |
data.channelToken |
Always null (it's only present to comply with the JSON schema) |
Response - Error
If there was an error whilst deleting the channel, the response will look like this:
{
"messageType": "ERROR",
"code": 0,
"message": "Message",
"data": {
"errorDetails": "Details"
}
}
Field | Description |
---|---|
message |
Short description of what happened |
data.errorDetails |
Details about the error that occurred |
Subscribers can subscribe to channels and receive messages that are sent to these channels.
Connection
To connect to the Push-Server, a client has to establish a WebSocket connection to ws://localhost:5222
.
The client will automatically receive a message from the server:
{
"messageType": "CLIENT_ACK",
"code": 200,
"message": "Connected",
"data": {
"uuid": "clientUuid",
"subscribedChannels": []
}
}
Field | Description |
---|---|
data.uuid |
The UUID of the client that was generated by the server It is used by publishers to send a message to specific clients |
data.subscribedChannels |
A list of channels that the client is subscribed to Initially empty |
Subscribing to channels
Before a client can receive messages, he has to subscribe to a channel. This can be done by sending a message to the server:
{
"messageType": "CLIENT_ACTION",
"action": "SUBSCRIBE",
"data": {
"channel": "channelName"
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that should be subscribed |
Response - Success
If the channel was subscribed successfully, the client will receive the following response:
{
"messageType": "CLIENT_ACK",
"code": 200,
"message": "Connected",
"data": {
"uuid": "clientUuid",
"subscribedChannels": [
"channelName"
]
}
}
Field | Description |
---|---|
data.uuid |
The UUID of the client that was generated by the server |
data.subscribedChannels |
A list of channels that the client is subscribed to The channel that was subscribed to is now in this list |
Unsubscribing from channels
If a client wishes to no longer receive messages from a specific channel, he can unsubscribe from that channel by sending a message to the server:
{
"messageType": "CLIENT_ACTION",
"action": "UNSUBSCRIBE",
"data": {
"channel": "channelName"
}
}
Field | Description |
---|---|
data.channel |
The name of the channel that should be unsubscribed |
Response - Success
If the client unsubscribed from the channel successfully, the client will receive the following response:
{
"messageType": "CLIENT_ACK",
"code": 200,
"message": "Connected",
"data": {
"uuid": "clientUuid",
"subscribedChannels": []
}
}
Field | Description |
---|---|
data.uuid |
The UUID of the client that was generated by the server |
data.subscribedChannels |
A list of channels that the client is subscribed to The channel that was unsubscribed is no longer in this list |
Receiving messages
Messages are sent to the client in JSON format:
{
"messageType": "CLIENT_PUSH",
"code": 200,
"message": "Push Message",
"data": {
"uuid": "clientUuid",
"subscribedChannels": [
"channelName"
],
"pushMessage": {
"channel": "channelName",
"message": "Message"
}
}
}
Field | Description |
---|---|
data.uuid |
The client's UUID |
data.subscribedChannels |
The list of channels that the client is subscribed to |
data.pushMessage.channel |
The name of the channel that the message was sent on |
data.pushMessage.message |
The raw message that was sent on the channel |
- Java
- Java-WebSocket v1.5.4 - GitHub: TooTallNate/Java-WebSocket, licensed under MIT License
- Gson v2.10.1 - GitHub: google/gson, licensed under Apache License 2.0
- Json-Schema-Validator v1.0.87 - GitHub: networknt/json-schema-validator, licensed under Apache License 2.0
- TestNG - GitHub: cbeust/testng, licensed under Apache License 2.0
- Maven - GitHub: apache/maven, licensed under Apache License 2.0
- Docker
- maven 3.8.7-openjdk-21-slim - DockerHub: maven, licensed under Apache License 2.0
- openjdk 21-jdk-slim - DockerHub: openjdk, licensed under GPLv2
- GitHub Actions
- actions/checkout v3 - GitHub: actions/checkout, licensed under MIT License
- docker/login-action v1 - GitHub: docker/login-action, licensed under Apache License 2.0
This project is licensed under the MIT License.