Skip to content

Commit

Permalink
add ability to overide default transport timeout when creating client #…
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed May 4, 2023
1 parent 2a26a65 commit 1b97628
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 71 deletions.
6 changes: 5 additions & 1 deletion packages/node-opcua-client/source/client_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ export interface OPCUAClientBaseOptions {
* @default false
*/
keepSessionAlive?: boolean;
/**
* the number of milliseconds that the client should wait until it sends a keep alive message to the server.
*/
keepAliveInterval?: number;

/**
* certificate Manager
Expand Down Expand Up @@ -132,6 +136,7 @@ export interface OPCUAClientBaseOptions {
* @advanced
*/
transportSettings?: TransportSettings;
transportTimeout?: number;
}

export interface GetEndpointsOptions {
Expand Down Expand Up @@ -325,7 +330,6 @@ export interface OPCUAClientBase {
readonly connectionStrategy: ConnectionStrategy;
readonly keepPendingSessionsOnDisconnect: boolean;
readonly endpointUrl: string;
readonly keepSessionAlive: boolean;
readonly applicationName: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class ClientSessionKeepAliveManager extends EventEmitter implements Clien
this.count = 0;
}

public start(): void {
public start(keepAliveInterval?: number): void {
assert(!this.timerId);
/* istanbul ignore next*/
if (this.session.timeout < 600) {
Expand All @@ -58,10 +58,12 @@ export class ClientSessionKeepAliveManager extends EventEmitter implements Clien
);
}

const v = Math.min(this.session.timeout, ClientSecureChannelLayer.defaultTransportTimeout);
const selectedCheckInterval =
keepAliveInterval ||
Math.min(Math.floor(Math.min((this.session.timeout * 2) / 3, 20000)), ClientSecureChannelLayer.defaultTransportTimeout);

this.pingTimeout = Math.floor(Math.min(this.session.timeout / 3, 20000));
this.checkInterval = Math.floor(Math.max(50, Math.min((this.session.timeout * 2) / 3, 20000)));
this.checkInterval = selectedCheckInterval;
this.pingTimeout = Math.floor(Math.min(Math.max(50, selectedCheckInterval / 2), 20000));

// make sure first one is almost immediate
this.timerId = setTimeout(() => this.ping_server(), this.pingTimeout);
Expand Down
46 changes: 0 additions & 46 deletions packages/node-opcua-client/source/opcua_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { ClientSubscription, ClientSubscriptionOptions } from "./client_subscrip
import { OPCUAClientImpl } from "./private/opcua_client_impl";
import { UserIdentityInfo } from "./user_identity_info";


export interface OPCUAClientOptions extends OPCUAClientBaseOptions {
/**
* the requested session timeout in CreateSession (ms)
Expand All @@ -43,51 +42,6 @@ export interface OPCUAClientOptions extends OPCUAClientBaseOptions {
* @default true
*/
endpointMustExist?: boolean;

// --------------------------------------------------------------------
connectionStrategy?: ConnectionStrategyOptions;

/** the server certificate. */
serverCertificate?: Certificate;

/***
* default secure token lifetime in ms
*/
defaultSecureTokenLifetime?: number;

/**
* the security mode
* @default MessageSecurityMode.None
*/
securityMode?: MessageSecurityMode | string;

/**
* the security policy
* @default SecurityPolicy.None
*/
securityPolicy?: SecurityPolicy | string;

/**
* @default false
*/
keepSessionAlive?: boolean;

/**
* client certificate pem file.
* @default "certificates/client_self-signed_cert_2048.pem"
*/
certificateFile?: string;

/**
* client private key pem file.
* @default "certificates/client_key_2048.pem"
*/
privateKeyFile?: string;

/**
* a client name string that will be used to generate session names.
*/
clientName?: string;
}

export interface OPCUAClient extends OPCUAClientBase {
Expand Down
16 changes: 11 additions & 5 deletions packages/node-opcua-client/source/private/client_base_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
* true if session shall periodically probe the server to keep the session alive and prevent timeout
*/
public keepSessionAlive: boolean;
public readonly keepAliveInterval?: number;

public _sessions: ClientSessionImpl[];
protected _serverEndpoints: EndpointDescription[];
Expand All @@ -383,6 +384,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
private _tmpClient?: OPCUAClientBase;
private _instanceNumber: number;
private _transportSettings: TransportSettings;
private _transportTimeout?: number;

public clientCertificateManager: OPCUACertificateManager;

Expand Down Expand Up @@ -451,6 +453,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
this.serverCertificate = options.serverCertificate;

this.keepSessionAlive = typeof options.keepSessionAlive === "boolean" ? options.keepSessionAlive : false;
this.keepAliveInterval = options.keepAliveInterval;

// statistics...
this._byteRead = 0;
Expand All @@ -471,6 +474,7 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
this._setInternalState("disconnected");

this._transportSettings = options.transportSettings || {};
this._transportTimeout = options.transportTimeout;
}

private _cancel_reconnection(callback: ErrorCallback) {
Expand Down Expand Up @@ -645,8 +649,8 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
securityPolicy: this.securityPolicy,
serverCertificate: this.serverCertificate,
tokenRenewalInterval: this.tokenRenewalInterval,
transportSettings: this._transportSettings
// transportTimeout:
transportSettings: this._transportSettings,
transportTimeout: this._transportTimeout
});
secureChannel.on("backoff", (count: number, delay: number) => {
this.emit("backoff", count, delay);
Expand Down Expand Up @@ -1288,8 +1292,8 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
setImmediate(callback);
});
} else {
this.emit("close", null);
this._setInternalState("disconnected");
// this.emit("close", null);
setImmediate(callback);
}
}
Expand Down Expand Up @@ -1569,12 +1573,14 @@ export class ClientBaseImpl extends OPCUASecureObject implements OPCUAClientBase
* @event close
* @param error
*/
this.emit("close", err);
if (err) {
this.emit("connection_lost", err?.message); // instead of "close"
}
this.emit("close", err); // instead of "close"
} else {
/**
* @event connection_lost
*/
// this.emit("close", err);
if (this.reconnectOnFailure && this._internalState !== "reconnecting") {
debugLog(" ClientBaseImpl emitting connection_lost");
this.emit("connection_lost", err?.message); // instead of "close"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1998,7 +1998,7 @@ export class ClientSessionImpl extends EventEmitter implements ClientSession {
});
}

public startKeepAliveManager(): void {
public startKeepAliveManager(keepAliveInterval?: number): void {
if (this._keepAliveManager) {
// "keepAliveManger already started"
return;
Expand All @@ -2019,7 +2019,7 @@ export class ClientSessionImpl extends EventEmitter implements ClientSession {
*/
this.emit("keepalive", state, count);
});
this._keepAliveManager.start();
this._keepAliveManager.start(keepAliveInterval);
}

public stopKeepAliveManager(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ export class OPCUAClientImpl extends ClientBaseImpl implements OPCUAClient {
session.serverNonce = response.serverNonce;
session.lastResponseReceivedTime = new Date();
if (this.keepSessionAlive) {
session.startKeepAliveManager();
session.startKeepAliveManager(this.keepAliveInterval);
}
session.userIdentityInfo = userIdentityInfo;
return callback(null, session);
Expand Down
123 changes: 123 additions & 0 deletions packages/node-opcua-end2end-test/test/end_to_end/test_e2e_1002.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import "should";
import * as sinon from "sinon";
import { ClientSecureChannelLayer, get_mini_nodeset_filename, OPCUAClient, OPCUAServer } from "node-opcua";

const port = 2128;

async function startServer(): Promise<OPCUAServer> {
// get IP of the machine
const mini = get_mini_nodeset_filename();

const server = new OPCUAServer({
port,
// nodeset_filename: [nodesets.standard],
nodeset_filename: [mini]
});
await server.initialize();
await server.start();
return server;
}
// tslint:disable-next-line:no-var-requires
const describe = require("node-opcua-leak-detector").describeWithLeakDetector;
describe("#1002 - ability to set transport timeout ", () => {
let server: OPCUAServer;
before(async () => {
server = await startServer();
});
after(async () => {
await server.shutdown();
server.dispose();
});
it("A- using default transport timeout", async () => {
const endpointUrl = server.getEndpointUrl();
const client = OPCUAClient.create({});
const spyConnectionLost = sinon.spy();
const spyClose = sinon.spy();
const spyConnectionReestablished = sinon.spy();
client.on("connection_lost", spyConnectionLost);
client.on("close", spyClose);
client.on("connection_reestablished", spyConnectionReestablished);
const actualTimeout = await client.withSessionAsync<number>(endpointUrl, async (session) => {
const timeout = (client as any)._secureChannel!._transport.timeout;
const socket = (client as any)._secureChannel!._transport._socket as NodeJS.Socket;
socket.on("timeout", () => console.log("socket timeout"));
return timeout;
});
actualTimeout.should.eql(ClientSecureChannelLayer.defaultTransportTimeout);
spyClose.callCount.should.eql(1);
spyConnectionLost.callCount.should.eql(0);
spyConnectionReestablished.callCount.should.eql(0);
});
it("B- should be possible to set the transport timeout - no automatic reconnection", async () => {
const endpointUrl = server.getEndpointUrl();

const transportTimeout = 1234;
const client = OPCUAClient.create({
transportTimeout,
connectionStrategy: { maxRetry: 0 } // we don't want automatic reconnection => maxRetry = 0
});
client.on("backoff", () => console.log("keep trying", endpointUrl));
client.on("connection_lost", () => console.log("connection lost"));
client.on("connection_reestablished", () => console.log("connection_reestablished"));
const spyConnectionLost = sinon.spy();
const spyClose = sinon.spy();
const spyConnectionReestablished = sinon.spy();
client.on("connection_lost", spyConnectionLost);
client.on("close", spyClose);
client.on("connection_reestablished", spyConnectionReestablished);

const actualTimeout = await client.withSessionAsync(endpointUrl, async (session) => {
const timeout = (client as any)._secureChannel!._transport.timeout;
const socket = (client as any)._secureChannel!._transport._socket as NodeJS.Socket;
socket.on("timeout", () => console.log("socket timeout"));

console.log("timeout = ", timeout);
console.log("connected");
await new Promise((resolve) => setTimeout(resolve, transportTimeout + 4000));
console.log("done");

return (timeout as number) || 0;
});
actualTimeout.should.eql(transportTimeout);
spyConnectionLost.callCount.should.eql(1);
spyClose.callCount.should.eql(1);
spyConnectionReestablished.callCount.should.eql(0);
});

it("C- should be possible to set the transport timeout - with automatic reconnection", async () => {
const endpointUrl = server.getEndpointUrl();

const transportTimeout = 1000;
const client = OPCUAClient.create({
transportTimeout,
connectionStrategy: { maxRetry: 1 } // we WANT automatic reconnection => maxRetry <> 1
});
client.on("backoff", () => console.log("keep trying", endpointUrl));
client.on("connection_lost", () => console.log("connection lost"));
client.on("connection_reestablished", () => console.log("connection_reestablished"));
const spyConnectionLost = sinon.spy();
const spyClose = sinon.spy();
const spyConnectionReestablished = sinon.spy();
client.on("connection_lost", spyConnectionLost);
client.on("close", spyClose);
client.on("connection_reestablished", spyConnectionReestablished);

const actualTimeout = await client.withSessionAsync(endpointUrl, async (session) => {
const timeout = (client as any)._secureChannel!._transport.timeout;
const socket = (client as any)._secureChannel!._transport._socket as NodeJS.Socket;
socket.on("timeout", () => console.log("socket timeout"));

console.log("timeout = ", timeout);
console.log("connected");
await new Promise((resolve) => setTimeout(resolve, transportTimeout + 6000));
console.log("done");

return (timeout as number) || 0;
});
actualTimeout.should.eql(transportTimeout);
spyConnectionLost.callCount.should.be.greaterThan(2);
spyConnectionReestablished.callCount.should.be.greaterThan(2);
spyClose.callCount.should.eql(1);
spyConnectionReestablished.callCount.should.be.greaterThan(2);
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/* eslint-disable max-statements */
// tslint:disable: no-shadowed-variable
// tslint:disable: no-console
import {
AddressSpace,
assert,
AttributeIds,
ClientMonitoredItem,
ClientSession,
Expand All @@ -15,21 +12,15 @@ import {
DataChangeNotification,
DataChangeTrigger,
DataType,
DataValue,
DeadbandType,
ExtensionObject,
getCurrentClock,
makeBrowsePath,
MonitoredItem,
MonitoredItemNotification,
MonitoringMode,
MonitoringParametersOptions,
Namespace,
NodeIdLike,
NotificationMessage,
OPCUAClient,
Range,
ServerSidePublishEngine,
ServiceFault,
SetTriggeringRequestOptions,
StatusCode,
Expand Down Expand Up @@ -57,10 +48,12 @@ function getInternalPublishEngine(session: ClientSession): ClientSidePublishEngi
return s;
}
export function t(test: any) {
const options = {};


async function createSession() {
const client = OPCUAClient.create(options);
const client = OPCUAClient.create({
keepSessionAlive: true,
keepAliveInterval: 1000,
});
const endpointUrl = test.endpointUrl;
await client.connect(endpointUrl);
const session = await client.createSession();
Expand Down

0 comments on commit 1b97628

Please sign in to comment.