You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
fix(client): bring disableClientInfo option back (#2959)
* fix(client): bring disableClientInfo option back It disappeared in v5 fixes #2958
This commit is contained in:
committed by
GitHub
parent
f3d1d3352e
commit
4a5f879ec9
@@ -4,11 +4,11 @@ import { BasicAuth, CredentialsError, CredentialsProvider, StreamingCredentialsP
|
|||||||
import RedisCommandsQueue, { CommandOptions } from './commands-queue';
|
import RedisCommandsQueue, { CommandOptions } from './commands-queue';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
|
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
|
||||||
import { ClientClosedError, ClientOfflineError, DisconnectsClientError, WatchError } from '../errors';
|
import { ClientClosedError, ClientOfflineError, DisconnectsClientError, SimpleError, WatchError } from '../errors';
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import { TcpSocketConnectOpts } from 'node:net';
|
import { TcpSocketConnectOpts } from 'node:net';
|
||||||
import { PUBSUB_TYPE, PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';
|
import { PUBSUB_TYPE, PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';
|
||||||
import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply } from '../RESP/types';
|
import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply, CommandArguments } from '../RESP/types';
|
||||||
import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command';
|
import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command';
|
||||||
import { RedisMultiQueuedCommand } from '../multi-command';
|
import { RedisMultiQueuedCommand } from '../multi-command';
|
||||||
import HELLO, { HelloOptions } from '../commands/HELLO';
|
import HELLO, { HelloOptions } from '../commands/HELLO';
|
||||||
@@ -19,6 +19,7 @@ import { RedisVariadicArgument, parseArgs, pushVariadicArguments } from '../comm
|
|||||||
import { BasicClientSideCache, ClientSideCacheConfig, ClientSideCacheProvider } from './cache';
|
import { BasicClientSideCache, ClientSideCacheConfig, ClientSideCacheProvider } from './cache';
|
||||||
import { BasicCommandParser, CommandParser } from './parser';
|
import { BasicCommandParser, CommandParser } from './parser';
|
||||||
import SingleEntryCache from '../single-entry-cache';
|
import SingleEntryCache from '../single-entry-cache';
|
||||||
|
import { version } from '../../package.json'
|
||||||
|
|
||||||
export interface RedisClientOptions<
|
export interface RedisClientOptions<
|
||||||
M extends RedisModules = RedisModules,
|
M extends RedisModules = RedisModules,
|
||||||
@@ -135,6 +136,14 @@ export interface RedisClientOptions<
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
clientSideCache?: ClientSideCacheProvider | ClientSideCacheConfig;
|
clientSideCache?: ClientSideCacheProvider | ClientSideCacheConfig;
|
||||||
|
/**
|
||||||
|
* 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<
|
||||||
@@ -514,7 +523,28 @@ export default class RedisClient<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async #handshake(selectedDB: number) {
|
async #handshake(chainId: symbol, asap: boolean) {
|
||||||
|
const promises = [];
|
||||||
|
const commandsWithErrorHandlers = await this.#getHandshakeCommands();
|
||||||
|
|
||||||
|
if (asap) commandsWithErrorHandlers.reverse()
|
||||||
|
|
||||||
|
for (const { cmd, errorHandler } of commandsWithErrorHandlers) {
|
||||||
|
promises.push(
|
||||||
|
this.#queue
|
||||||
|
.addCommand(cmd, {
|
||||||
|
chainId,
|
||||||
|
asap
|
||||||
|
})
|
||||||
|
.catch(errorHandler)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return promises;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #getHandshakeCommands(): Promise<
|
||||||
|
Array<{ cmd: CommandArguments } & { errorHandler?: (err: Error) => void }>
|
||||||
|
> {
|
||||||
const commands = [];
|
const commands = [];
|
||||||
const cp = this.#options?.credentialsProvider;
|
const cp = this.#options?.credentialsProvider;
|
||||||
|
|
||||||
@@ -532,8 +562,8 @@ export default class RedisClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cp && cp.type === 'streaming-credentials-provider') {
|
if (cp && cp.type === 'streaming-credentials-provider') {
|
||||||
|
const [credentials, disposable] =
|
||||||
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp)
|
await this.#subscribeForStreamingCredentials(cp);
|
||||||
this.#credentialsSubscription = disposable;
|
this.#credentialsSubscription = disposable;
|
||||||
|
|
||||||
if (credentials.password) {
|
if (credentials.password) {
|
||||||
@@ -548,59 +578,88 @@ export default class RedisClient<
|
|||||||
hello.SETNAME = this.#options.name;
|
hello.SETNAME = this.#options.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
commands.push(
|
commands.push({ cmd: parseArgs(HELLO, this.#options.RESP, hello) });
|
||||||
parseArgs(HELLO, this.#options.RESP, hello)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (cp && cp.type === 'async-credentials-provider') {
|
if (cp && cp.type === 'async-credentials-provider') {
|
||||||
|
|
||||||
const credentials = await cp.credentials();
|
const credentials = await cp.credentials();
|
||||||
|
|
||||||
if (credentials.username || credentials.password) {
|
if (credentials.username || credentials.password) {
|
||||||
commands.push(
|
commands.push({
|
||||||
parseArgs(COMMANDS.AUTH, {
|
cmd: parseArgs(COMMANDS.AUTH, {
|
||||||
username: credentials.username,
|
username: credentials.username,
|
||||||
password: credentials.password ?? ''
|
password: credentials.password ?? ''
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cp && cp.type === 'streaming-credentials-provider') {
|
if (cp && cp.type === 'streaming-credentials-provider') {
|
||||||
|
const [credentials, disposable] =
|
||||||
const [credentials, disposable] = await this.#subscribeForStreamingCredentials(cp)
|
await this.#subscribeForStreamingCredentials(cp);
|
||||||
this.#credentialsSubscription = disposable;
|
this.#credentialsSubscription = disposable;
|
||||||
|
|
||||||
if (credentials.username || credentials.password) {
|
if (credentials.username || credentials.password) {
|
||||||
commands.push(
|
commands.push({
|
||||||
parseArgs(COMMANDS.AUTH, {
|
cmd: parseArgs(COMMANDS.AUTH, {
|
||||||
username: credentials.username,
|
username: credentials.username,
|
||||||
password: credentials.password ?? ''
|
password: credentials.password ?? ''
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#options?.name) {
|
if (this.#options?.name) {
|
||||||
commands.push(
|
commands.push({
|
||||||
parseArgs(COMMANDS.CLIENT_SETNAME, this.#options.name)
|
cmd: parseArgs(COMMANDS.CLIENT_SETNAME, this.#options.name)
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedDB !== 0) {
|
if (this.#selectedDB !== 0) {
|
||||||
commands.push(['SELECT', this.#selectedDB.toString()]);
|
commands.push({ cmd: ['SELECT', this.#selectedDB.toString()] });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#options?.readonly) {
|
if (this.#options?.readonly) {
|
||||||
commands.push(
|
commands.push({ cmd: parseArgs(COMMANDS.READONLY) });
|
||||||
parseArgs(COMMANDS.READONLY)
|
}
|
||||||
);
|
|
||||||
|
if (!this.#options?.disableClientInfo) {
|
||||||
|
commands.push({
|
||||||
|
cmd: ['CLIENT', 'SETINFO', 'LIB-VER', version],
|
||||||
|
errorHandler: (err: Error) => {
|
||||||
|
// Only throw if not a SimpleError - unknown subcommand
|
||||||
|
// Client libraries are expected to ignore failures
|
||||||
|
// of type SimpleError - unknown subcommand, which are
|
||||||
|
// expected from older servers ( < v7 )
|
||||||
|
if (!(err instanceof SimpleError) || !err.isUnknownSubcommand()) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
cmd: [
|
||||||
|
'CLIENT',
|
||||||
|
'SETINFO',
|
||||||
|
'LIB-NAME',
|
||||||
|
this.#options?.clientInfoTag
|
||||||
|
? `node-redis(${this.#options.clientInfoTag})`
|
||||||
|
: 'node-redis'
|
||||||
|
],
|
||||||
|
errorHandler: (err: Error) => {
|
||||||
|
// Only throw if not a SimpleError - unknown subcommand
|
||||||
|
// Client libraries are expected to ignore failures
|
||||||
|
// of type SimpleError - unknown subcommand, which are
|
||||||
|
// expected from older servers ( < v7 )
|
||||||
|
if (!(err instanceof SimpleError) || !err.isUnknownSubcommand()) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#clientSideCache) {
|
if (this.#clientSideCache) {
|
||||||
commands.push(this.#clientSideCache.trackingOn());
|
commands.push({cmd: this.#clientSideCache.trackingOn()});
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
@@ -629,15 +688,7 @@ export default class RedisClient<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const commands = await this.#handshake(this.#selectedDB);
|
promises.push(...(await this.#handshake(chainId, true)));
|
||||||
for (let i = commands.length - 1; i >= 0; --i) {
|
|
||||||
promises.push(
|
|
||||||
this.#queue.addCommand(commands[i], {
|
|
||||||
chainId,
|
|
||||||
asap: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (promises.length) {
|
if (promises.length) {
|
||||||
this.#write();
|
this.#write();
|
||||||
@@ -1221,13 +1272,7 @@ export default class RedisClient<
|
|||||||
selectedDB = this._self.#options?.database ?? 0;
|
selectedDB = this._self.#options?.database ?? 0;
|
||||||
this._self.#credentialsSubscription?.dispose();
|
this._self.#credentialsSubscription?.dispose();
|
||||||
this._self.#credentialsSubscription = null;
|
this._self.#credentialsSubscription = null;
|
||||||
for (const command of (await this._self.#handshake(selectedDB))) {
|
promises.push(...(await this._self.#handshake(chainId, false)));
|
||||||
promises.push(
|
|
||||||
this._self.#queue.addCommand(command, {
|
|
||||||
chainId
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this._self.#scheduleWrite();
|
this._self.#scheduleWrite();
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
this._self.#selectedDB = selectedDB;
|
this._self.#selectedDB = selectedDB;
|
||||||
|
@@ -2,6 +2,7 @@ import { strict as assert } from 'node:assert';
|
|||||||
import CLIENT_INFO from './CLIENT_INFO';
|
import CLIENT_INFO from './CLIENT_INFO';
|
||||||
import testUtils, { GLOBAL } from '../test-utils';
|
import testUtils, { GLOBAL } from '../test-utils';
|
||||||
import { parseArgs } from './generic-transformers';
|
import { parseArgs } from './generic-transformers';
|
||||||
|
import { version } from '../../package.json';
|
||||||
|
|
||||||
describe('CLIENT INFO', () => {
|
describe('CLIENT INFO', () => {
|
||||||
testUtils.isVersionGreaterThanHook([6, 2]);
|
testUtils.isVersionGreaterThanHook([6, 2]);
|
||||||
@@ -48,4 +49,89 @@ describe('CLIENT INFO', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
|
|
||||||
|
testUtils.testWithClient('client.clientInfo Redis < 7', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
if (!testUtils.isVersionGreaterThan([7])) {
|
||||||
|
assert.strictEqual(reply.libName, undefined, 'LibName should be undefined for Redis < 7');
|
||||||
|
assert.strictEqual(reply.libVer, undefined, 'LibVer should be undefined for Redis < 7');
|
||||||
|
}
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
|
|
||||||
|
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 info disabled', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
assert.equal(reply.libName, '');
|
||||||
|
assert.equal(reply.libVer, '');
|
||||||
|
}, {
|
||||||
|
...GLOBAL.SERVERS.OPEN,
|
||||||
|
clientOptions: {
|
||||||
|
disableClientInfo: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp unset, info enabled, tag set', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
assert.equal(reply.libName, 'node-redis(client1)');
|
||||||
|
assert.equal(reply.libVer, version);
|
||||||
|
}, {
|
||||||
|
...GLOBAL.SERVERS.OPEN,
|
||||||
|
clientOptions: {
|
||||||
|
clientInfoTag: 'client1'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp unset, info enabled, tag unset', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
assert.equal(reply.libName, 'node-redis');
|
||||||
|
assert.equal(reply.libVer, version);
|
||||||
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
|
|
||||||
|
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp2 info enabled', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
assert.equal(reply.libName, 'node-redis(client1)');
|
||||||
|
assert.equal(reply.libVer, version);
|
||||||
|
}, {
|
||||||
|
...GLOBAL.SERVERS.OPEN,
|
||||||
|
clientOptions: {
|
||||||
|
RESP: 2,
|
||||||
|
clientInfoTag: 'client1'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp2 info disabled', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
assert.equal(reply.libName, '');
|
||||||
|
assert.equal(reply.libVer, '');
|
||||||
|
}, {
|
||||||
|
...GLOBAL.SERVERS.OPEN,
|
||||||
|
clientOptions: {
|
||||||
|
disableClientInfo: true,
|
||||||
|
RESP: 2
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp3 info enabled', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
assert.equal(reply.libName, 'node-redis(client1)');
|
||||||
|
assert.equal(reply.libVer, version);
|
||||||
|
}, {
|
||||||
|
...GLOBAL.SERVERS.OPEN,
|
||||||
|
clientOptions: {
|
||||||
|
RESP: 3,
|
||||||
|
clientInfoTag: 'client1'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.testWithClientIfVersionWithinRange([[7], 'LATEST'], 'client.clientInfo Redis>=7 resp3 info disabled', async client => {
|
||||||
|
const reply = await client.clientInfo();
|
||||||
|
assert.equal(reply.libName, '');
|
||||||
|
assert.equal(reply.libVer, '');
|
||||||
|
}, {
|
||||||
|
...GLOBAL.SERVERS.OPEN,
|
||||||
|
clientOptions: {
|
||||||
|
disableClientInfo: true,
|
||||||
|
RESP: 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -52,6 +52,14 @@ export interface ClientInfoReply {
|
|||||||
* available since 7.0
|
* available since 7.0
|
||||||
*/
|
*/
|
||||||
resp?: number;
|
resp?: number;
|
||||||
|
/**
|
||||||
|
* available since 7.0
|
||||||
|
*/
|
||||||
|
libName?: string;
|
||||||
|
/**
|
||||||
|
* available since 7.0
|
||||||
|
*/
|
||||||
|
libVer?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g;
|
const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g;
|
||||||
@@ -67,7 +75,6 @@ export default {
|
|||||||
for (const item of rawReply.toString().matchAll(CLIENT_INFO_REGEX)) {
|
for (const item of rawReply.toString().matchAll(CLIENT_INFO_REGEX)) {
|
||||||
map[item[1]] = item[2];
|
map[item[1]] = item[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
const reply: ClientInfoReply = {
|
const reply: ClientInfoReply = {
|
||||||
id: Number(map.id),
|
id: Number(map.id),
|
||||||
addr: map.addr,
|
addr: map.addr,
|
||||||
@@ -89,7 +96,9 @@ export default {
|
|||||||
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) {
|
||||||
|
@@ -70,7 +70,11 @@ export class ErrorReply extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SimpleError extends ErrorReply {}
|
export class SimpleError extends ErrorReply {
|
||||||
|
isUnknownSubcommand(): boolean {
|
||||||
|
return this.message.toLowerCase().indexOf('err unknown subcommand') !== -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class BlobError extends ErrorReply {}
|
export class BlobError extends ErrorReply {}
|
||||||
|
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist"
|
"outDir": "./dist",
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./index.ts",
|
"./index.ts",
|
||||||
"./lib/**/*.ts"
|
"./lib/**/*.ts",
|
||||||
|
"./package.json"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"./lib/test-utils.ts",
|
"./lib/test-utils.ts",
|
||||||
@@ -18,6 +19,6 @@
|
|||||||
"./lib"
|
"./lib"
|
||||||
],
|
],
|
||||||
"entryPointStrategy": "expand",
|
"entryPointStrategy": "expand",
|
||||||
"out": "../../documentation/client"
|
"out": "../../documentation/client",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"allowJs": true
|
"allowJs": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user