mirror of
https://github.com/fjogeleit/http-request-action.git
synced 2026-02-05 17:45:55 +08:00
Compare commits
45 Commits
v1.13.0
...
support-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f415ec7c5 | ||
|
|
25a5a55111 | ||
|
|
e1affb38cd | ||
|
|
3abafba399 | ||
|
|
17b52dc74f | ||
|
|
04de458128 | ||
|
|
6769207b0b | ||
|
|
37cfb73c02 | ||
|
|
7273a89218 | ||
|
|
d08c0d1c88 | ||
|
|
9580c192fd | ||
|
|
41c615e3c0 | ||
|
|
845e3400a7 | ||
|
|
6871522388 | ||
|
|
17c1694300 | ||
|
|
baf3fe8b8b | ||
|
|
3d8af5c847 | ||
|
|
35986be8f9 | ||
|
|
3c98f6caf2 | ||
|
|
389f30ee3a | ||
|
|
e9345a1e67 | ||
|
|
894ad39332 | ||
|
|
25fb88fa43 | ||
|
|
eab8015483 | ||
|
|
f62db86b8d | ||
|
|
ef8ec33f46 | ||
|
|
c431724973 | ||
|
|
a9fc010566 | ||
|
|
8cbbec6c93 | ||
|
|
20c7f757a1 | ||
|
|
5e4203c2ac | ||
|
|
6659346e66 | ||
|
|
fc0207c5aa | ||
|
|
394beeafea | ||
|
|
e8dd067b83 | ||
|
|
a7bd4f21e5 | ||
|
|
2e2dec74b5 | ||
|
|
5f7d5f7c54 | ||
|
|
991c07d6d9 | ||
|
|
5500a62817 | ||
|
|
81ecdf1750 | ||
|
|
04b426e25f | ||
|
|
fc435761fc | ||
|
|
12f71e545a | ||
|
|
edf33a9d70 |
4
.github/workflows/build-action.yml
vendored
4
.github/workflows/build-action.yml
vendored
@@ -14,10 +14,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -26,10 +26,10 @@ jobs:
|
||||
echo "branch=$branch" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.ref.outputs.branch }}
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Install dependencies
|
||||
@@ -56,10 +56,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.17.0'
|
||||
- name: Build action
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- integrity
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
@@ -123,21 +123,31 @@ jobs:
|
||||
|
||||
- name: Create Test File
|
||||
run: |
|
||||
echo "test" > testfile.txt
|
||||
echo "test" > testfile1.txt
|
||||
echo "test" > testfile2.txt
|
||||
|
||||
- name: Request Postman Echo POST Multipart
|
||||
uses: ./
|
||||
with:
|
||||
url: 'https://postman-echo.com/post'
|
||||
method: 'POST'
|
||||
data: '{ "key": "value" }'
|
||||
files: '{ "file": "${{ github.workspace }}/testfile.txt" }'
|
||||
files: '{ "file": "${{ github.workspace }}/testfile1.txt" }'
|
||||
|
||||
- name: Request Postman Echo POST Multipart File Array
|
||||
uses: ./
|
||||
with:
|
||||
url: 'https://postman-echo.com/post'
|
||||
method: 'POST'
|
||||
data: '{ "key": "value" }'
|
||||
files: '{ "file": ["${{ github.workspace }}/testfile1.txt", "${{ github.workspace }}/testfile2.txt"] }'
|
||||
|
||||
- name: Request Postman Echo POST and persist response
|
||||
uses: ./
|
||||
with:
|
||||
url: 'https://postman-echo.com/post'
|
||||
method: 'POST'
|
||||
file: "${{ github.workspace }}/testfile.txt"
|
||||
file: "${{ github.workspace }}/testfile1.txt"
|
||||
responseFile: "${{ github.workspace }}/response.json"
|
||||
- name: Output responseFile
|
||||
run: |
|
||||
@@ -148,14 +158,14 @@ jobs:
|
||||
with:
|
||||
url: 'https://postman-echo.com/post'
|
||||
method: 'POST'
|
||||
files: '{ "file": "${{ github.workspace }}/testfile.txt" }'
|
||||
files: '{ "file": "${{ github.workspace }}/testfile1.txt" }'
|
||||
|
||||
- name: Request Postman Echo POST single file
|
||||
uses: ./
|
||||
with:
|
||||
url: 'https://postman-echo.com/post'
|
||||
method: 'POST'
|
||||
file: "${{ github.workspace }}/testfile.txt"
|
||||
file: "${{ github.workspace }}/testfile1.txt"
|
||||
|
||||
- name: Request Postman Echo POST URLEncoded string data
|
||||
uses: ./
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
node_modules
|
||||
.vscode
|
||||
@@ -42,7 +42,11 @@ jobs:
|
||||
|preventFailureOnNoResponse| Prevent this Action to fail if the request respond without an response. Use 'true' (string) as value to enable it ||
|
||||
|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 ||
|
||||
|httpsCert| Client Certificate as string ||
|
||||
|httpsKey| Client Certificate Key as string ||
|
||||
|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
|
||||
|
||||
|
||||
12
action.yml
12
action.yml
@@ -50,9 +50,21 @@ inputs:
|
||||
httpsCA:
|
||||
description: 'Certificate authority as string in PEM format'
|
||||
required: false
|
||||
httpsCert:
|
||||
description: 'Client Certificate as string'
|
||||
required: false
|
||||
httpsKey:
|
||||
description: 'Client Certificate Key as string'
|
||||
required: false
|
||||
responseFile:
|
||||
description: 'Persist the response data to the specified file path'
|
||||
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:
|
||||
response:
|
||||
description: 'HTTP Response Content'
|
||||
|
||||
@@ -42,10 +42,14 @@ request({
|
||||
data: argv.data,
|
||||
method: argv.method,
|
||||
instanceConfig,
|
||||
preventFailureOnNoResponse: false,
|
||||
escapeData: false,
|
||||
files: argv.files,
|
||||
file: argv.file,
|
||||
ignoredCodes: [],
|
||||
actions: new LogActions()
|
||||
actions: new LogActions(),
|
||||
options: {
|
||||
ignoredCodes: [],
|
||||
escapeData: false,
|
||||
preventFailureOnNoResponse: false,
|
||||
retry: 0,
|
||||
retryWait: 0
|
||||
}
|
||||
})
|
||||
|
||||
24198
dist/index.js
vendored
24198
dist/index.js
vendored
File diff suppressed because one or more lines are too long
3061
package-lock.json
generated
3061
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "http-request-action",
|
||||
"version": "1.11.2",
|
||||
"version": "1.14.2",
|
||||
"description": "",
|
||||
"main": "src/index.js",
|
||||
"private": false,
|
||||
@@ -19,13 +19,15 @@
|
||||
},
|
||||
"homepage": "https://github.com/fjogeleit/http-request-action#readme",
|
||||
"devDependencies": {
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"axios": "^1.2",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"axios": "^1.6",
|
||||
"form-data": "^4.0.0",
|
||||
"yargs": "^17.6.2"
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0"
|
||||
"@actions/core": "^1.10.1",
|
||||
"install": "^0.13.0",
|
||||
"npm": "^10.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
|
||||
81
src/helper.js
Normal file
81
src/helper.js
Normal file
@@ -0,0 +1,81 @@
|
||||
'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 = async (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)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(v => formData.append(key, fs.createReadStream(v)))
|
||||
} else {
|
||||
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'
|
||||
|
||||
const axios = require('axios');
|
||||
const FormData = require('form-data')
|
||||
const fs = require('fs')
|
||||
const FormData = require('form-data');
|
||||
const fs = require('fs');
|
||||
const url = require('url');
|
||||
const { GithubActions } = require('./githubActions');
|
||||
const { convertToJSON, convertToFormData, retry } = require('./helper');
|
||||
|
||||
const METHOD_GET = 'GET'
|
||||
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.file Single request file (absolute path)
|
||||
* @param {GithubActions} param0.actions
|
||||
* @param {number[]} param0.ignoredCodes Prevent Action to fail if the API response with one of this StatusCodes
|
||||
* @param {boolean} param0.preventFailureOnNoResponse Prevent Action to fail if the API respond without Response
|
||||
* @param {boolean} param0.escapeData Escape unescaped JSON content in data
|
||||
* @param {{
|
||||
* ignoredCodes: number[];
|
||||
* preventFailureOnNoResponse: boolean,
|
||||
* escapeData: boolean;
|
||||
* retry: number;
|
||||
* retryWait: number;
|
||||
* }} param0.options
|
||||
*
|
||||
* @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 {
|
||||
if (escapeData) {
|
||||
if (options.escapeData) {
|
||||
data = data.replace(/"[^"]*"/g, (match) => {
|
||||
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))
|
||||
|
||||
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 time after each retry
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
return null
|
||||
}
|
||||
|
||||
actions.setOutput('response', JSON.stringify(response.data))
|
||||
|
||||
actions.setOutput('headers', response.headers)
|
||||
|
||||
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 }));
|
||||
}
|
||||
|
||||
if (error.response && ignoredCodes.includes(error.response.status)) {
|
||||
actions.warning(JSON.stringify({ code: error.response.status, message: error.response.data }))
|
||||
} else if (error.response) {
|
||||
if (error.response) {
|
||||
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" }));
|
||||
} else if (error.request && preventFailureOnNoResponse) {
|
||||
actions.warning(JSON.stringify(error));
|
||||
} else {
|
||||
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 {FormData} formData
|
||||
|
||||
31
src/index.js
31
src/index.js
@@ -13,7 +13,8 @@ if (!!core.getInput('customHeaders')) {
|
||||
try {
|
||||
customHeaders = JSON.parse(core.getInput('customHeaders'));
|
||||
} catch(error) {
|
||||
core.error('Could not parse customHeaders string value')
|
||||
core.debug(`Invalid customHeaders string: ${core.getInput('customHeaders')}`)
|
||||
core.error(`Could not parse customHeaders string value: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +31,12 @@ const instanceConfig = {
|
||||
headers: { ...headers, ...customHeaders }
|
||||
}
|
||||
|
||||
if (!!core.getInput('httpsCA')) {
|
||||
instanceConfig.httpsAgent = new https.Agent({ ca: core.getInput('httpsCA') })
|
||||
if (!!core.getInput('httpsCA') || !!core.getInput('httpsCert')) {
|
||||
instanceConfig.httpsAgent = new https.Agent({
|
||||
ca: core.getInput('httpsCA') || undefined,
|
||||
cert: core.getInput('httpsCert') || undefined,
|
||||
key: core.getInput('httpsKey') || undefined
|
||||
})
|
||||
}
|
||||
|
||||
if (!!core.getInput('username') || !!core.getInput('password')) {
|
||||
@@ -43,6 +48,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 files = core.getInput('files') || '{}';
|
||||
const file = core.getInput('file')
|
||||
@@ -65,7 +80,15 @@ if (!!responseFile) {
|
||||
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') {
|
||||
handler.forEach(h => h(response))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user