Skip to content

Commit

Permalink
Implementing custom cache option
Browse files Browse the repository at this point in the history
* Adding IWSDLCache interface which can be extended with custom caching
  implementations
* Added DefaultWSDLCache implementation which matches current caching
  handling, ie global singleton which indefinitely caches WSDLs.
* Added options.wsdlCache to allow users to override the default cache.
* Added test cases for default cache and overriding option.
  • Loading branch information
millerdk12 committed Oct 31, 2024
1 parent 9598d8e commit 265e644
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 24 deletions.
1 change: 1 addition & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ GitHub issues have been disabled to focus on pull requests. ([#731](https://gith
- `wsdl_headers` (*Object*): Set HTTP headers with values to be sent on WSDL requests.
- `wsdl_options` (*Object*): Set options for the request module on WSDL requests. If using the default request module, see [Request Config | Axios Docs](https://axios-http.com/docs/req_config).
- `disableCache` (*boolean*): Prevents caching WSDL files and option objects.
- `wsdlCache` (*IWSDLCache*): Custom cache implementation. If not provided, defaults to caching WSDLs indefinitely.
- `overridePromiseSuffix` (*string*): Override the default method name suffix of WSDL operations for Promise-based methods. If any WSDL operation name ends with `Async', you must use this option. (**Default:** `Async`)
- `normalizeNames` (*boolean*): Replace non-identifier characters (`[^a-z$_0-9]`) with `_` in WSDL operation names. Note: Clients using WSDLs with two operations like `soap:method` and `soap-method` will be overwritten. In this case, you must use bracket notation instead (`client['soap:method']()`).
- `namespaceArrayElements` (*boolean*): Support non-standard array semantics. JSON arrays of the form `{list: [{elem: 1}, {elem: 2}]}` will be marshalled into XML as `<list><elem>1</elem></list> <list><elem>2</elem></list>`. If `false`, it would be marshalled into `<list> <elem>1</elem> <elem>2</elem> </list>`. (**Default:** `true`)
Expand Down
46 changes: 23 additions & 23 deletions src/soap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import debugBuilder from 'debug';
import { Client } from './client';
import * as _security from './security';
import { Server, ServerType } from './server';
import { IOptions, IServerOptions, IServices } from './types';
import { IOptions, IServerOptions, IServices, IWSDLCache } from './types';
import { wsdlCacheSingleton } from './utils';
import { open_wsdl, WSDL } from './wsdl';

const debug = debugBuilder('node-soap:soap');
Expand All @@ -23,29 +24,22 @@ export { WSDL } from './wsdl';

type WSDLCallback = (error: any, result?: WSDL) => any;

function createCache() {
const cache: {
[key: string]: WSDL,
} = {};
return (key: string, load: (cb: WSDLCallback) => any, callback: WSDLCallback) => {
if (!cache[key]) {
load((err, result) => {
if (err) {
return callback(err);
}
cache[key] = result;
callback(null, result);
});
} else {
process.nextTick(() => {
callback(null, cache[key]);
});
}
};
function getFromCache(key: string, cache: IWSDLCache, load: (cb: WSDLCallback) => any, callback: WSDLCallback) {
if (!cache.has(key)) {
load((err, result) => {
if (err) {
return callback(err);
}
cache.set(key, result);
callback(null, result);
});
} else {
process.nextTick(() => {
callback(null, cache.get(key));
});
}
}

const getFromCache = createCache();

function _requestWSDL(url: string, options: IOptions, callback: WSDLCallback) {
if (typeof options === 'function') {
callback = options;
Expand All @@ -58,7 +52,13 @@ function _requestWSDL(url: string, options: IOptions, callback: WSDLCallback) {
if (options.disableCache === true) {
openWsdl(callback);
} else {
getFromCache(url, openWsdl, callback);
let cache: IWSDLCache;
if (options.wsdlCache) {
cache = options.wsdlCache;
} else {
cache = wsdlCacheSingleton;
}
getFromCache(url, cache, openWsdl, callback);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import * as req from 'axios';
import { ReadStream } from 'fs';
import { WSDL } from './wsdl';

export interface IHeaders {
[k: string]: any;
Expand Down Expand Up @@ -117,6 +118,8 @@ export type Option = IOptions;
export interface IOptions extends IWsdlBaseOptions {
/** don't cache WSDL files, request them every time. */
disableCache?: boolean;
/** Custom cache implementation. If not provided, defaults to caching WSDLs indefinitely. */
wsdlCache?: IWSDLCache;
/** override the SOAP service's host specified in the .wsdl file. */
endpoint?: string;
/** set specific key instead of <pre><soap:Body></soap:Body></pre>. */
Expand Down Expand Up @@ -164,3 +167,9 @@ export interface IMTOMAttachments {
headers: { [key: string]: string },
}>;
}

export interface IWSDLCache {
has(key: string): boolean;
get(key: string): WSDL;
set(key: string, wsdl: WSDL): void;
}
29 changes: 28 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import * as crypto from 'crypto';
import { IMTOMAttachments } from './types';
import { IMTOMAttachments, IWSDLCache } from './types';
import { WSDL } from './wsdl';

export function passwordDigest(nonce: string, created: string, password: string): string {
// digest = base64 ( sha1 ( nonce + created + password ) )
Expand Down Expand Up @@ -113,3 +114,29 @@ export function parseMTOMResp(payload: Buffer, boundary: string, callback: (err?
})
.catch(callback);
}

class DefaultWSDLCache implements IWSDLCache {
private cache: {
[key: string]: WSDL,
};
constructor() {
this.cache = {};
}

public has(key: string): boolean {
return !!this.cache[key];
}

public get(key: string): WSDL {
return this.cache[key];
}

public set(key: string, wsdl: WSDL) {
this.cache[key] = wsdl;
}

public clear() {
this.cache = {};
}
}
export const wsdlCacheSingleton = new DefaultWSDLCache();
86 changes: 86 additions & 0 deletions test/client-options-wsdlcache-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict';
var soap = require('..'),
assert = require('assert'),
sinon = require('sinon'),
utils = require('../lib/utils'),
wsdl = require("../lib/wsdl");
describe('SOAP Client - WSDL Cache', function() {
var sandbox = sinon.createSandbox();
var wsdlUri = __dirname + '/wsdl/Dummy.wsdl';
beforeEach(function () {
sandbox.spy(wsdl, 'open_wsdl');
});
afterEach(function () {
sandbox.restore();
});

it('should use default cache if not provided', function(done) {
// ensure cache is empty to prevent impacts to this case
// if other test already loaded this WSDL
utils.wsdlCacheSingleton.clear();

// cache miss
soap.createClient(wsdlUri, {}, function(err, clientFirstCall) {
if (err) return done(err);
assert.strictEqual(wsdl.open_wsdl.callCount, 1);

// hits cache
soap.createClient(wsdlUri, {}, function(err, clientSecondCall) {
if (err) return done(err);
assert.strictEqual(wsdl.open_wsdl.callCount, 1);

// disabled cache
soap.createClient(wsdlUri, {disableCache: true}, function(err, clientSecondCall) {
if (err) return done(err);
assert.strictEqual(wsdl.open_wsdl.callCount, 2);
done();
});
});
});
});

it('should use the provided WSDL cache', function(done) {
/** @type {IWSDLCache} */
var dummyCache = {
has: function () {},
get: function () {},
set: function () {},
};
sandbox.stub(dummyCache, 'has');
sandbox.stub(dummyCache, 'get');
sandbox.stub(dummyCache, 'set');
dummyCache.has.returns(false);
var options = {
wsdlCache: dummyCache,
};
soap.createClient(wsdlUri, options, function(err, clientFirstCall) {
assert.strictEqual(dummyCache.has.callCount, 1);
assert.strictEqual(dummyCache.get.callCount, 0);
assert.strictEqual(dummyCache.set.callCount, 1);
// cache miss
assert.strictEqual(wsdl.open_wsdl.callCount, 1);

var cacheEntry = dummyCache.set.firstCall.args;
assert.deepStrictEqual(cacheEntry[0], wsdlUri);

var cachedWSDL = cacheEntry[1];
assert.ok(cachedWSDL instanceof wsdl.WSDL);
assert.deepStrictEqual(clientFirstCall.wsdl, cachedWSDL);

sandbox.reset();
dummyCache.has.returns(true);
dummyCache.get.returns(cachedWSDL);

soap.createClient(wsdlUri, options, function(err, clientSecondCall) {
// hits cache
assert.strictEqual(wsdl.open_wsdl.callCount, 0);
assert.strictEqual(dummyCache.has.callCount, 1);
assert.strictEqual(dummyCache.get.callCount, 1);
assert.deepStrictEqual(dummyCache.get.firstCall.args, [wsdlUri]);
assert.strictEqual(dummyCache.set.callCount, 0);
assert.deepStrictEqual(clientSecondCall.wsdl, cachedWSDL);
done();
});
});
});
});

0 comments on commit 265e644

Please sign in to comment.