Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancement/websockets #614

Open
wants to merge 4 commits into
base: enhancement/options-type-validations
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,15 @@ DEBUG_LISTEN_TO_CONSOLE = false
DEBUG_DUMPIO = false
DEBUG_SLOW_MO = 0
DEBUG_DEBUGGING_PORT = 9222

# WEBSOCKET CONFIG
WEB_SOCKET_ENABLE = false
WEB_SOCKET_RECONNECT = false
WEB_SOCKET_REJECT_UNAUTHORIZED = false
WEB_SOCKET_PING_TIMEOUT = 16000
WEB_SOCKET_RECONNECT_INTERVAL = 3000
WEB_SOCKET_RECONNECT_ATTEMPTS = 3
WEB_SOCKET_MESSAGE_INTERVAL = 3600000
WEB_SOCKET_GATHER_ALL_OPTIONS = false
WEB_SOCKET_URL =
WEB_SOCKET_SECRET =
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,17 @@ _Available default JSON config:_
"dumpio": false,
"slowMo": 0,
"debuggingPort": 9222
},
"webSocket": {
"enable": false,
"reconnect": false,
"rejectUnauthorized": false,
"pingTimeout": 16000,
"reconnectInterval": 3000,
"reconnectAttempts": 3,
"messageInterval": 3600000,
"gatherAllOptions": false,
"url": null
}
}
```
Expand Down Expand Up @@ -418,6 +429,19 @@ _Available environment variables:_
- `DEBUG_SLOW_MO`: Slows down Puppeteer operations by the specified number of milliseconds (defaults to `0`).
- `DEBUG_DEBUGGING_PORT`: Specifies the debugging port (defaults to `9222`).

### WebSocket Config

- `WEB_SOCKET_ENABLE`: Enables or disables the WebSocket connection (defaults to `false`).
- `WEB_SOCKET_RECONNECT`: Controls whether or not to try reconnecting to the WebSocket server in case of a disconnect (defaults to `false`).
- `WEB_SOCKET_REJECT_UNAUTHORIZED`: Determines whether the client verifies the server's SSL/TLS certificate during the handshake process (defaults to `false`).
- `WEB_SOCKET_PING_TIMEOUT`: The timeout, in milliseconds, for the heartbeat mechanism between the client and server (defaults to `16000`).
- `WEB_SOCKET_RECONNECT_INTERVAL`: The interval, in milliseconds, for the reconnect attempt (defaults to `3000`).
- `WEB_SOCKET_RECONNECT_ATTEMPTS`: The number of reconnect attempts before returning a connection error (defaults to `3`).
- `WEB_SOCKET_MESSAGE_INTERVAL`: The interval, in milliseconds, for auto sending the data through a WebSocket connection (defaults to `3600000`).
- `WEB_SOCKET_GATHER_ALL_OPTIONS`: Decides whether or not to gather all chart's options or only ones defined in the **telemetry.json** file (defaults to `false`).
- `WEB_SOCKET_URL`: The URL of the WebSocket server (defaults to ``).
- `WEB_SOCKET_SECRET`: The secret used to create a JSON Web Token sent to the WebSocket server (defaults to ``).

## Command Line Arguments

To supply command line arguments, add them as flags when running the application:
Expand Down Expand Up @@ -548,6 +572,18 @@ _Available CLI arguments:_
- `--slowMo`: Slows down Puppeteer operations by the specified number of milliseconds (defaults to `0`).
- `--debuggingPort`: Specifies the debugging port (defaults to `9222`).

### WebSocket Config

- `--enableWs`: Enables or disables the WebSocket connection (defaults to `false`).
- `--wsReconnect`: Controls whether or not to try reconnecting to the WebSocket server in case of a disconnect (defaults to `false`).
- `--wsRejectUnauthorized`: Determines whether the client verifies the server's SSL/TLS certificate during the handshake process (defaults to `false`).
- `--wsPingTimeout`: The timeout, in milliseconds, for the heartbeat mechanism between the client and server (defaults to `16000`).
- `--wsReconnectInterval`: The interval, in milliseconds, for the reconnect attempt (defaults to `3000`).
- `--wsReconnectAttempts`: The number of reconnect attempts before returning a connection error (defaults to `3`).
- `--wsMessageInterval`: The interval, in milliseconds, for auto sending the data through a WebSocket connection (defaults to `3600000`).
- `--wsGatherAllOptions`: Decides whether or not to gather all chart's options or only ones defined in the **telemetry.json** file (defaults to `false`).
- `--wsUrl`: The URL of the WebSocket server (defaults to `null`).

# HTTP Server

Apart from using as a CLI tool, which allows you to run one command at a time, it is also possible to configure the server to accept POST requests. The simplest way to enable the server is to run the command below:
Expand Down Expand Up @@ -831,6 +867,30 @@ This package supports both CommonJS and ES modules.

Samples and tests for every mentioned export method can be found in the `./samples` and `./tests` folders. Detailed descriptions are available in their corresponding sections on the [Wiki](https://github.com/highcharts/node-export-server/wiki).

# WebSocket

One of the new features that v4 introduces is the ability to configure and establish a WebSocket connection between the Export Server and a user-configured WebSocket server. This can be useful for gathering telemetry data and statistics about the usage of your Export Server instance.

## How It Works

When enabled, a WebSocket connection will be established on Export Server startup. The WebSocket server to which the connection is made must also be configured. The authorization process is completed by sending a JWT generated based on a secret that must also be set on the WebSocket server side.

Once the connection is established, the chart data from each request to the Export Server is passed to the telemetry module, which filters the data based on a JSON file that specifies which data needs to be sent. This file can be found under `./lib/schemas/telemetry.json` and can be modified as needed, but the proposed structure must be maintained. It is also possible to send the entire object of chart options by setting the `gatherAllOptions` option to **true**.

Data processed in this way is saved in an object that collects information about requests for a certain period and is batch sent to the WebSocket server at specified intervals. After sending, the object is cleared of request data and gathers new data until the next interval.

Please refer to the [Configuration](https://github.com/highcharts/node-export-server?tab=readme-ov-file#configuration) section for descriptions of the options.

## Additional Notes

- In order for the heartbeat mechanism between the WebSocket client and server to work correctly, the `pingTimeout` should be set to a higher value in milliseconds than its equivalent in the WebSocket server.

- Setting **0** for any of the interval/timeout-related options (`pingTimeout`, `reconnectInterval`, or `messageInterval`) will disable that option. Bear in mind, however, that disabling `pingTimeout` might result in WebSocket clients not being terminated if no response is received from the server.

- Disabling `pingTimeout` will also disable the reconnect mechanism.

- When using a self-signed certificate (for example, for testing purposes), the `rejectUnauthorized` option should be disabled. Otherwise, it will result in an error and the connection to the WebSocket server will fail.

# Tips, Tricks & Notes

## Note About Version And Help Information
Expand Down
4 changes: 4 additions & 0 deletions lib/resourceRelease.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ See LICENSE file in root for details.
import { killPool } from './pool.js';
import { clearAllTimers } from './timer.js';
import { closeServers } from './server/server.js';
import { terminateClients } from './server/webSocket.js';

/**
* Clean up function to trigger before ending process for the graceful shutdown.
Expand All @@ -34,6 +35,9 @@ export async function shutdownCleanUp(exitCode) {
// Clear all ongoing intervals
clearAllTimers(),

// Terminate all connected WebSocket clients
terminateClients(),

// Get available server instances (HTTP/HTTPS) and close them
closeServers(),

Expand Down
97 changes: 97 additions & 0 deletions lib/schemas/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,103 @@ export const defaultConfig = {
type: 'number'
}
}
},
webSocket: {
enable: {
value: false,
type: ['boolean'],
envLink: 'WEB_SOCKET_ENABLE',
cliName: 'enableWs',
description: 'Enables or disables the WebSocket connection',
promptOptions: {
type: 'toggle'
}
},
reconnect: {
value: false,
type: ['boolean'],
envLink: 'WEB_SOCKET_RECONNECT',
cliName: 'wsReconnect',
description:
'Whether or not to attempt to reconnect to the WebSocket server if disconnected',
promptOptions: {
type: 'toggle'
}
},
rejectUnauthorized: {
value: false,
type: ['boolean'],
envLink: 'WEB_SOCKET_REJECT_UNAUTHORIZED',
cliName: 'wsRejectUnauthorized',
description:
"Whether or not to client should verify the server's SSL/TLS certificate during the handshake",
promptOptions: {
type: 'toggle'
}
},
pingTimeout: {
value: 16000,
type: ['number'],
envLink: 'WEB_SOCKET_PING_TIMEOUT',
cliName: 'wsPingTimeout',
description:
'Timeout in milliseconds for the heartbeat mechanism between client and server',
promptOptions: {
type: 'number'
}
},
reconnectInterval: {
value: 3000,
type: ['number'],
envLink: 'WEB_SOCKET_RECONNECT_INTERVAL',
cliName: 'wsReconnectInterval',
description: 'Interval in milliseconds between reconnect attempts',
promptOptions: {
type: 'number'
}
},
reconnectAttempts: {
value: 3,
type: ['number'],
envLink: 'WEB_SOCKET_RECONNECT_ATTEMPTS',
cliName: 'wsReconnectAttempts',
description: 'Number of attempts to reconnect before reporting an error',
promptOptions: {
type: 'number'
}
},
messageInterval: {
value: 3600000,
type: ['number'],
envLink: 'WEB_SOCKET_MESSAGE_INTERVAL',
cliName: 'wsMessageInterval',
description:
'Interval in milliseconds for automatically sending data through the WebSocket connection',
promptOptions: {
type: 'number'
}
},
gatherAllOptions: {
value: false,
type: ['boolean'],
envLink: 'WEB_SOCKET_GATHER_ALL_OPTIONS',
cliName: 'wsGatherAllOptions',
description:
'Whether or not to gather all chart options or only those defined in the telemetry.json file',
promptOptions: {
type: 'toggle'
}
},
url: {
value: null,
type: ['string', 'null'],
envLink: 'WEB_SOCKET_URL',
cliName: 'wsUrl',
description: 'URL of the WebSocket server',
promptOptions: {
type: 'text'
}
}
}
};

Expand Down
22 changes: 22 additions & 0 deletions lib/schemas/telemetry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"boost": null,
"chart": {
"type": null,
"options3d": {
"enabled": null
}
},
"colors": null,
"legend": {
"enabled": null
},
"series": {
"type": null
},
"xAxis": {
"type": null
},
"yAxis": {
"type": null
}
}
7 changes: 7 additions & 0 deletions lib/server/routes/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ See LICENSE file in root for details.
import { startExport } from '../../chart.js';
import { getOptions } from '../../config.js';
import { log } from '../../logger.js';
import { prepareTelemetry } from '../../telemetry.js';
import { measureTime, mergeConfigOptions } from '../../utils.js';

import NoCorrectResultError from '../../errors/NoCorrectResultError.js';
Expand Down Expand Up @@ -119,6 +120,12 @@ async function requestExport(request, response, next) {
throw NoCorrectResultError();
}

// Telemetry only for the options based request
if (!options.export.svg) {
// Prepare and send the options through the WebSocket
prepareTelemetry(options.export.options, options.payload.requestId);
}

// Return the result in an appropriate format
if (data.result) {
// Get the type from options
Expand Down
11 changes: 11 additions & 0 deletions lib/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import multer from 'multer';
import { getOptions } from '../config.js';
import { log, logWithStack } from '../logger.js';
import { rateLimiting } from './rateLimiting.js';
import { webSocketInit } from './webSocket.js';
import { __dirname } from '../utils.js';

import errorMiddleware from './middlewares/error.js';
Expand Down Expand Up @@ -142,6 +143,11 @@ export async function startServer(serverOptions = getOptions().server) {
3,
`[server] Started HTTP server on ${serverOptions.host}:${serverOptions.port}.`
);

if (activeServers.size === 1) {
// Start a WebSocket connection
webSocketInit({ ...httpServer.address(), protocol: 'http' });
}
});
}

Expand Down Expand Up @@ -185,6 +191,11 @@ export async function startServer(serverOptions = getOptions().server) {
3,
`[server] Started HTTPS server on ${serverOptions.host}:${serverOptions.ssl.port}.`
);

if (activeServers.size === 1) {
// Start a WebSocket connection
webSocketInit({ ...httpsServer.address(), protocol: 'https' });
}
});
}
}
Expand Down
Loading
Loading