1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

Send client user-agent during connection, via CLIENT SETINFO (#2645)

* Add SETINFO support to client connection, with the ability to disable sending the user agent if the end user desires.  * Also enables modifying the user-agent with a tag to enable distinguishing different usages.
This commit is contained in:
Shaya Potter
2023-11-07 14:43:20 +02:00
committed by GitHub
parent c64ce74383
commit a8b81bdd01
6 changed files with 90 additions and 5 deletions

View File

@@ -10,6 +10,8 @@ import { once } from 'events';
import { ClientKillFilters } from '../commands/CLIENT_KILL'; import { ClientKillFilters } from '../commands/CLIENT_KILL';
import { promisify } from 'util'; import { promisify } from 'util';
import {version} from '../../package.json';
export const SQUARE_SCRIPT = defineScript({ export const SQUARE_SCRIPT = defineScript({
SCRIPT: 'return ARGV[1] * ARGV[1];', SCRIPT: 'return ARGV[1] * ARGV[1];',
NUMBER_OF_KEYS: 0, NUMBER_OF_KEYS: 0,
@@ -118,6 +120,44 @@ describe('Client', () => {
...GLOBAL.SERVERS.PASSWORD, ...GLOBAL.SERVERS.PASSWORD,
disableClientSetup: true disableClientSetup: true
}); });
testUtils.testWithClient('should set default lib name and version', async client => {
const clientInfo = await client.clientInfo();
assert.equal(clientInfo.libName, 'node-redis');
assert.equal(clientInfo.libVer, version);
}, {
...GLOBAL.SERVERS.PASSWORD,
minimumDockerVersion: [7, 2]
});
testUtils.testWithClient('disable sending lib name and version', async client => {
const clientInfo = await client.clientInfo();
assert.equal(clientInfo.libName, '');
assert.equal(clientInfo.libVer, '');
}, {
...GLOBAL.SERVERS.PASSWORD,
clientOptions: {
...GLOBAL.SERVERS.PASSWORD.clientOptions,
disableClientInfo: true
},
minimumDockerVersion: [7, 2]
});
testUtils.testWithClient('send client name tag', async client => {
const clientInfo = await client.clientInfo();
assert.equal(clientInfo.libName, 'node-redis(test)');
assert.equal(clientInfo.libVer, version);
}, {
...GLOBAL.SERVERS.PASSWORD,
clientOptions: {
...GLOBAL.SERVERS.PASSWORD.clientOptions,
clientInfoTag: "test"
},
minimumDockerVersion: [7, 2]
});
}); });
describe('authentication', () => { describe('authentication', () => {

View File

@@ -11,11 +11,13 @@ import { ScanCommandOptions } from '../commands/SCAN';
import { HScanTuple } from '../commands/HSCAN'; import { HScanTuple } from '../commands/HSCAN';
import { attachCommands, attachExtensions, fCallArguments, transformCommandArguments, transformCommandReply, transformLegacyCommandArguments } from '../commander'; import { attachCommands, attachExtensions, fCallArguments, transformCommandArguments, transformCommandReply, transformLegacyCommandArguments } from '../commander';
import { Pool, Options as PoolOptions, createPool } from 'generic-pool'; import { Pool, Options as PoolOptions, createPool } from 'generic-pool';
import { ClientClosedError, ClientOfflineError, DisconnectsClientError } from '../errors'; import { ClientClosedError, ClientOfflineError, DisconnectsClientError, ErrorReply } from '../errors';
import { URL } from 'url'; import { URL } from 'url';
import { TcpSocketConnectOpts } from 'net'; import { TcpSocketConnectOpts } from 'net';
import { PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub'; import { PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';
import {version} from '../../package.json';
export interface RedisClientOptions< export interface RedisClientOptions<
M extends RedisModules = RedisModules, M extends RedisModules = RedisModules,
F extends RedisFunctions = RedisFunctions, F extends RedisFunctions = RedisFunctions,
@@ -66,6 +68,14 @@ export interface RedisClientOptions<
* Useful with Redis deployments that do not use TCP Keep-Alive. * Useful with Redis deployments that do not use TCP Keep-Alive.
*/ */
pingInterval?: number; pingInterval?: number;
/**
* If set to true, disables sending client identifier (user-agent like message) to the redis server
*/
disableClientInfo?: boolean;
/**
* Tag to append to library name that is sent to the Redis server
*/
clientInfoTag?: string;
} }
type WithCommands = { type WithCommands = {
@@ -274,6 +284,33 @@ export default class RedisClient<
); );
} }
if (!this.#options?.disableClientInfo) {
promises.push(
this.#queue.addCommand(
[ 'CLIENT', 'SETINFO', 'LIB-VER', version],
{ asap: true }
).catch(err => {
if (!(err instanceof ErrorReply)) {
throw err;
}
})
);
promises.push(
this.#queue.addCommand(
[
'CLIENT', 'SETINFO', 'LIB-NAME',
this.#options?.clientInfoTag ? `node-redis(${this.#options.clientInfoTag})` : 'node-redis'
],
{ asap: true }
).catch(err => {
if (!(err instanceof ErrorReply)) {
throw err;
}
})
);
}
if (this.#options?.name) { if (this.#options?.name) {
promises.push( promises.push(
this.#queue.addCommand( this.#queue.addCommand(

View File

@@ -31,6 +31,9 @@ export interface ClientInfoReply {
user?: string; // 6.0 user?: string; // 6.0
redir?: number; // 6.2 redir?: number; // 6.2
resp?: number; // 7.0 resp?: number; // 7.0
// 7.2
libName?: string;
libVer?: string;
} }
const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g; const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g;
@@ -62,7 +65,9 @@ export function transformReply(rawReply: string): ClientInfoReply {
totMem: Number(map['tot-mem']), totMem: Number(map['tot-mem']),
events: map.events, events: map.events,
cmd: map.cmd, cmd: map.cmd,
user: map.user user: map.user,
libName: map['lib-name'],
libVer: map['lib-ver'],
}; };
if (map.laddr !== undefined) { if (map.laddr !== undefined) {

View File

@@ -4,7 +4,8 @@ import { promiseTimeout } from './utils';
const utils = new TestUtils({ const utils = new TestUtils({
dockerImageName: 'redis', dockerImageName: 'redis',
dockerImageVersionArgument: 'redis-version' dockerImageVersionArgument: 'redis-version',
defaultDockerVersion: '7.2'
}); });
export default utils; export default utils;

View File

@@ -5,7 +5,8 @@
}, },
"include": [ "include": [
"./index.ts", "./index.ts",
"./lib/**/*.ts" "./lib/**/*.ts",
"./package.json"
], ],
"exclude": [ "exclude": [
"./lib/test-utils.ts", "./lib/test-utils.ts",

View File

@@ -4,7 +4,8 @@
"declaration": true, "declaration": true,
"allowJs": true, "allowJs": true,
"useDefineForClassFields": true, "useDefineForClassFields": true,
"esModuleInterop": false "esModuleInterop": false,
"resolveJsonModule": true
}, },
"ts-node": { "ts-node": {
"files": true "files": true