diff --git a/__tests__/proxy.test.ts b/__tests__/proxy.test.ts index 45ef0b0..b766f0c 100644 --- a/__tests__/proxy.test.ts +++ b/__tests__/proxy.test.ts @@ -1,44 +1,68 @@ +import * as http from 'http' +import * as httpm from '../_out'; import * as pm from '../_out/proxy'; +import * as proxy from 'proxy' import * as url from 'url'; -describe('proxy', () => { - beforeEach(() => { +let _proxyConnects: string[] +let _proxyServer: http.Server +let _proxyUrl = 'http://127.0.0.1:8080' +describe('proxy', () => { + beforeAll(async () => { + // Start proxy server + _proxyServer = proxy() + await new Promise((resolve) => { + const port = Number(_proxyUrl.split(':')[2]) + _proxyServer.listen(port, () => resolve()) + }) + _proxyServer.on('connect', (req) => { + _proxyConnects.push(req.url) + }); + }) + + beforeEach(() => { + _proxyConnects = [] + _clearVars() }) afterEach(() => { - }) - + + afterAll(async() => { + _clearVars() + + // Stop proxy server + await new Promise((resolve) => { + _proxyServer.once('close', () => resolve()) + _proxyServer.close() + }) + }) + it('does not return proxyUrl if variables not set', () => { - _clearVars(); let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); expect(proxyUrl).toBeUndefined(); }) it('returns proxyUrl if https_proxy set for https url', () => { - _clearVars(); process.env["https_proxy"] = "https://myproxysvr"; let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); expect(proxyUrl).toBeDefined(); }) it('does not return proxyUrl if http_proxy set for https url', () => { - _clearVars(); process.env["http_proxy"] = "https://myproxysvr"; let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); expect(proxyUrl).toBeUndefined(); }) it('returns proxyUrl if http_proxy set for http url', () => { - _clearVars(); process.env["http_proxy"] = "http://myproxysvr"; let proxyUrl = pm.getProxyUrl(url.parse('http://github.com')); expect(proxyUrl).toBeDefined(); }) it('does not return proxyUrl if only host as no_proxy list', () => { - _clearVars(); process.env["https_proxy"] = "https://myproxysvr"; process.env["no_proxy"] = "myserver" let proxyUrl = pm.getProxyUrl(url.parse('https://myserver')); @@ -46,7 +70,6 @@ describe('proxy', () => { }) it('does not return proxyUrl if host in no_proxy list', () => { - _clearVars(); process.env["https_proxy"] = "https://myproxysvr"; process.env["no_proxy"] = "otherserver,myserver,anotherserver:8080" let proxyUrl = pm.getProxyUrl(url.parse('https://myserver')); @@ -54,7 +77,6 @@ describe('proxy', () => { }) it('does not return proxyUrl if host in no_proxy list with spaces', () => { - _clearVars(); process.env["https_proxy"] = "https://myproxysvr"; process.env["no_proxy"] = "otherserver, myserver ,anotherserver:8080" let proxyUrl = pm.getProxyUrl(url.parse('https://myserver')); @@ -62,15 +84,13 @@ describe('proxy', () => { }) it('does not return proxyUrl if host in no_proxy list with ports', () => { - _clearVars(); process.env["https_proxy"] = "https://myproxysvr"; process.env["no_proxy"] = "otherserver, myserver:8080 ,anotherserver" let proxyUrl = pm.getProxyUrl(url.parse('https://myserver:8080')); expect(proxyUrl).toBeUndefined(); - }) + }) it('returns proxyUrl if https_proxy set and not in no_proxy list', () => { - _clearVars(); process.env["https_proxy"] = "https://myproxysvr"; process.env["no_proxy"] = "otherserver, myserver ,anotherserver:8080" let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); @@ -78,12 +98,57 @@ describe('proxy', () => { }) it('returns proxyUrl if https_proxy set empty no_proxy set', () => { - _clearVars(); process.env["https_proxy"] = "https://myproxysvr"; process.env["no_proxy"] = "" let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); expect(proxyUrl).toBeDefined(); - }) + }) + + it('does basic http get request through proxy', async () => { + process.env['http_proxy'] = _proxyUrl + const httpClient = new httpm.HttpClient(); + let res: httpm.HttpClientResponse = await httpClient.get('http://httpbin.org/get'); + expect(res.message.statusCode).toBe(200); + let body: string = await res.readBody(); + let obj: any = JSON.parse(body); + expect(obj.url).toBe("https://httpbin.org/get"); + expect(_proxyConnects).toEqual(['httpbin.org:80']) + }) + + it('does basic http get request when bypass proxy', async () => { + process.env['http_proxy'] = _proxyUrl + process.env['no_proxy'] = 'httpbin.org' + const httpClient = new httpm.HttpClient(); + let res: httpm.HttpClientResponse = await httpClient.get('http://httpbin.org/get'); + expect(res.message.statusCode).toBe(200); + let body: string = await res.readBody(); + let obj: any = JSON.parse(body); + expect(obj.url).toBe("https://httpbin.org/get"); + expect(_proxyConnects).toHaveLength(0) + }) + + it('does basic https get request through proxy', async () => { + process.env['https_proxy'] = _proxyUrl + const httpClient = new httpm.HttpClient(); + let res: httpm.HttpClientResponse = await httpClient.get('https://httpbin.org/get'); + expect(res.message.statusCode).toBe(200); + let body: string = await res.readBody(); + let obj: any = JSON.parse(body); + expect(obj.url).toBe("https://httpbin.org/get"); + expect(_proxyConnects).toEqual(['httpbin.org:443']) + }) + + it('does basic https get request when bypass proxy', async () => { + process.env['https_proxy'] = _proxyUrl + process.env['no_proxy'] = 'httpbin.org' + const httpClient = new httpm.HttpClient(); + let res: httpm.HttpClientResponse = await httpClient.get('https://httpbin.org/get'); + expect(res.message.statusCode).toBe(200); + let body: string = await res.readBody(); + let obj: any = JSON.parse(body); + expect(obj.url).toBe("https://httpbin.org/get"); + expect(_proxyConnects).toHaveLength(0) + }) }) function _clearVars() { diff --git a/index.ts b/index.ts index 25efee5..f39839d 100644 --- a/index.ts +++ b/index.ts @@ -327,6 +327,16 @@ export class HttpClient { } } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + public getAgent(serverUrl: string): http.Agent { + let parsedUrl = url.parse(serverUrl) + return this._getAgent(parsedUrl) + } + private _prepareRequest(method: string, requestUrl: url.Url, headers: ifm.IHeaders): ifm.IRequestInfo { const info: ifm.IRequestInfo = {}; @@ -372,7 +382,7 @@ export class HttpClient { return lowercaseKeys(headers || {}); } - private _getAgent(parsedUrl: url.Url) { + private _getAgent(parsedUrl: url.Url): http.Agent { let agent; let proxyUrl: url.Url = pm.getProxyUrl(parsedUrl); let useProxy = proxyUrl && proxyUrl.hostname; diff --git a/package-lock.json b/package-lock.json index aa62741..1ea9b5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -436,9 +436,9 @@ } }, "@types/node": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.5.tgz", - "integrity": "sha512-wupvfmtbqRJzjCm1H2diy7wo31Gn1OzvqoxCfQuKM9eSecogzP0WTlrjdq7cf7jgSO2ZX6hxwgRPR8Wt7FA22g==", + "version": "12.12.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.24.tgz", + "integrity": "sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug==", "dev": true }, "@types/stack-utils": { @@ -541,6 +541,73 @@ "normalize-path": "^2.1.1" } }, + "args": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/args/-/args-3.0.2.tgz", + "integrity": "sha1-hQu46IHzE5IDpeTLF2QxCStWLC0=", + "dev": true, + "requires": { + "camelcase": "4.1.0", + "chalk": "1.1.3", + "minimist": "1.2.0", + "pkginfo": "0.4.0", + "string-similarity": "1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -735,6 +802,12 @@ } } }, + "basic-auth-parser": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz", + "integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE=", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -2237,6 +2310,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3434,19 +3524,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "nock": { - "version": "11.7.2", - "resolved": "https://registry.npmjs.org/nock/-/nock-11.7.2.tgz", - "integrity": "sha512-7swr5bL1xBZ5FctyubjxEVySXOSebyqcL7Vy1bx1nS9IUqQWj81cmKjVKJLr8fHhtzI1MV8nyCdENA/cGcY1+Q==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.13", - "mkdirp": "^0.5.0", - "propagate": "^2.0.0" - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -3760,6 +3837,12 @@ "find-up": "^3.0.0" } }, + "pkginfo": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.0.tgz", + "integrity": "sha1-NJ27f/04CB/K3AhT32h/DHdEzWU=", + "dev": true + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -3800,11 +3883,16 @@ "sisteransi": "^1.0.3" } }, - "propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true + "proxy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.1.tgz", + "integrity": "sha512-mM9Hl6Mbw2Iiw4WLzjtPObtxX3xdsv0Fr07Kqm+GXg0eVObKBD7mc+TMQwkv2zztk5EtyLdv0+eFNXhBfPiU8A==", + "dev": true, + "requires": { + "args": "3.0.2", + "basic-auth-parser": "0.0.2", + "debug": "^4.1.1" + } }, "psl": { "version": "1.7.0", @@ -4434,6 +4522,15 @@ } } }, + "string-similarity": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-1.1.0.tgz", + "integrity": "sha1-PGZJiFikZex8QMfYFzm72ZWQSRQ=", + "dev": true, + "requires": { + "lodash": "^4.13.1" + } + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -4627,6 +4724,11 @@ } } }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 5bc9155..1d6414f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@actions/http-client", - "version": "1.0.1", + "version": "1.0.2", "description": "Actions Http Client", "main": "index.js", "scripts": { @@ -23,10 +23,13 @@ "homepage": "https://github.com/actions/http-client#readme", "devDependencies": { "@types/jest": "^24.0.25", - "@types/node": "^13.1.5", + "@types/node": "^12.12.24", "jest": "^24.9.0", - "nock": "^11.7.2", + "proxy": "^1.0.1", "ts-jest": "^24.3.0", "typescript": "^3.7.4" + }, + "dependencies": { + "tunnel": "0.0.6" } }