Skip to content

Commit

Permalink
Roundups
Browse files Browse the repository at this point in the history
  • Loading branch information
oklemenz2 committed Dec 21, 2023
1 parent 6b6c8c4 commit 140a226
Show file tree
Hide file tree
Showing 9 changed files with 416 additions and 136 deletions.
56 changes: 42 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ WebSocket server options can be provided via `cds.requires.websocket.options`.

Default protocol path is `/ws` and can be overwritten via `cds.env.protocols.websocket.path` resp. `cds.env.protocols.ws.path`;

Services are exposed therefore like this: `/ws/<service-path`:
Services are exposed therefore like this: `/ws/<service-path>`:

**Examples:**

Expand Down Expand Up @@ -93,6 +93,14 @@ Locally, the following default environment files need to exist:
- `test/\_env/default-env.json`
- `test/\_env/approuter/default-services.json`

Approuter is configured to support websockets in `xs-app.json` according to [@sap/approuter - websockets property](https://www.npmjs.com/package/@sap/approuter#websockets-property):

```
"websockets": {
"enabled": true
}
```

#### Local

For local testing a mocked basic authorization is hardcoded in `flp.html/index.html`.
Expand Down Expand Up @@ -278,43 +286,63 @@ describe("WebSocket", () => {
### Adapters (Socket.IO)

An Adapter is a server-side component which is responsible for broadcasting events to all or a subset of clients.
Details can be found here: https://socket.io/docs/v4/adapter/

Currently, two adapters are supported out-of-the-box:

#### Redis

Every event that is sent to multiple clients is sent to all matching clients connected to the current server
and published in a Redis channel, and received by the other Socket.IO servers of the cluster.

##### Redis Adapter
Redis adapter can be disabled by setting `cds.requires.websocket.adapter: false`.

##### WS

Per default a basic publish/subscribe redis adapter is active, if the app is bound to a Redis service.
The Redis channel key can be specified via `cds.requires.websocket.adapter.options.key`. Default value is `websocket`.

##### Socket.IO

To use the redis adapter, the following steps have to be performed:
The following Redis adapters for Socket.IO are supported out-of-the-box.

###### Redis Adapter

To use the Redis Adapter, the following steps have to be performed:

- Install Redis Adapter dependency:
`npm install @socket.io/redis-adapter`
- Set `cds.requires.websocket.adapter: "@socket.io/redis-adapter"`
- Set `cds.requires.websocket.adapter.impl: "@socket.io/redis-adapter"`
- Application needs to be bound to a Redis instance
- Locally a `default-env.json` file need to exist with redis configuration
- Redis adapter options can be specified via `cds.requires.websocket.adapter.options`;
- Redis Adapter options can be specified via `cds.requires.websocket.adapter.options`

Details:
https://socket.io/docs/v4/redis-adapter/

##### Redis Stream Adapter
###### Redis Streams Adapter

To use the redis adapter, the following steps have to be performed:
To use the Redis Stream Adapter, the following steps have to be performed:

- Install Redis Adapter dependency:
`npm install @socket.io/redis-adapter`
- Set `cds.requires.websocket.adapter: "@socket.io/redis-streams-adapter"`
- Install Redis Streams Adapter dependency:
`npm install @socket.io/redis-streams-adapter`
- Set `cds.requires.websocket.adapter.impl: "@socket.io/redis-streams-adapter"`
- Application needs to be bound to a Redis instance
- Locally a `default-env.json` file need to exist with redis configuration
- Redis adapter options can be specified via `cds.requires.websocket.adapter.options`;
- Redis Streams Adapter options can be specified via `cds.requires.websocket.adapter.options`

Details:
https://socket.io/docs/v4/redis-streams-adapter/

### Deployment

This module also works on a deployed infrastructure like Cloud Foundry (CF) or Kubernetes (K8s).

An example Cloud Foundry deployment can be found in `test/_env`:
- `cd test/_env`
- [manifest.yml](test/_env/manifest.yml)
- `npm run cf:push`
- Prepares modules `approuter` and `backend` in `test/_env` and pushes to Cloud Foundry
- Approuter performs authentication flow with XSUAA and forwards to backend
- Backend serves endpoints (websocket, odata) and UI apps (runs on an in-memory SQlite3 database)

## Support, Feedback, Contributing

This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/cap-js-community/websocket/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"start:socketio": "npm run start:socketio --prefix=./test/_env",
"start:uaa": "npm run start:uaa --prefix=./test/_env",
"start:socketio:uaa": "npm run start:socketio:uaa --prefix=./test/_env",
"start:approuter": "npm start --prefix=./test/_env/approuter",
"start:approuter": "PORT=5001 npm start --prefix=./test/_env/approuter",
"test": "jest",
"test:update": "npm test -- -u",
"lint": "npm run eslint && npm run prettier",
Expand Down
3 changes: 3 additions & 0 deletions src/socket/socket.io.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class SocketIOServer extends SocketServer {

async function applyAdapter() {
try {
if (cds.env.requires.websocket?.adapter === false) {
return;
}
const adapterImpl = cds.env.requires?.websocket?.adapter?.impl;
if (adapterImpl) {
const { createAdapter } = require(adapterImpl);
Expand Down
28 changes: 17 additions & 11 deletions src/socket/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ class SocketWSServer extends SocketServer {
LOG?.error(error);
});
applyMiddleware(ws, request, async () => {
const redisClient = await redis.createMainClientAndConnect();
const channel = cds.env.requires.websocket?.adapter?.options?.key ?? "websocket";
subscribeRedis(redisClient, ws, channel);
const adapterActive = cds.env.requires.websocket?.adapter !== false;
const redisChannel = cds.env.requires.websocket?.adapter?.options?.key ?? "websocket";
let redisClient;
if (adapterActive) {
redisClient = await redis.createMainClientAndConnect();
subscribeRedis(redisClient, ws, redisChannel);
}
connected &&
connected({
socket: ws,
Expand All @@ -66,7 +70,9 @@ class SocketWSServer extends SocketServer {
// ignore
}
if (payload?.event === event) {
publishRedis(redisClient, ws, channel, message);
if (adapterActive) {
publishRedis(redisClient, ws, redisChannel, message);
}
callback(payload.data);
}
});
Expand Down Expand Up @@ -126,20 +132,20 @@ function applyMiddleware(ws, request, next) {
apply();
}

function subscribeRedis(redisClient, ws, channel) {
if (redisClient) {
redisClient.subscribe(channel);
redisClient.on("message", (onChannel, message) => {
function subscribeRedis(client, ws, channel) {
if (client) {
client.subscribe(channel);
client.on("message", (onChannel, message) => {
if (onChannel === channel) {
ws.send(message);
}
});
}
}

function publishRedis(redisClient, ws, channel, message) {
if (redisClient) {
redisClient.publish(channel, message);
function publishRedis(client, ws, channel, message) {
if (client) {
client.publish(channel, message);
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/_env/approuter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@sap/approuter": "^15.0.0"
},
"scripts": {
"start": "PORT=5001 node node_modules/@sap/approuter/approuter.js"
"start": "node node_modules/@sap/approuter/approuter.js"
},
"engines": {
"node": ">=20"
Expand Down
3 changes: 2 additions & 1 deletion test/_env/approuter/xs-app.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
{
"source": ".*",
"destination": "srv",
"csrfProtection": false
"csrfProtection": false,
"authenticationType": "xsuaa"
}
]
}
Loading

0 comments on commit 140a226

Please sign in to comment.