Skip to content

Commit 2953905

Browse files
Add maester v header (#71)
1 parent b119f68 commit 2953905

File tree

12 files changed

+226
-49
lines changed

12 files changed

+226
-49
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 3.0.1 (August 12, 2022)
2+
* Increase default values and limits for envs `API_RETRIES_COUNT` and `API_REQUEST_TIMEOUT`
3+
* Add `axiosReqWithRetryOnServerError` function to proceed with most commons use cases of making query to external API
4+
* Updated @elasticio/maester-client to v4.0.2
5+
16
## 3.0.0 (July 25, 2022)
27
* Updated method `uploadAttachment` from `AttachmentProcessor`
38
* Added method `getMaesterAttachmentUrlById` which return url to Maester attachment

README.md

+51
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- [JSON Schema Converter](#JSON-Schema-Converter)
1717
- [JSON Transformation](#JSON-Transformation)
1818
- [Attachment Processor](#Attachment-Processor)
19+
- [External API](#external-api)
1920
- [Logger](#Logger)
2021
- [License](#license)
2122

@@ -229,6 +230,56 @@ const result = await new AttachmentProcessor().getAttachment('http://example.com
229230
const result = await new AttachmentProcessor().getAttachment('http://example.com?storage_type=maester', 'stream'); // maester storage
230231
```
231232

233+
## External API
234+
235+
### Environment variables
236+
* **API_RETRIES_COUNT** (defaults to 3): maximum amount of retries for 5xx errors. If server still responding 5xx, error will be thrown.
237+
* **API_REQUEST_TIMEOUT** (defaults to 15000): specifies the number of milliseconds before the request times out. If the request takes longer than timeout, the request will be aborted.
238+
239+
- `axiosReqWithRetryOnServerError` (use with `.call()` to pass context, implement it as a method of class with `logger` and `cfg` (value of configuration object for current action) values in a constructor) - function which makes axios request by specified request-config, making logging and error handling:
240+
1. If 5xx error occurred, it will be retried maximum `API_RETRIES_COUNT` times, each retry will be delayed with `exponentialSleep` function.
241+
2. If 4xx error occurred - error will be throw.
242+
3. If action `cfg` has `doNotThrow404` set to true: 404 error won't be treated as error. <br>
243+
Look on examples below.
244+
- `getErrMsg` - forms error message from axios-response.
245+
- `getRetryOptions` - return valid values for envs `API_RETRIES_COUNT` and `API_REQUEST_TIMEOUT`. If values are higher or lower the limit - they'll be overwritten by default values.
246+
- `sleep` - return promise which resolves after N time.
247+
- `exponentialDelay` - returns number of milliseconds depending to current retry. See [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) to explanation.
248+
- `exponentialSleep` - return promise which resolves after N time. Where N is number of milliseconds from `exponentialDelay` execution.
249+
250+
Example for `axiosReqWithRetryOnServerError` function:
251+
```javascript
252+
class Client {
253+
private logger: any;
254+
255+
private cfg: any;
256+
257+
constructor(emitter, cfg) {
258+
this.logger = emitter.logger;
259+
this.cfg = cfg;
260+
}
261+
262+
public async apiRequest(options: AxiosRequestConfig): Promise<any> {
263+
try {
264+
const response = await axiosReq.axiosReqWithRetryOnServerError(this, requestOptions);
265+
return response.data;
266+
} catch (error) {
267+
if (error.response?.status === 401) {
268+
// update token
269+
}
270+
throw error;
271+
}
272+
}
273+
274+
public async getUserById(id) {
275+
return this.apiRequest({
276+
url: `/users/${id}`,
277+
method: 'GET',
278+
});
279+
}
280+
}
281+
```
282+
232283
## Logger
233284
The built in logger uses Bunyan Logger as its base implementation. The available logger methods can be found [here](https://github.com/elasticio/component-commons-library/blob/master/src/logger/logger.ts#L19).
234285

dist/spec-integration/attachmentProcessor.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const common_1 = require("./common");
3232
const AttachmentProcessor_1 = require("../src/attachment/AttachmentProcessor");
3333
chai_1.default.use(require('chai-as-promised'));
3434
describe('AttachmentProcessor', () => {
35-
const objectStorage = new maester_client_1.ObjectStorage({ uri: common_1.creds.uri, jwtSecret: common_1.creds.token });
36-
const attachmentProcessor = new AttachmentProcessor_1.AttachmentProcessor();
35+
const objectStorage = new maester_client_1.ObjectStorage({ uri: common_1.creds.uri, jwtSecret: common_1.creds.token, userAgent: 'userAgent' });
36+
const attachmentProcessor = new AttachmentProcessor_1.AttachmentProcessor('userAgent');
3737
describe('uploadAttachment', () => {
3838
it('uploadAttachment (/samples/sample.json)', async () => {
3939
const getFileAsStream = async () => fs_1.default.createReadStream(path_1.default.join(__dirname, './samples/sample.json'));

dist/spec/attachment/AttachmentProcessor.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const formStream = (dataString) => {
2121
return stream;
2222
};
2323
describe('AttachmentProcessor', () => {
24-
const attachmentProcessor = new AttachmentProcessor_1.AttachmentProcessor();
24+
const attachmentProcessor = new AttachmentProcessor_1.AttachmentProcessor('userAgent');
2525
describe('Steward', () => {
2626
it('Should successfully retrieve csv', async () => {
2727
const attachmentOptions = {

dist/src/attachment/AttachmentProcessor.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,19 @@ const url_1 = require("url");
1010
const maester_client_1 = require("@elastic.io/maester-client");
1111
const interfaces_1 = require("@elastic.io/maester-client/dist/src/interfaces");
1212
const logger_1 = require("../logger/logger");
13+
const package_json_1 = __importDefault(require("../../package.json"));
1314
const logger = (0, logger_1.getLogger)();
15+
const maesterClientVersion = package_json_1.default.dependencies['@elastic.io/maester-client'];
1416
exports.STORAGE_TYPE_PARAMETER = 'storage_type';
1517
exports.DEFAULT_STORAGE_TYPE = 'steward';
1618
exports.MAESTER_OBJECT_ID_ENDPOINT = '/objects/';
1719
const { ELASTICIO_OBJECT_STORAGE_TOKEN = '', ELASTICIO_OBJECT_STORAGE_URI = '' } = process.env;
1820
const maesterCreds = { jwtSecret: ELASTICIO_OBJECT_STORAGE_TOKEN, uri: ELASTICIO_OBJECT_STORAGE_URI };
1921
const DEFAULT_ATTACHMENT_REQUEST_TIMEOUT = process.env.REQUEST_TIMEOUT ? parseInt(process.env.REQUEST_TIMEOUT, 10) : interfaces_1.REQUEST_TIMEOUT.maxValue; // 20s
2022
class AttachmentProcessor {
23+
constructor(userAgent) {
24+
this.userAgent = `${userAgent} maester-client/${maesterClientVersion}`;
25+
}
2126
async getAttachment(url, responseType) {
2227
const storageType = this.getStorageTypeByUrl(url);
2328
const axConfig = {
@@ -26,6 +31,7 @@ class AttachmentProcessor {
2631
method: 'get',
2732
timeout: DEFAULT_ATTACHMENT_REQUEST_TIMEOUT,
2833
retry: interfaces_1.RETRIES_COUNT.defaultValue,
34+
headers: { 'User-Agent': this.userAgent }
2935
};
3036
switch (storageType) {
3137
case 'steward': return this.getStewardAttachment(axConfig);
@@ -38,7 +44,7 @@ class AttachmentProcessor {
3844
const headers = {};
3945
if (contentType)
4046
headers[interfaces_1.CONTENT_TYPE_HEADER] = contentType;
41-
const objectStorage = new maester_client_1.ObjectStorage(maesterCreds);
47+
const objectStorage = new maester_client_1.ObjectStorage({ ...maesterCreds, userAgent: this.userAgent });
4248
return objectStorage.add(getAttachment, {
4349
headers,
4450
retryOptions: {
@@ -55,8 +61,7 @@ class AttachmentProcessor {
5561
return ax(axConfig);
5662
}
5763
async getMaesterAttachment({ url, responseType }) {
58-
const client = new maester_client_1.StorageClient(maesterCreds);
59-
const objectStorage = new maester_client_1.ObjectStorage(maesterCreds, client);
64+
const objectStorage = new maester_client_1.ObjectStorage({ ...maesterCreds, userAgent: this.userAgent });
6065
const maesterAttachmentId = this.getMaesterAttachmentIdByUrl(url);
6166
const response = await objectStorage.getOne(maesterAttachmentId, { responseType });
6267
return { data: response };

dist/src/externalApi/index.js

+40-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
"use strict";
2+
var __importDefault = (this && this.__importDefault) || function (mod) {
3+
return (mod && mod.__esModule) ? mod : { "default": mod };
4+
};
25
Object.defineProperty(exports, "__esModule", { value: true });
3-
exports.getErrMsg = exports.exponentialSleep = exports.sleep = exports.exponentialDelay = exports.getRetryOptions = exports.API_REQUEST_TIMEOUT = exports.API_RETRIES_COUNT = void 0;
6+
exports.axiosReqWithRetryOnServerError = exports.getErrMsg = exports.exponentialSleep = exports.sleep = exports.exponentialDelay = exports.getRetryOptions = exports.API_REQUEST_TIMEOUT = exports.API_RETRIES_COUNT = void 0;
7+
const axios_1 = __importDefault(require("axios"));
48
exports.API_RETRIES_COUNT = {
59
minValue: 0,
6-
defaultValue: 2,
7-
maxValue: 4
10+
defaultValue: 3,
11+
maxValue: 5
812
};
913
const ENV_API_RETRIES_COUNT = process.env.API_RETRIES_COUNT ? parseInt(process.env.API_RETRIES_COUNT, 10) : exports.API_RETRIES_COUNT.defaultValue;
1014
exports.API_REQUEST_TIMEOUT = {
1115
minValue: 500,
12-
defaultValue: 10000,
13-
maxValue: 15000
16+
defaultValue: 15000,
17+
maxValue: 20000
1418
};
1519
const ENV_API_REQUEST_TIMEOUT = process.env.API_REQUEST_TIMEOUT ? parseInt(process.env.API_REQUEST_TIMEOUT, 10) : exports.API_REQUEST_TIMEOUT.defaultValue;
1620
/**
@@ -27,7 +31,7 @@ const getRetryOptions = () => ({
2731
});
2832
exports.getRetryOptions = getRetryOptions;
2933
const exponentialDelay = (currentRetries) => {
30-
const maxBackoff = 10000;
34+
const maxBackoff = 15000;
3135
const delay = (2 ** currentRetries) * 100;
3236
const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
3337
return Math.min(delay + randomSum, maxBackoff);
@@ -46,3 +50,33 @@ const getErrMsg = (errResponse) => {
4650
return `Got error "${statusText}", status - "${status}", body: ${JSON.stringify(data)}`;
4751
};
4852
exports.getErrMsg = getErrMsg;
53+
const axiosReqWithRetryOnServerError = async function (options, axiosInstance = axios_1.default) {
54+
var _a;
55+
const { retriesCount, requestTimeout } = (0, exports.getRetryOptions)();
56+
let response;
57+
let currentRetry = 0;
58+
let error;
59+
while (currentRetry < retriesCount) {
60+
try {
61+
response = await axiosInstance.request({
62+
...options,
63+
timeout: requestTimeout,
64+
validateStatus: (status) => (status >= 200 && status < 300) || (status === 404 && this.cfg.doNotThrow404)
65+
});
66+
return response;
67+
}
68+
catch (err) {
69+
error = err;
70+
if (((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) < 500) {
71+
throw error;
72+
}
73+
this.logger.info(`URL: "${options.url}", method: ${options.method}, Error message: "${err.message}"`);
74+
this.logger.error((0, exports.getErrMsg)(err.response));
75+
this.logger.info(`Request failed, retrying(${1 + currentRetry})`);
76+
await (0, exports.exponentialSleep)(currentRetry);
77+
currentRetry++;
78+
}
79+
}
80+
throw error;
81+
};
82+
exports.axiosReqWithRetryOnServerError = axiosReqWithRetryOnServerError;

package-lock.json

+68-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@elastic.io/component-commons-library",
3-
"version": "3.0.0",
3+
"version": "3.0.1",
44
"description": "Library for most common component development cases",
55
"author": {
66
"name": "elastic.io GmbH",
@@ -24,7 +24,7 @@
2424
},
2525
"dependencies": {
2626
"@elastic.io/jsonata-moment": "1.1.4",
27-
"@elastic.io/maester-client": "4.0.0",
27+
"@elastic.io/maester-client": "4.0.2",
2828
"@elastic.io/ntlm-client": "1.0.0",
2929
"async": "3.2.3",
3030
"axios": "0.27.2",

spec-integration/attachmentProcessor.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { AttachmentProcessor } from '../src/attachment/AttachmentProcessor';
1010
chai.use(require('chai-as-promised'));
1111

1212
describe('AttachmentProcessor', () => {
13-
const objectStorage = new ObjectStorage({ uri: creds.uri, jwtSecret: creds.token });
14-
const attachmentProcessor = new AttachmentProcessor();
13+
const objectStorage = new ObjectStorage({ uri: creds.uri, jwtSecret: creds.token, userAgent: 'userAgent' });
14+
const attachmentProcessor = new AttachmentProcessor('userAgent');
1515
describe('uploadAttachment', () => {
1616
it('uploadAttachment (/samples/sample.json)', async () => {
1717
const getFileAsStream = async () => fs.createReadStream(path.join(__dirname, './samples/sample.json'));

spec/attachment/AttachmentProcessor.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const formStream = (dataString: string): Readable => {
1919
};
2020

2121
describe('AttachmentProcessor', () => {
22-
const attachmentProcessor = new AttachmentProcessor();
22+
const attachmentProcessor = new AttachmentProcessor('userAgent');
2323
describe('Steward', () => {
2424
it('Should successfully retrieve csv', async () => {
2525
const attachmentOptions = {

0 commit comments

Comments
 (0)