Skip to content

Commit

Permalink
Send client user-agent during connection, via CLIENT SETINFO (#2645)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
sjpotter committed Nov 7, 2023
1 parent c64ce74 commit a8b81bd
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 5 deletions.
40 changes: 40 additions & 0 deletions packages/client/lib/client/index.spec.ts
Expand Up @@ -10,6 +10,8 @@ import { once } from 'events';
import { ClientKillFilters } from '../commands/CLIENT_KILL';
import { promisify } from 'util';

import {version} from '../../package.json';

export const SQUARE_SCRIPT = defineScript({
SCRIPT: 'return ARGV[1] * ARGV[1];',
NUMBER_OF_KEYS: 0,
Expand Down Expand Up @@ -118,6 +120,44 @@ describe('Client', () => {
...GLOBAL.SERVERS.PASSWORD,
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', () => {
Expand Down
39 changes: 38 additions & 1 deletion packages/client/lib/client/index.ts
Expand Up @@ -11,11 +11,13 @@ import { ScanCommandOptions } from '../commands/SCAN';
import { HScanTuple } from '../commands/HSCAN';
import { attachCommands, attachExtensions, fCallArguments, transformCommandArguments, transformCommandReply, transformLegacyCommandArguments } from '../commander';
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 { TcpSocketConnectOpts } from 'net';
import { PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';

import {version} from '../../package.json';

export interface RedisClientOptions<
M extends RedisModules = RedisModules,
F extends RedisFunctions = RedisFunctions,
Expand Down Expand Up @@ -66,6 +68,14 @@ export interface RedisClientOptions<
* Useful with Redis deployments that do not use TCP Keep-Alive.
*/
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 = {
Expand Down Expand Up @@ -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) {
promises.push(
this.#queue.addCommand(
Expand Down
7 changes: 6 additions & 1 deletion packages/client/lib/commands/CLIENT_INFO.ts
Expand Up @@ -31,6 +31,9 @@ export interface ClientInfoReply {
user?: string; // 6.0
redir?: number; // 6.2
resp?: number; // 7.0
// 7.2
libName?: string;
libVer?: string;
}

const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g;
Expand Down Expand Up @@ -62,7 +65,9 @@ export function transformReply(rawReply: string): ClientInfoReply {
totMem: Number(map['tot-mem']),
events: map.events,
cmd: map.cmd,
user: map.user
user: map.user,
libName: map['lib-name'],
libVer: map['lib-ver'],
};

if (map.laddr !== undefined) {
Expand Down
3 changes: 2 additions & 1 deletion packages/client/lib/test-utils.ts
Expand Up @@ -4,7 +4,8 @@ import { promiseTimeout } from './utils';

const utils = new TestUtils({
dockerImageName: 'redis',
dockerImageVersionArgument: 'redis-version'
dockerImageVersionArgument: 'redis-version',
defaultDockerVersion: '7.2'
});

export default utils;
Expand Down
3 changes: 2 additions & 1 deletion packages/client/tsconfig.json
Expand Up @@ -5,7 +5,8 @@
},
"include": [
"./index.ts",
"./lib/**/*.ts"
"./lib/**/*.ts",
"./package.json"
],
"exclude": [
"./lib/test-utils.ts",
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.base.json
Expand Up @@ -4,7 +4,8 @@
"declaration": true,
"allowJs": true,
"useDefineForClassFields": true,
"esModuleInterop": false
"esModuleInterop": false,
"resolveJsonModule": true
},
"ts-node": {
"files": true
Expand Down

0 comments on commit a8b81bd

Please sign in to comment.