mirror of
https://github.com/fjogeleit/http-request-action.git
synced 2024-11-21 11:20:57 -05:00
retry requests
Signed-off-by: Frank Jogeleit <frank.jogeleit@lovoo.com>
This commit is contained in:
parent
5f7d5f7c54
commit
2e2dec74b5
7 changed files with 344 additions and 129 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
.vscode
|
|
@ -43,6 +43,8 @@ jobs:
|
||||||
|ignoreStatusCodes| Prevent this Action to fail if the request respond with one of the configured Status Codes. Example: '404,401' ||
|
|ignoreStatusCodes| Prevent this Action to fail if the request respond with one of the configured Status Codes. Example: '404,401' ||
|
||||||
|httpsCA| Certificate authority as string in PEM format ||
|
|httpsCA| Certificate authority as string in PEM format ||
|
||||||
|responseFile| Persist the response data to the specified file path ||
|
|responseFile| Persist the response data to the specified file path ||
|
||||||
|
|retry| optional amount of retries if the request is failing, does not retry if the status code is ignored ||
|
||||||
|
|retryWait| time between each retry in millseconds | 3000 |
|
||||||
|
|
||||||
### Response
|
### Response
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,12 @@ inputs:
|
||||||
responseFile:
|
responseFile:
|
||||||
description: 'Persist the response data to the specified file path'
|
description: 'Persist the response data to the specified file path'
|
||||||
required: false
|
required: false
|
||||||
|
retry:
|
||||||
|
description: 'optional amount of retries if the request fails'
|
||||||
|
required: false
|
||||||
|
retryWait:
|
||||||
|
description: 'wait time between retries in milliseconds'
|
||||||
|
required: false
|
||||||
outputs:
|
outputs:
|
||||||
response:
|
response:
|
||||||
description: 'HTTP Response Content'
|
description: 'HTTP Response Content'
|
||||||
|
|
273
dist/index.js
vendored
273
dist/index.js
vendored
|
@ -5056,6 +5056,91 @@ const createPersistHandler = (filePath, actions) => (response) => {
|
||||||
|
|
||||||
module.exports = { createPersistHandler }
|
module.exports = { createPersistHandler }
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 6989:
|
||||||
|
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
const { GithubActions } = __nccwpck_require__(8169);
|
||||||
|
const FormData = __nccwpck_require__(4334);
|
||||||
|
const fs = __nccwpck_require__(7147);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const convertToJSON = (value) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) || {};
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ [key: string]: string }} data
|
||||||
|
* @param {{ [key: string]: string }} files
|
||||||
|
* @param {boolean} convertPaths
|
||||||
|
*
|
||||||
|
* @returns {FormData}
|
||||||
|
*/
|
||||||
|
const convertToFormData = (data, files, convertPaths) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
formData.append(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(files)) {
|
||||||
|
formData.append(key, fs.createReadStream(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => Promise} callback
|
||||||
|
* @param {{ retry: number; sleep: number; actions: GithubActions }} options
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
const retry = async (callback, options) => {
|
||||||
|
let lastErr = null;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return await callback();
|
||||||
|
} catch (err) {
|
||||||
|
lastErr = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < options.retries) {
|
||||||
|
options.actions.warning(`#${i + 1} request failed: ${err}`);
|
||||||
|
await sleep(options.sleep);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
} while (i <= options.retry);
|
||||||
|
|
||||||
|
throw lastErr;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
convertToJSON,
|
||||||
|
convertToFormData,
|
||||||
|
retry,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 9082:
|
/***/ 9082:
|
||||||
|
@ -5065,10 +5150,11 @@ module.exports = { createPersistHandler }
|
||||||
|
|
||||||
|
|
||||||
const axios = __nccwpck_require__(8757);
|
const axios = __nccwpck_require__(8757);
|
||||||
const FormData = __nccwpck_require__(4334)
|
const FormData = __nccwpck_require__(4334);
|
||||||
const fs = __nccwpck_require__(7147)
|
const fs = __nccwpck_require__(7147);
|
||||||
const url = __nccwpck_require__(7310);
|
const url = __nccwpck_require__(7310);
|
||||||
const { GithubActions } = __nccwpck_require__(8169);
|
const { GithubActions } = __nccwpck_require__(8169);
|
||||||
|
const { convertToJSON, convertToFormData, retry } = __nccwpck_require__(6989);
|
||||||
|
|
||||||
const METHOD_GET = 'GET'
|
const METHOD_GET = 'GET'
|
||||||
const METHOD_POST = 'POST'
|
const METHOD_POST = 'POST'
|
||||||
|
@ -5085,15 +5171,21 @@ const CONTENT_TYPE_URLENCODED = 'application/x-www-form-urlencoded'
|
||||||
* @param {string} param0.files Map of Request Files (name: absolute path) as JSON String, default: {}
|
* @param {string} param0.files Map of Request Files (name: absolute path) as JSON String, default: {}
|
||||||
* @param {string} param0.file Single request file (absolute path)
|
* @param {string} param0.file Single request file (absolute path)
|
||||||
* @param {GithubActions} param0.actions
|
* @param {GithubActions} param0.actions
|
||||||
* @param {number[]} param0.ignoredCodes Prevent Action to fail if the API response with one of this StatusCodes
|
* @param {{
|
||||||
* @param {boolean} param0.preventFailureOnNoResponse Prevent Action to fail if the API respond without Response
|
* ignoredCodes: number[];
|
||||||
* @param {boolean} param0.escapeData Escape unescaped JSON content in data
|
* preventFailureOnNoResponse: boolean,
|
||||||
|
* escapeData: boolean;
|
||||||
|
* retry: number;
|
||||||
|
* retryWait: number;
|
||||||
|
* }} param0.options
|
||||||
*
|
*
|
||||||
* @returns {Promise<axios.AxiosResponse>}
|
* @returns {Promise<axios.AxiosResponse>}
|
||||||
*/
|
*/
|
||||||
const request = async({ method, instanceConfig, data, files, file, actions, ignoredCodes, preventFailureOnNoResponse, escapeData }) => {
|
const request = async({ method, instanceConfig, data, files, file, actions, options }) => {
|
||||||
|
actions.debug(`options: ${JSON.stringify(options)}`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (escapeData) {
|
if (options.escapeData) {
|
||||||
data = data.replace(/"[^"]*"/g, (match) => {
|
data = data.replace(/"[^"]*"/g, (match) => {
|
||||||
return match.replace(/[\n\r]\s*/g, "\\n");
|
return match.replace(/[\n\r]\s*/g, "\\n");
|
||||||
});
|
});
|
||||||
|
@ -5145,10 +5237,38 @@ const request = async({ method, instanceConfig, data, files, file, actions, igno
|
||||||
|
|
||||||
actions.debug('Request Data: ' + JSON.stringify(requestData))
|
actions.debug('Request Data: ' + JSON.stringify(requestData))
|
||||||
|
|
||||||
const response = await instance.request(requestData)
|
const execRequest = async () => {
|
||||||
|
try {
|
||||||
|
return await instance.request(requestData)
|
||||||
|
} catch(error) {
|
||||||
|
if (error.response && options.ignoredCodes.includes(error.response.status)) {
|
||||||
|
actions.warning(`ignored status code: ${JSON.stringify({ code: error.response.status, message: error.response.data })}`)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error.response && error.request && options.preventFailureOnNoResponse) {
|
||||||
|
actions.warning(`no response received: ${JSON.stringify(error)}`);
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {axios.AxiosResponse|null} */
|
||||||
|
const response = await retry(execRequest, {
|
||||||
|
actions,
|
||||||
|
retry: options.retry || 0,
|
||||||
|
sleep: options.retryWait // wait 3s after each retry
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
actions.setOutput('response', JSON.stringify(response.data))
|
actions.setOutput('response', JSON.stringify(response.data))
|
||||||
|
|
||||||
actions.setOutput('headers', response.headers)
|
actions.setOutput('headers', response.headers)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -5158,53 +5278,16 @@ const request = async({ method, instanceConfig, data, files, file, actions, igno
|
||||||
actions.setOutput('requestError', JSON.stringify({ name, message, code, status: response && response.status ? response.status : null }));
|
actions.setOutput('requestError', JSON.stringify({ name, message, code, status: response && response.status ? response.status : null }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.response && ignoredCodes.includes(error.response.status)) {
|
if (error.response) {
|
||||||
actions.warning(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
|
||||||
} else if (error.response) {
|
|
||||||
actions.setFailed(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
actions.setFailed(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
||||||
} else if (error.request && !preventFailureOnNoResponse) {
|
} else if (error.request) {
|
||||||
actions.setFailed(JSON.stringify({ error: "no response received" }));
|
actions.setFailed(JSON.stringify({ error: "no response received" }));
|
||||||
} else if (error.request && preventFailureOnNoResponse) {
|
|
||||||
actions.warning(JSON.stringify(error));
|
|
||||||
} else {
|
} else {
|
||||||
actions.setFailed(JSON.stringify({ message: error.message, data }));
|
actions.setFailed(JSON.stringify({ message: error.message, data }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
const convertToJSON = (value) => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(value) || {}
|
|
||||||
} catch(e) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Object} files
|
|
||||||
*
|
|
||||||
* @returns {FormData}
|
|
||||||
*/
|
|
||||||
const convertToFormData = (data, files) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
|
||||||
formData.append(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(files)) {
|
|
||||||
formData.append(key, fs.createReadStream(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ baseURL: string; timeout: number; headers: { [name: string]: string } }} instanceConfig
|
* @param {{ baseURL: string; timeout: number; headers: { [name: string]: string } }} instanceConfig
|
||||||
* @param {FormData} formData
|
* @param {FormData} formData
|
||||||
|
@ -5406,7 +5489,7 @@ module.exports = require("zlib");
|
||||||
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
// Axios v1.3.2 Copyright (c) 2023 Matt Zabriskie and contributors
|
// Axios v1.3.4 Copyright (c) 2023 Matt Zabriskie and contributors
|
||||||
|
|
||||||
|
|
||||||
const FormData$1 = __nccwpck_require__(4334);
|
const FormData$1 = __nccwpck_require__(4334);
|
||||||
|
@ -6989,11 +7072,15 @@ function isValidHeaderName(str) {
|
||||||
return /^[-_a-zA-Z]+$/.test(str.trim());
|
return /^[-_a-zA-Z]+$/.test(str.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchHeaderValue(context, value, header, filter) {
|
function matchHeaderValue(context, value, header, filter, isHeaderNameFilter) {
|
||||||
if (utils.isFunction(filter)) {
|
if (utils.isFunction(filter)) {
|
||||||
return filter.call(this, value, header);
|
return filter.call(this, value, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isHeaderNameFilter) {
|
||||||
|
value = header;
|
||||||
|
}
|
||||||
|
|
||||||
if (!utils.isString(value)) return;
|
if (!utils.isString(value)) return;
|
||||||
|
|
||||||
if (utils.isString(filter)) {
|
if (utils.isString(filter)) {
|
||||||
|
@ -7137,7 +7224,7 @@ class AxiosHeaders {
|
||||||
|
|
||||||
while (i--) {
|
while (i--) {
|
||||||
const key = keys[i];
|
const key = keys[i];
|
||||||
if(!matcher || matchHeaderValue(this, this[key], key, matcher)) {
|
if(!matcher || matchHeaderValue(this, this[key], key, matcher, true)) {
|
||||||
delete this[key];
|
delete this[key];
|
||||||
deleted = true;
|
deleted = true;
|
||||||
}
|
}
|
||||||
|
@ -7356,7 +7443,7 @@ function buildFullPath(baseURL, requestedURL) {
|
||||||
return requestedURL;
|
return requestedURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "1.3.2";
|
const VERSION = "1.3.4";
|
||||||
|
|
||||||
function parseProtocol(url) {
|
function parseProtocol(url) {
|
||||||
const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url);
|
const match = /^([-+\w]{1,25})(:?\/\/|:)/.exec(url);
|
||||||
|
@ -7918,15 +8005,39 @@ function setProxy(options, configProxy, location) {
|
||||||
|
|
||||||
const isHttpAdapterSupported = typeof process !== 'undefined' && utils.kindOf(process) === 'process';
|
const isHttpAdapterSupported = typeof process !== 'undefined' && utils.kindOf(process) === 'process';
|
||||||
|
|
||||||
|
// temporary hotfix
|
||||||
|
|
||||||
|
const wrapAsync = (asyncExecutor) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let onDone;
|
||||||
|
let isDone;
|
||||||
|
|
||||||
|
const done = (value, isRejected) => {
|
||||||
|
if (isDone) return;
|
||||||
|
isDone = true;
|
||||||
|
onDone && onDone(value, isRejected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _resolve = (value) => {
|
||||||
|
done(value);
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const _reject = (reason) => {
|
||||||
|
done(reason, true);
|
||||||
|
reject(reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
asyncExecutor(_resolve, _reject, (onDoneHandler) => (onDone = onDoneHandler)).catch(_reject);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
/*eslint consistent-return:0*/
|
/*eslint consistent-return:0*/
|
||||||
const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) {
|
const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) {
|
||||||
/*eslint no-async-promise-executor:0*/
|
return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) {
|
||||||
return new Promise(async function dispatchHttpRequest(resolvePromise, rejectPromise) {
|
let {data} = config;
|
||||||
let data = config.data;
|
const {responseType, responseEncoding} = config;
|
||||||
const responseType = config.responseType;
|
|
||||||
const responseEncoding = config.responseEncoding;
|
|
||||||
const method = config.method.toUpperCase();
|
const method = config.method.toUpperCase();
|
||||||
let isFinished;
|
|
||||||
let isDone;
|
let isDone;
|
||||||
let rejected = false;
|
let rejected = false;
|
||||||
let req;
|
let req;
|
||||||
|
@ -7934,10 +8045,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) {
|
||||||
// temporary internal emitter until the AxiosRequest class will be implemented
|
// temporary internal emitter until the AxiosRequest class will be implemented
|
||||||
const emitter = new EventEmitter__default["default"]();
|
const emitter = new EventEmitter__default["default"]();
|
||||||
|
|
||||||
function onFinished() {
|
const onFinished = () => {
|
||||||
if (isFinished) return;
|
|
||||||
isFinished = true;
|
|
||||||
|
|
||||||
if (config.cancelToken) {
|
if (config.cancelToken) {
|
||||||
config.cancelToken.unsubscribe(abort);
|
config.cancelToken.unsubscribe(abort);
|
||||||
}
|
}
|
||||||
|
@ -7947,28 +8055,15 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter.removeAllListeners();
|
emitter.removeAllListeners();
|
||||||
}
|
};
|
||||||
|
|
||||||
function done(value, isRejected) {
|
|
||||||
if (isDone) return;
|
|
||||||
|
|
||||||
|
onDone((value, isRejected) => {
|
||||||
isDone = true;
|
isDone = true;
|
||||||
|
|
||||||
if (isRejected) {
|
if (isRejected) {
|
||||||
rejected = true;
|
rejected = true;
|
||||||
onFinished();
|
onFinished();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
isRejected ? rejectPromise(value) : resolvePromise(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolve = function resolve(value) {
|
|
||||||
done(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const reject = function reject(value) {
|
|
||||||
done(value, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
function abort(reason) {
|
function abort(reason) {
|
||||||
emitter.emit('abort', !reason || reason.type ? new CanceledError(null, config, req) : reason);
|
emitter.emit('abort', !reason || reason.type ? new CanceledError(null, config, req) : reason);
|
||||||
|
@ -8066,7 +8161,7 @@ const httpAdapter = isHttpAdapterSupported && function httpAdapter(config) {
|
||||||
if (!headers.hasContentLength()) {
|
if (!headers.hasContentLength()) {
|
||||||
try {
|
try {
|
||||||
const knownLength = await util__default["default"].promisify(data.getLength).call(data);
|
const knownLength = await util__default["default"].promisify(data.getLength).call(data);
|
||||||
headers.setContentLength(knownLength);
|
Number.isFinite(knownLength) && knownLength >= 0 && headers.setContentLength(knownLength);
|
||||||
/*eslint no-empty:0*/
|
/*eslint no-empty:0*/
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
@ -9683,6 +9778,16 @@ if (!!core.getInput('username') || !!core.getInput('password')) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let retry = 0
|
||||||
|
if (!!core.getInput('retry')) {
|
||||||
|
retry = parseInt(core.getInput('retry'))
|
||||||
|
}
|
||||||
|
|
||||||
|
let retryWait = 3000
|
||||||
|
if (!!core.getInput('retryWait')) {
|
||||||
|
retry = parseInt(core.getInput('retryWait'))
|
||||||
|
}
|
||||||
|
|
||||||
const data = core.getInput('data') || '{}';
|
const data = core.getInput('data') || '{}';
|
||||||
const files = core.getInput('files') || '{}';
|
const files = core.getInput('files') || '{}';
|
||||||
const file = core.getInput('file')
|
const file = core.getInput('file')
|
||||||
|
@ -9705,7 +9810,15 @@ if (!!responseFile) {
|
||||||
handler.push(createPersistHandler(responseFile, actions))
|
handler.push(createPersistHandler(responseFile, actions))
|
||||||
}
|
}
|
||||||
|
|
||||||
request({ data, method, instanceConfig, preventFailureOnNoResponse, escapeData, files, file, ignoredCodes, actions }).then(response => {
|
const options = {
|
||||||
|
ignoredCodes,
|
||||||
|
preventFailureOnNoResponse,
|
||||||
|
escapeData,
|
||||||
|
retry,
|
||||||
|
retryWait
|
||||||
|
}
|
||||||
|
|
||||||
|
request({ data, method, instanceConfig, files, file, actions, options }).then(response => {
|
||||||
if (typeof response == 'object') {
|
if (typeof response == 'object') {
|
||||||
handler.forEach(h => h(response))
|
handler.forEach(h => h(response))
|
||||||
}
|
}
|
||||||
|
|
77
src/helper.js
Normal file
77
src/helper.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { GithubActions } = require('./githubActions');
|
||||||
|
const FormData = require('form-data');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
const convertToJSON = (value) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value) || {};
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ [key: string]: string }} data
|
||||||
|
* @param {{ [key: string]: string }} files
|
||||||
|
* @param {boolean} convertPaths
|
||||||
|
*
|
||||||
|
* @returns {FormData}
|
||||||
|
*/
|
||||||
|
const convertToFormData = (data, files, convertPaths) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
formData.append(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(files)) {
|
||||||
|
formData.append(key, fs.createReadStream(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {() => Promise} callback
|
||||||
|
* @param {{ retry: number; sleep: number; actions: GithubActions }} options
|
||||||
|
*
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
const retry = async (callback, options) => {
|
||||||
|
let lastErr = null;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
return await callback();
|
||||||
|
} catch (err) {
|
||||||
|
lastErr = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < options.retries) {
|
||||||
|
options.actions.warning(`#${i + 1} request failed: ${err}`);
|
||||||
|
await sleep(options.sleep);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
} while (i <= options.retry);
|
||||||
|
|
||||||
|
throw lastErr;
|
||||||
|
};
|
||||||
|
|
||||||
|
function sleep(milliseconds) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
convertToJSON,
|
||||||
|
convertToFormData,
|
||||||
|
retry,
|
||||||
|
};
|
|
@ -1,10 +1,11 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const FormData = require('form-data')
|
const FormData = require('form-data');
|
||||||
const fs = require('fs')
|
const fs = require('fs');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const { GithubActions } = require('./githubActions');
|
const { GithubActions } = require('./githubActions');
|
||||||
|
const { convertToJSON, convertToFormData, retry } = require('./helper');
|
||||||
|
|
||||||
const METHOD_GET = 'GET'
|
const METHOD_GET = 'GET'
|
||||||
const METHOD_POST = 'POST'
|
const METHOD_POST = 'POST'
|
||||||
|
@ -21,15 +22,21 @@ const CONTENT_TYPE_URLENCODED = 'application/x-www-form-urlencoded'
|
||||||
* @param {string} param0.files Map of Request Files (name: absolute path) as JSON String, default: {}
|
* @param {string} param0.files Map of Request Files (name: absolute path) as JSON String, default: {}
|
||||||
* @param {string} param0.file Single request file (absolute path)
|
* @param {string} param0.file Single request file (absolute path)
|
||||||
* @param {GithubActions} param0.actions
|
* @param {GithubActions} param0.actions
|
||||||
* @param {number[]} param0.ignoredCodes Prevent Action to fail if the API response with one of this StatusCodes
|
* @param {{
|
||||||
* @param {boolean} param0.preventFailureOnNoResponse Prevent Action to fail if the API respond without Response
|
* ignoredCodes: number[];
|
||||||
* @param {boolean} param0.escapeData Escape unescaped JSON content in data
|
* preventFailureOnNoResponse: boolean,
|
||||||
|
* escapeData: boolean;
|
||||||
|
* retry: number;
|
||||||
|
* retryWait: number;
|
||||||
|
* }} param0.options
|
||||||
*
|
*
|
||||||
* @returns {Promise<axios.AxiosResponse>}
|
* @returns {Promise<axios.AxiosResponse>}
|
||||||
*/
|
*/
|
||||||
const request = async({ method, instanceConfig, data, files, file, actions, ignoredCodes, preventFailureOnNoResponse, escapeData }) => {
|
const request = async({ method, instanceConfig, data, files, file, actions, options }) => {
|
||||||
|
actions.debug(`options: ${JSON.stringify(options)}`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (escapeData) {
|
if (options.escapeData) {
|
||||||
data = data.replace(/"[^"]*"/g, (match) => {
|
data = data.replace(/"[^"]*"/g, (match) => {
|
||||||
return match.replace(/[\n\r]\s*/g, "\\n");
|
return match.replace(/[\n\r]\s*/g, "\\n");
|
||||||
});
|
});
|
||||||
|
@ -81,10 +88,38 @@ const request = async({ method, instanceConfig, data, files, file, actions, igno
|
||||||
|
|
||||||
actions.debug('Request Data: ' + JSON.stringify(requestData))
|
actions.debug('Request Data: ' + JSON.stringify(requestData))
|
||||||
|
|
||||||
const response = await instance.request(requestData)
|
const execRequest = async () => {
|
||||||
|
try {
|
||||||
|
return await instance.request(requestData)
|
||||||
|
} catch(error) {
|
||||||
|
if (error.response && options.ignoredCodes.includes(error.response.status)) {
|
||||||
|
actions.warning(`ignored status code: ${JSON.stringify({ code: error.response.status, message: error.response.data })}`)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error.response && error.request && options.preventFailureOnNoResponse) {
|
||||||
|
actions.warning(`no response received: ${JSON.stringify(error)}`);
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {axios.AxiosResponse|null} */
|
||||||
|
const response = await retry(execRequest, {
|
||||||
|
actions,
|
||||||
|
retry: options.retry || 0,
|
||||||
|
sleep: options.retryWait // wait 3s after each retry
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
actions.setOutput('response', JSON.stringify(response.data))
|
actions.setOutput('response', JSON.stringify(response.data))
|
||||||
|
|
||||||
actions.setOutput('headers', response.headers)
|
actions.setOutput('headers', response.headers)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -94,53 +129,16 @@ const request = async({ method, instanceConfig, data, files, file, actions, igno
|
||||||
actions.setOutput('requestError', JSON.stringify({ name, message, code, status: response && response.status ? response.status : null }));
|
actions.setOutput('requestError', JSON.stringify({ name, message, code, status: response && response.status ? response.status : null }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.response && ignoredCodes.includes(error.response.status)) {
|
if (error.response) {
|
||||||
actions.warning(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
|
||||||
} else if (error.response) {
|
|
||||||
actions.setFailed(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
actions.setFailed(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
||||||
} else if (error.request && !preventFailureOnNoResponse) {
|
} else if (error.request) {
|
||||||
actions.setFailed(JSON.stringify({ error: "no response received" }));
|
actions.setFailed(JSON.stringify({ error: "no response received" }));
|
||||||
} else if (error.request && preventFailureOnNoResponse) {
|
|
||||||
actions.warning(JSON.stringify(error));
|
|
||||||
} else {
|
} else {
|
||||||
actions.setFailed(JSON.stringify({ message: error.message, data }));
|
actions.setFailed(JSON.stringify({ message: error.message, data }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
const convertToJSON = (value) => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(value) || {}
|
|
||||||
} catch(e) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Object} files
|
|
||||||
*
|
|
||||||
* @returns {FormData}
|
|
||||||
*/
|
|
||||||
const convertToFormData = (data, files) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
|
||||||
formData.append(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(files)) {
|
|
||||||
formData.append(key, fs.createReadStream(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
return formData
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ baseURL: string; timeout: number; headers: { [name: string]: string } }} instanceConfig
|
* @param {{ baseURL: string; timeout: number; headers: { [name: string]: string } }} instanceConfig
|
||||||
* @param {FormData} formData
|
* @param {FormData} formData
|
||||||
|
|
20
src/index.js
20
src/index.js
|
@ -43,6 +43,16 @@ if (!!core.getInput('username') || !!core.getInput('password')) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let retry = 0
|
||||||
|
if (!!core.getInput('retry')) {
|
||||||
|
retry = parseInt(core.getInput('retry'))
|
||||||
|
}
|
||||||
|
|
||||||
|
let retryWait = 3000
|
||||||
|
if (!!core.getInput('retryWait')) {
|
||||||
|
retry = parseInt(core.getInput('retryWait'))
|
||||||
|
}
|
||||||
|
|
||||||
const data = core.getInput('data') || '{}';
|
const data = core.getInput('data') || '{}';
|
||||||
const files = core.getInput('files') || '{}';
|
const files = core.getInput('files') || '{}';
|
||||||
const file = core.getInput('file')
|
const file = core.getInput('file')
|
||||||
|
@ -65,7 +75,15 @@ if (!!responseFile) {
|
||||||
handler.push(createPersistHandler(responseFile, actions))
|
handler.push(createPersistHandler(responseFile, actions))
|
||||||
}
|
}
|
||||||
|
|
||||||
request({ data, method, instanceConfig, preventFailureOnNoResponse, escapeData, files, file, ignoredCodes, actions }).then(response => {
|
const options = {
|
||||||
|
ignoredCodes,
|
||||||
|
preventFailureOnNoResponse,
|
||||||
|
escapeData,
|
||||||
|
retry,
|
||||||
|
retryWait
|
||||||
|
}
|
||||||
|
|
||||||
|
request({ data, method, instanceConfig, files, file, actions, options }).then(response => {
|
||||||
if (typeof response == 'object') {
|
if (typeof response == 'object') {
|
||||||
handler.forEach(h => h(response))
|
handler.forEach(h => h(response))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue