Skip to content

Commit 79a72a6

Browse files
authored
feat: support configure a proxy url (#197)
* feat: support configure a proxy url Signed-off-by: Ruben Romero Montes <[email protected]> * feat: add tests for proxy Signed-off-by: Ruben Romero Montes <[email protected]> --------- Signed-off-by: Ruben Romero Montes <[email protected]>
1 parent 7c73819 commit 79a72a6

File tree

4 files changed

+128
-10
lines changed

4 files changed

+128
-10
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@ http_requests
66
json_responses
77
integration/**/package-lock.json
88
unit-tests-result.json
9-
.gradle
9+
.gradle
10+
build
11+
target

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,9 @@ let options = {
307307
'EXHORT_PIP3_PATH' : '/path/to/pip3',
308308
'EXHORT_PYTHON_PATH' : '/path/to/python',
309309
'EXHORT_PIP_PATH' : '/path/to/pip',
310-
'EXHORT_GRADLE_PATH' : '/path/to/gradle'
311-
310+
'EXHORT_GRADLE_PATH' : '/path/to/gradle',
311+
// Configure proxy for all requests
312+
'EXHORT_PROXY_URL': 'http://proxy.example.com:8080'
312313
}
313314

314315
// Get stack analysis in JSON format ( all package managers, pom.xml is as an example here)
@@ -322,6 +323,27 @@ let componentAnalysis = await exhort.componentAnalysis('/path/to/pom.xml', optio
322323
**_Environment variables takes precedence._**
323324
</p>
324325

326+
<h4>Proxy Configuration</h4>
327+
<p>
328+
You can configure a proxy for all HTTP/HTTPS requests made by the API. This is useful when your environment requires going through a proxy to access external services.
329+
330+
You can set the proxy URL in two ways:
331+
332+
1. Using environment variable:
333+
```shell
334+
export EXHORT_PROXY_URL=http://proxy.example.com:8080
335+
```
336+
337+
2. Using the options object when calling the API programmatically:
338+
```javascript
339+
const options = {
340+
'EXHORT_PROXY_URL': 'http://proxy.example.com:8080'
341+
}
342+
```
343+
344+
The proxy URL should be in the format: `http://host:port` or `https://host:port`. The API will automatically use the appropriate protocol (HTTP or HTTPS) based on the proxy URL provided.
345+
</p>
346+
325347
<h4>Customizing Executables</h4>
326348
<p>
327349
This project uses each ecosystem's executable for creating dependency trees. These executables are expected to be

src/analysis.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,32 @@ import fs from "node:fs";
22
import path from "node:path";
33
import {EOL} from "os";
44
import {RegexNotToBeLogged, getCustom} from "./tools.js";
5+
import http from 'node:http';
6+
import https from 'node:https';
57

68
export default { requestComponent, requestStack, validateToken }
79

810
const rhdaTokenHeader = "rhda-token";
911
const rhdaSourceHeader = "rhda-source"
1012
const rhdaOperationTypeHeader = "rhda-operation-type"
1113

14+
/**
15+
* Adds proxy agent configuration to fetch options if a proxy URL is specified
16+
* @param {Object} options - The base fetch options
17+
* @param {Object} opts - The exhort options that may contain proxy configuration
18+
* @returns {Object} The fetch options with proxy agent if applicable
19+
*/
20+
function addProxyAgent(options, opts) {
21+
const proxyUrl = getCustom('EXHORT_PROXY_URL', null, opts);
22+
if (proxyUrl) {
23+
const proxyUrlObj = new URL(proxyUrl);
24+
options.agent = proxyUrlObj.protocol === 'https:'
25+
? new https.Agent({ proxy: proxyUrl })
26+
: new http.Agent({ proxy: proxyUrl });
27+
}
28+
return options;
29+
}
30+
1231
/**
1332
* Send a stack analysis request and get the report as 'text/html' or 'application/json'.
1433
* @param {import('./provider').Provider | import('./providers/base_java.js').default } provider - the provided data for constructing the request
@@ -29,15 +48,18 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
2948
if (process.env["EXHORT_DEBUG"] === "true") {
3049
console.log("Starting time of sending stack analysis request to exhort server= " + startTime)
3150
}
32-
let resp = await fetch(`${url}/api/v4/analysis`, {
51+
52+
const fetchOptions = addProxyAgent({
3353
method: 'POST',
3454
headers: {
3555
'Accept': html ? 'text/html' : 'application/json',
3656
'Content-Type': provided.contentType,
3757
...getTokenHeaders(opts)
3858
},
3959
body: provided.content
40-
})
60+
}, opts);
61+
62+
let resp = await fetch(`${url}/api/v4/analysis`, fetchOptions)
4163
let result
4264
if(resp.status === 200) {
4365
if (!html) {
@@ -82,15 +104,18 @@ async function requestComponent(provider, manifest, url, opts = {}) {
82104
if (process.env["EXHORT_DEBUG"] === "true") {
83105
console.log("Starting time of sending component analysis request to exhort server= " + new Date())
84106
}
85-
let resp = await fetch(`${url}/api/v4/analysis`, {
107+
108+
const fetchOptions = addProxyAgent({
86109
method: 'POST',
87110
headers: {
88111
'Accept': 'application/json',
89112
'Content-Type': provided.contentType,
90113
...getTokenHeaders(opts),
91114
},
92115
body: provided.content
93-
})
116+
}, opts);
117+
118+
let resp = await fetch(`${url}/api/v4/analysis`, fetchOptions)
94119
let result
95120
if(resp.status === 200) {
96121
result = await resp.json()
@@ -119,13 +144,14 @@ async function requestComponent(provider, manifest, url, opts = {}) {
119144
* @return {Promise<number>} return the HTTP status Code of the response from the validate token request.
120145
*/
121146
async function validateToken(url, opts = {}) {
122-
let resp = await fetch(`${url}/api/v4/token`, {
147+
const fetchOptions = addProxyAgent({
123148
method: 'GET',
124149
headers: {
125-
// 'Accept': 'text/plain',
126150
...getTokenHeaders(opts),
127151
}
128-
})
152+
}, opts);
153+
154+
let resp = await fetch(`${url}/api/v4/token`, fetchOptions)
129155
if (process.env["EXHORT_DEBUG"] === "true") {
130156
let exRequestId = resp.headers.get("ex-request-id");
131157
if (exRequestId) {

test/analysis.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,72 @@ suite('testing the analysis module for sending api requests', () => {
202202
}
203203
))
204204
})
205+
206+
suite('verify proxy configuration', () => {
207+
let fakeManifest = 'fake-file.typ'
208+
let stackProviderStub = sinon.stub()
209+
stackProviderStub.withArgs(fakeManifest).returns(fakeProvided)
210+
let fakeProvider = {
211+
provideComponent: () => {},
212+
provideStack: stackProviderStub,
213+
isSupported: () => {}
214+
};
215+
216+
afterEach(() => {
217+
delete process.env['EXHORT_PROXY_URL']
218+
})
219+
220+
test('when HTTP proxy is configured, verify agent is set correctly', interceptAndRun(
221+
rest.post(`${backendUrl}/api/v3/analysis`, (req, res, ctx) => {
222+
// The request should go through the proxy
223+
return res(ctx.json({ok: 'ok'}))
224+
}),
225+
async () => {
226+
const httpProxyUrl = 'http://proxy.example.com:8080'
227+
const options = {
228+
'EXHORT_PROXY_URL': httpProxyUrl
229+
}
230+
let res = await analysis.requestStack(fakeProvider, fakeManifest, backendUrl, false, options)
231+
expect(res).to.deep.equal({ok: 'ok'})
232+
}
233+
))
234+
235+
test('when HTTPS proxy is configured, verify agent is set correctly', interceptAndRun(
236+
rest.post(`${backendUrl}/api/v3/analysis`, (req, res, ctx) => {
237+
// The request should go through the proxy
238+
return res(ctx.json({ok: 'ok'}))
239+
}),
240+
async () => {
241+
const httpsProxyUrl = 'https://proxy.example.com:8080'
242+
const options = {
243+
'EXHORT_PROXY_URL': httpsProxyUrl
244+
}
245+
let res = await analysis.requestStack(fakeProvider, fakeManifest, backendUrl, false, options)
246+
expect(res).to.deep.equal({ok: 'ok'})
247+
}
248+
))
249+
250+
test('when proxy is configured via environment variable, verify agent is set correctly', interceptAndRun(
251+
rest.post(`${backendUrl}/api/v3/analysis`, (req, res, ctx) => {
252+
// The request should go through the proxy
253+
return res(ctx.json({ok: 'ok'}))
254+
}),
255+
async () => {
256+
process.env['EXHORT_PROXY_URL'] = 'http://proxy.example.com:8080'
257+
let res = await analysis.requestStack(fakeProvider, fakeManifest, backendUrl)
258+
expect(res).to.deep.equal({ok: 'ok'})
259+
}
260+
))
261+
262+
test('when no proxy is configured, verify no agent is set', interceptAndRun(
263+
rest.post(`${backendUrl}/api/v3/analysis`, (req, res, ctx) => {
264+
// The request should go directly without proxy
265+
return res(ctx.json({ok: 'ok'}))
266+
}),
267+
async () => {
268+
let res = await analysis.requestStack(fakeProvider, fakeManifest, backendUrl)
269+
expect(res).to.deep.equal({ok: 'ok'})
270+
}
271+
))
272+
})
205273
})

0 commit comments

Comments
 (0)