diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 934d222..a3b9963 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,8 +40,12 @@ jobs: - name: npm test run: npm test - # - name: Format - # run: npm run format-check + - name: Format + shell: bash + run: npm run format-check + if: matrix.runs-on == 'ubuntu-latest' - name: audit security + continue-on-error: true run: npm audit --audit-level=moderate + if: matrix.runs-on == 'ubuntu-latest' diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index 5c0e607..a2821be 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -1,56 +1,61 @@ -import * as httpm from '../_out'; -import * as am from '../_out/auth'; +import * as httpm from '../_out' +import * as am from '../_out/auth' describe('auth', () => { - beforeEach(() => { + beforeEach(() => {}) - }) - - afterEach(() => { + afterEach(() => {}) - }) - - it('does basic http get request with basic auth', async() => { - let bh: am.BasicCredentialHandler = new am.BasicCredentialHandler('johndoe', 'password'); - let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [bh]); - let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get'); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - let auth: string = obj.headers.Authorization; - let creds: string = Buffer.from(auth.substring('Basic '.length), 'base64').toString(); - expect(creds).toBe('johndoe:password'); - expect(obj.url).toBe("http://httpbin.org/get"); - }); + it('does basic http get request with basic auth', async () => { + let bh: am.BasicCredentialHandler = new am.BasicCredentialHandler( + 'johndoe', + 'password' + ) + let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [bh]) + let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + let auth: string = obj.headers.Authorization + let creds: string = Buffer.from( + auth.substring('Basic '.length), + 'base64' + ).toString() + expect(creds).toBe('johndoe:password') + expect(obj.url).toBe('http://httpbin.org/get') + }) - it('does basic http get request with pat token auth', async() => { - let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'; - let ph: am.PersonalAccessTokenCredentialHandler = - new am.PersonalAccessTokenCredentialHandler(token); + it('does basic http get request with pat token auth', async () => { + let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' + let ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler( + token + ) - let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]); - let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get'); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - let auth: string = obj.headers.Authorization; - let creds: string = Buffer.from(auth.substring('Basic '.length), 'base64').toString(); - expect(creds).toBe('PAT:' + token); - expect(obj.url).toBe("http://httpbin.org/get"); - }); - - it('does basic http get request with pat token auth', async() => { - let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'; - let ph: am.BearerCredentialHandler = - new am.BearerCredentialHandler(token); + let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]) + let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + let auth: string = obj.headers.Authorization + let creds: string = Buffer.from( + auth.substring('Basic '.length), + 'base64' + ).toString() + expect(creds).toBe('PAT:' + token) + expect(obj.url).toBe('http://httpbin.org/get') + }) - let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]); - let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get'); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - let auth: string = obj.headers.Authorization; - expect(auth).toBe('Bearer ' + token); - expect(obj.url).toBe("http://httpbin.org/get"); - }); + it('does basic http get request with pat token auth', async () => { + let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' + let ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token) + + let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]) + let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + let auth: string = obj.headers.Authorization + expect(auth).toBe('Bearer ' + token) + expect(obj.url).toBe('http://httpbin.org/get') + }) }) diff --git a/__tests__/basics.test.ts b/__tests__/basics.test.ts index eb1c3e9..20e910a 100644 --- a/__tests__/basics.test.ts +++ b/__tests__/basics.test.ts @@ -1,256 +1,329 @@ -import * as httpm from '../_out'; +import * as httpm from '../_out' import * as ifm from '../_out/interfaces' -import * as path from 'path'; -import * as fs from 'fs'; +import * as path from 'path' +import * as fs from 'fs' -let sampleFilePath: string = path.join(__dirname, 'testoutput.txt'); +let sampleFilePath: string = path.join(__dirname, 'testoutput.txt') interface HttpBinData { - url: string; - data: any; - json: any; - headers: any; - args?: any + url: string + data: any + json: any + headers: any + args?: any } describe('basics', () => { - let _http: httpm.HttpClient; + let _http: httpm.HttpClient - beforeEach(() => { - _http = new httpm.HttpClient('http-client-tests'); + beforeEach(() => { + _http = new httpm.HttpClient('http-client-tests') + }) + + afterEach(() => {}) + + it('constructs', () => { + let http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests') + expect(http).toBeDefined() + }) + + // responses from httpbin return something like: + // { + // "args": {}, + // "headers": { + // "Connection": "close", + // "Host": "httpbin.org", + // "User-Agent": "typed-test-client-tests" + // }, + // "origin": "173.95.152.44", + // "url": "https://httpbin.org/get" + // } + + it('does basic http get request', async done => { + let res: httpm.HttpClientResponse = await _http.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('http://httpbin.org/get') + expect(obj.headers['User-Agent']).toBeTruthy() + done() + }) + + it('does basic http get request with no user agent', async done => { + let http: httpm.HttpClient = new httpm.HttpClient() + let res: httpm.HttpClientResponse = await http.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('http://httpbin.org/get') + expect(obj.headers['User-Agent']).toBeFalsy() + done() + }) + + it('does basic https get request', async done => { + let res: httpm.HttpClientResponse = await _http.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') + done() + }) + + it('does basic http get request with default headers', async done => { + let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + } + }) + let res: httpm.HttpClientResponse = await http.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.headers.Accept).toBe('application/json') + expect(obj.headers['Content-Type']).toBe('application/json') + expect(obj.url).toBe('http://httpbin.org/get') + done() + }) + + it('does basic http get request with merged headers', async done => { + let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json' + } }) - - afterEach(() => { + let res: httpm.HttpClientResponse = await http.get( + 'http://httpbin.org/get', + { + 'content-type': 'application/x-www-form-urlencoded' + } + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + expect(obj.headers.Accept).toBe('application/json') + expect(obj.headers['Content-Type']).toBe( + 'application/x-www-form-urlencoded' + ) + expect(obj.url).toBe('http://httpbin.org/get') + done() + }) + it('pipes a get request', () => { + return new Promise(async (resolve, reject) => { + let file: NodeJS.WritableStream = fs.createWriteStream(sampleFilePath) + ;(await _http.get('https://httpbin.org/get')).message + .pipe(file) + .on('close', () => { + let body: string = fs.readFileSync(sampleFilePath).toString() + let obj: any = JSON.parse(body) + expect(obj.url).toBe('https://httpbin.org/get') + resolve() + }) }) - - it('constructs', () => { - let http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests'); - expect(http).toBeDefined(); - }); + }) - // responses from httpbin return something like: - // { - // "args": {}, - // "headers": { - // "Connection": "close", - // "Host": "httpbin.org", - // "User-Agent": "typed-test-client-tests" - // }, - // "origin": "173.95.152.44", - // "url": "https://httpbin.org/get" - // } + it('does basic get request with redirects', async done => { + let res: httpm.HttpClientResponse = await _http.get( + 'https://httpbin.org/redirect-to?url=' + + encodeURIComponent('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') + done() + }) - it('does basic http get request', async(done) => { - let res: httpm.HttpClientResponse = await _http.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("http://httpbin.org/get"); - expect(obj.headers["User-Agent"]).toBeTruthy(); - done(); - }); - - it('does basic http get request with no user agent', async(done) => { - let http: httpm.HttpClient = new httpm.HttpClient(); - let res: httpm.HttpClientResponse = await http.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("http://httpbin.org/get"); - expect(obj.headers["User-Agent"]).toBeFalsy(); - done(); - }); + it('does basic get request with redirects (303)', async done => { + let res: httpm.HttpClientResponse = await _http.get( + 'https://httpbin.org/redirect-to?url=' + + encodeURIComponent('https://httpbin.org/get') + + '&status_code=303' + ) + 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') + done() + }) - it('does basic https get request', async(done) => { - let res: httpm.HttpClientResponse = await _http.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"); - done(); - }); + it('returns 404 for not found get request on redirect', async done => { + let res: httpm.HttpClientResponse = await _http.get( + 'https://httpbin.org/redirect-to?url=' + + encodeURIComponent('https://httpbin.org/status/404') + + '&status_code=303' + ) + expect(res.message.statusCode).toBe(404) + let body: string = await res.readBody() + done() + }) - it('does basic http get request with default headers', async(done) => { - let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }); - let res: httpm.HttpClientResponse = await http.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.headers.Accept).toBe('application/json'); - expect(obj.headers['Content-Type']).toBe('application/json'); - expect(obj.url).toBe("http://httpbin.org/get"); - done(); - }); + it('does not follow redirects if disabled', async done => { + let http: httpm.HttpClient = new httpm.HttpClient( + 'typed-test-client-tests', + null, + {allowRedirects: false} + ) + let res: httpm.HttpClientResponse = await http.get( + 'https://httpbin.org/redirect-to?url=' + + encodeURIComponent('https://httpbin.org/get') + ) + expect(res.message.statusCode).toBe(302) + let body: string = await res.readBody() + done() + }) - it('does basic http get request with merged headers', async(done) => { - let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }); - let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get', { - 'content-type': 'application/x-www-form-urlencoded' - }); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - expect(obj.headers.Accept).toBe('application/json'); - expect(obj.headers['Content-Type']).toBe('application/x-www-form-urlencoded'); - expect(obj.url).toBe("http://httpbin.org/get"); - done(); - }); + it('does basic head request', async done => { + let res: httpm.HttpClientResponse = await _http.head( + 'http://httpbin.org/get' + ) + expect(res.message.statusCode).toBe(200) + done() + }) - it('pipes a get request', () => { - return new Promise(async (resolve, reject) => { - let file: NodeJS.WritableStream = fs.createWriteStream(sampleFilePath); - (await _http.get('https://httpbin.org/get')).message.pipe(file).on('close', () => { - let body: string = fs.readFileSync(sampleFilePath).toString(); - let obj:any = JSON.parse(body); - expect(obj.url).toBe("https://httpbin.org/get"); - resolve(); - }); - }); - }); - - it('does basic get request with redirects', async(done) => { - let res: httpm.HttpClientResponse = await _http.get("https://httpbin.org/redirect-to?url=" + encodeURIComponent("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"); - done(); - }); + it('does basic http delete request', async done => { + let res: httpm.HttpClientResponse = await _http.del( + 'http://httpbin.org/delete' + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + done() + }) - it('does basic get request with redirects (303)', async(done) => { - let res: httpm.HttpClientResponse = await _http.get("https://httpbin.org/redirect-to?url=" + encodeURIComponent("https://httpbin.org/get") + '&status_code=303') - 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"); - done(); - }); + it('does basic http post request', async done => { + let b: string = 'Hello World!' + let res: httpm.HttpClientResponse = await _http.post( + 'http://httpbin.org/post', + b + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + expect(obj.data).toBe(b) + expect(obj.url).toBe('http://httpbin.org/post') + done() + }) - it('returns 404 for not found get request on redirect', async(done) => { - let res: httpm.HttpClientResponse = await _http.get("https://httpbin.org/redirect-to?url=" + encodeURIComponent("https://httpbin.org/status/404") + '&status_code=303') - expect(res.message.statusCode).toBe(404); - let body: string = await res.readBody(); - done(); - }); + it('does basic http patch request', async done => { + let b: string = 'Hello World!' + let res: httpm.HttpClientResponse = await _http.patch( + 'http://httpbin.org/patch', + b + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + expect(obj.data).toBe(b) + expect(obj.url).toBe('http://httpbin.org/patch') + done() + }) - it('does not follow redirects if disabled', async(done) => { - let http: httpm.HttpClient = new httpm.HttpClient('typed-test-client-tests', null, { allowRedirects: false }); - let res: httpm.HttpClientResponse = await http.get("https://httpbin.org/redirect-to?url=" + encodeURIComponent("https://httpbin.org/get")) - expect(res.message.statusCode).toBe(302); - let body: string = await res.readBody(); - done(); - }); + it('does basic http options request', async done => { + let res: httpm.HttpClientResponse = await _http.options( + 'http://httpbin.org' + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + done() + }) - it('does basic head request', async(done) => { - let res: httpm.HttpClientResponse = await _http.head('http://httpbin.org/get'); - expect(res.message.statusCode).toBe(200); - done(); - }); + it('returns 404 for not found get request', async done => { + let res: httpm.HttpClientResponse = await _http.get( + 'http://httpbin.org/status/404' + ) + expect(res.message.statusCode).toBe(404) + let body: string = await res.readBody() + done() + }) - it('does basic http delete request', async(done) => { - let res: httpm.HttpClientResponse = await _http.del('http://httpbin.org/delete'); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - done(); - }); + it('gets a json object', async () => { + let jsonObj: ifm.ITypedResponse = await _http.getJson< + HttpBinData + >('https://httpbin.org/get') + expect(jsonObj.statusCode).toBe(200) + expect(jsonObj.result).toBeDefined() + expect(jsonObj.result.url).toBe('https://httpbin.org/get') + expect(jsonObj.result.headers['Accept']).toBe( + httpm.MediaTypes.ApplicationJson + ) + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) - it('does basic http post request', async(done) => { - let b: string = 'Hello World!'; - let res: httpm.HttpClientResponse = await _http.post('http://httpbin.org/post', b); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - expect(obj.data).toBe(b); - expect(obj.url).toBe("http://httpbin.org/post"); - done(); - }); + it('getting a non existent json object returns null', async () => { + let jsonObj: ifm.ITypedResponse = await _http.getJson< + HttpBinData + >('https://httpbin.org/status/404') + expect(jsonObj.statusCode).toBe(404) + expect(jsonObj.result).toBeNull() + }) - it('does basic http patch request', async(done) => { - let b: string = 'Hello World!'; - let res: httpm.HttpClientResponse = await _http.patch('http://httpbin.org/patch', b); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - expect(obj.data).toBe(b); - expect(obj.url).toBe("http://httpbin.org/patch"); - done(); - }); - - it('does basic http options request', async(done) => { - let res: httpm.HttpClientResponse = await _http.options('http://httpbin.org'); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - done(); - }); - - it('returns 404 for not found get request', async(done) => { - let res: httpm.HttpClientResponse = await _http.get('http://httpbin.org/status/404'); - expect(res.message.statusCode).toBe(404); - let body: string = await res.readBody(); - done(); - }); - - it('gets a json object', async() => { - let jsonObj: ifm.ITypedResponse = await _http.getJson('https://httpbin.org/get'); - expect(jsonObj.statusCode).toBe(200); - expect(jsonObj.result).toBeDefined(); - expect(jsonObj.result.url).toBe('https://httpbin.org/get'); - expect(jsonObj.result.headers["Accept"]).toBe(httpm.MediaTypes.ApplicationJson); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); - - it('getting a non existent json object returns null', async() => { - let jsonObj: ifm.ITypedResponse = await _http.getJson('https://httpbin.org/status/404'); - expect(jsonObj.statusCode).toBe(404); - expect(jsonObj.result).toBeNull(); - }); + it('posts a json object', async () => { + let res: any = {name: 'foo'} + let restRes: ifm.ITypedResponse = await _http.postJson< + HttpBinData + >('https://httpbin.org/post', res) + expect(restRes.statusCode).toBe(200) + expect(restRes.result).toBeDefined() + expect(restRes.result.url).toBe('https://httpbin.org/post') + expect(restRes.result.json.name).toBe('foo') + expect(restRes.result.headers['Accept']).toBe( + httpm.MediaTypes.ApplicationJson + ) + expect(restRes.result.headers['Content-Type']).toBe( + httpm.MediaTypes.ApplicationJson + ) + expect(restRes.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) - it('posts a json object', async() => { - let res: any = { name: 'foo' }; - let restRes: ifm.ITypedResponse = await _http.postJson('https://httpbin.org/post', res); - expect(restRes.statusCode).toBe(200); - expect(restRes.result).toBeDefined(); - expect(restRes.result.url).toBe('https://httpbin.org/post'); - expect(restRes.result.json.name).toBe('foo'); - expect(restRes.result.headers["Accept"]).toBe(httpm.MediaTypes.ApplicationJson); - expect(restRes.result.headers["Content-Type"]).toBe(httpm.MediaTypes.ApplicationJson); - expect(restRes.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); + it('puts a json object', async () => { + let res: any = {name: 'foo'} + let restRes: ifm.ITypedResponse = await _http.putJson< + HttpBinData + >('https://httpbin.org/put', res) + expect(restRes.statusCode).toBe(200) + expect(restRes.result).toBeDefined() + expect(restRes.result.url).toBe('https://httpbin.org/put') + expect(restRes.result.json.name).toBe('foo') - it('puts a json object', async() => { - let res: any = { name: 'foo' }; - let restRes: ifm.ITypedResponse = await _http.putJson('https://httpbin.org/put', res); - expect(restRes.statusCode).toBe(200); - expect(restRes.result).toBeDefined(); - expect(restRes.result.url).toBe('https://httpbin.org/put'); - expect(restRes.result.json.name).toBe('foo'); + expect(restRes.result.headers['Accept']).toBe( + httpm.MediaTypes.ApplicationJson + ) + expect(restRes.result.headers['Content-Type']).toBe( + httpm.MediaTypes.ApplicationJson + ) + expect(restRes.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) - expect(restRes.result.headers["Accept"]).toBe(httpm.MediaTypes.ApplicationJson); - expect(restRes.result.headers["Content-Type"]).toBe(httpm.MediaTypes.ApplicationJson); - expect(restRes.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); - - it('patch a json object', async() => { - let res: any = { name: 'foo' }; - let restRes: ifm.ITypedResponse = await _http.patchJson('https://httpbin.org/patch', res); - expect(restRes.statusCode).toBe(200); - expect(restRes.result).toBeDefined(); - expect(restRes.result.url).toBe('https://httpbin.org/patch'); - expect(restRes.result.json.name).toBe('foo'); - expect(restRes.result.headers["Accept"]).toBe(httpm.MediaTypes.ApplicationJson); - expect(restRes.result.headers["Content-Type"]).toBe(httpm.MediaTypes.ApplicationJson); - expect(restRes.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); -}); + it('patch a json object', async () => { + let res: any = {name: 'foo'} + let restRes: ifm.ITypedResponse = await _http.patchJson< + HttpBinData + >('https://httpbin.org/patch', res) + expect(restRes.statusCode).toBe(200) + expect(restRes.result).toBeDefined() + expect(restRes.result.url).toBe('https://httpbin.org/patch') + expect(restRes.result.json.name).toBe('foo') + expect(restRes.result.headers['Accept']).toBe( + httpm.MediaTypes.ApplicationJson + ) + expect(restRes.result.headers['Content-Type']).toBe( + httpm.MediaTypes.ApplicationJson + ) + expect(restRes.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) +}) diff --git a/__tests__/headers.test.ts b/__tests__/headers.test.ts index ca36191..bf92dbc 100644 --- a/__tests__/headers.test.ts +++ b/__tests__/headers.test.ts @@ -1,79 +1,115 @@ -import * as httpm from '../_out'; +import * as httpm from '../_out' import * as ifm from '../_out/interfaces' describe('headers', () => { - let _http: httpm.HttpClient; + let _http: httpm.HttpClient - beforeEach(() => { - _http = new httpm.HttpClient('http-client-tests'); - }); + beforeEach(() => { + _http = new httpm.HttpClient('http-client-tests') + }) - it('preserves existing headers on getJson', async() => { - let additionalHeaders = { [httpm.Headers.Accept]: "foo" }; - let jsonObj: ifm.ITypedResponse = await _http.getJson('https://httpbin.org/get', additionalHeaders); - expect(jsonObj.result.headers["Accept"]).toBe("foo"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); + it('preserves existing headers on getJson', async () => { + let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} + let jsonObj: ifm.ITypedResponse = await _http.getJson( + 'https://httpbin.org/get', + additionalHeaders + ) + expect(jsonObj.result.headers['Accept']).toBe('foo') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) - let httpWithHeaders = new httpm.HttpClient(); - httpWithHeaders.requestOptions = { - headers: { - [httpm.Headers.Accept]: "baz" - } - }; - jsonObj = await httpWithHeaders.getJson('https://httpbin.org/get'); - expect(jsonObj.result.headers["Accept"]).toBe("baz"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); + let httpWithHeaders = new httpm.HttpClient() + httpWithHeaders.requestOptions = { + headers: { + [httpm.Headers.Accept]: 'baz' + } + } + jsonObj = await httpWithHeaders.getJson('https://httpbin.org/get') + expect(jsonObj.result.headers['Accept']).toBe('baz') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) - it('preserves existing headers on postJson', async() => { - let additionalHeaders = { [httpm.Headers.Accept]: "foo" }; - let jsonObj: ifm.ITypedResponse = await _http.postJson('https://httpbin.org/post', {}, additionalHeaders); - expect(jsonObj.result.headers["Accept"]).toBe("foo"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); + it('preserves existing headers on postJson', async () => { + let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} + let jsonObj: ifm.ITypedResponse = await _http.postJson( + 'https://httpbin.org/post', + {}, + additionalHeaders + ) + expect(jsonObj.result.headers['Accept']).toBe('foo') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) - let httpWithHeaders = new httpm.HttpClient(); - httpWithHeaders.requestOptions = { - headers: { - [httpm.Headers.Accept]: "baz" - } - }; - jsonObj = await httpWithHeaders.postJson('https://httpbin.org/post', {}); - expect(jsonObj.result.headers["Accept"]).toBe("baz"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); + let httpWithHeaders = new httpm.HttpClient() + httpWithHeaders.requestOptions = { + headers: { + [httpm.Headers.Accept]: 'baz' + } + } + jsonObj = await httpWithHeaders.postJson( + 'https://httpbin.org/post', + {} + ) + expect(jsonObj.result.headers['Accept']).toBe('baz') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) - it('preserves existing headers on putJson', async() => { - let additionalHeaders = { [httpm.Headers.Accept]: "foo" }; - let jsonObj: ifm.ITypedResponse = await _http.putJson('https://httpbin.org/put', {}, additionalHeaders); - expect(jsonObj.result.headers["Accept"]).toBe("foo"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); + it('preserves existing headers on putJson', async () => { + let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} + let jsonObj: ifm.ITypedResponse = await _http.putJson( + 'https://httpbin.org/put', + {}, + additionalHeaders + ) + expect(jsonObj.result.headers['Accept']).toBe('foo') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) - let httpWithHeaders = new httpm.HttpClient(); - httpWithHeaders.requestOptions = { - headers: { - [httpm.Headers.Accept]: "baz" - } - }; - jsonObj = await httpWithHeaders.putJson('https://httpbin.org/put', {}); - expect(jsonObj.result.headers["Accept"]).toBe("baz"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); + let httpWithHeaders = new httpm.HttpClient() + httpWithHeaders.requestOptions = { + headers: { + [httpm.Headers.Accept]: 'baz' + } + } + jsonObj = await httpWithHeaders.putJson('https://httpbin.org/put', {}) + expect(jsonObj.result.headers['Accept']).toBe('baz') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) - it('preserves existing headers on patchJson', async() => { - let additionalHeaders = { [httpm.Headers.Accept]: "foo" }; - let jsonObj: ifm.ITypedResponse = await _http.patchJson('https://httpbin.org/patch', {}, additionalHeaders); - expect(jsonObj.result.headers["Accept"]).toBe("foo"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); + it('preserves existing headers on patchJson', async () => { + let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} + let jsonObj: ifm.ITypedResponse = await _http.patchJson( + 'https://httpbin.org/patch', + {}, + additionalHeaders + ) + expect(jsonObj.result.headers['Accept']).toBe('foo') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) - let httpWithHeaders = new httpm.HttpClient(); - httpWithHeaders.requestOptions = { - headers: { - [httpm.Headers.Accept]: "baz" - } - }; - jsonObj = await httpWithHeaders.patchJson('https://httpbin.org/patch', {}); - expect(jsonObj.result.headers["Accept"]).toBe("baz"); - expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(httpm.MediaTypes.ApplicationJson); - }); - -}); + let httpWithHeaders = new httpm.HttpClient() + httpWithHeaders.requestOptions = { + headers: { + [httpm.Headers.Accept]: 'baz' + } + } + jsonObj = await httpWithHeaders.patchJson( + 'https://httpbin.org/patch', + {} + ) + expect(jsonObj.result.headers['Accept']).toBe('baz') + expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( + httpm.MediaTypes.ApplicationJson + ) + }) +}) diff --git a/__tests__/keepalive.test.ts b/__tests__/keepalive.test.ts index b532190..8a2ebe7 100644 --- a/__tests__/keepalive.test.ts +++ b/__tests__/keepalive.test.ts @@ -1,65 +1,79 @@ -import * as httpm from '../_out'; +import * as httpm from '../_out' describe('basics', () => { - let _http: httpm.HttpClient; + let _http: httpm.HttpClient - beforeEach(() => { - _http = new httpm.HttpClient('http-client-tests', [], { keepAlive: true }); - }) - - afterEach(() => { - _http.dispose(); - }) - - it('does basic http get request with keepAlive true', async(done) => { - let res: httpm.HttpClientResponse = await _http.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("http://httpbin.org/get"); - done(); - }); + beforeEach(() => { + _http = new httpm.HttpClient('http-client-tests', [], {keepAlive: true}) + }) - it('does basic head request with keepAlive true', async(done) => { - let res: httpm.HttpClientResponse = await _http.head('http://httpbin.org/get'); - expect(res.message.statusCode).toBe(200); - done(); - }); + afterEach(() => { + _http.dispose() + }) - it('does basic http delete request with keepAlive true', async(done) => { - let res: httpm.HttpClientResponse = await _http.del('http://httpbin.org/delete'); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - done(); - }); - - it('does basic http post request with keepAlive true', async(done) => { - let b: string = 'Hello World!'; - let res: httpm.HttpClientResponse = await _http.post('http://httpbin.org/post', b); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - expect(obj.data).toBe(b); - expect(obj.url).toBe("http://httpbin.org/post"); - done(); - }); - - it('does basic http patch request with keepAlive true', async(done) => { - let b: string = 'Hello World!'; - let res: httpm.HttpClientResponse = await _http.patch('http://httpbin.org/patch', b); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - let obj:any = JSON.parse(body); - expect(obj.data).toBe(b); - expect(obj.url).toBe("http://httpbin.org/patch"); - done(); - }); - - it('does basic http options request with keepAlive true', async(done) => { - let res: httpm.HttpClientResponse = await _http.options('http://httpbin.org'); - expect(res.message.statusCode).toBe(200); - let body: string = await res.readBody(); - done(); - }); -}); + it('does basic http get request with keepAlive true', async done => { + let res: httpm.HttpClientResponse = await _http.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('http://httpbin.org/get') + done() + }) + + it('does basic head request with keepAlive true', async done => { + let res: httpm.HttpClientResponse = await _http.head( + 'http://httpbin.org/get' + ) + expect(res.message.statusCode).toBe(200) + done() + }) + + it('does basic http delete request with keepAlive true', async done => { + let res: httpm.HttpClientResponse = await _http.del( + 'http://httpbin.org/delete' + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + done() + }) + + it('does basic http post request with keepAlive true', async done => { + let b: string = 'Hello World!' + let res: httpm.HttpClientResponse = await _http.post( + 'http://httpbin.org/post', + b + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + expect(obj.data).toBe(b) + expect(obj.url).toBe('http://httpbin.org/post') + done() + }) + + it('does basic http patch request with keepAlive true', async done => { + let b: string = 'Hello World!' + let res: httpm.HttpClientResponse = await _http.patch( + 'http://httpbin.org/patch', + b + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + let obj: any = JSON.parse(body) + expect(obj.data).toBe(b) + expect(obj.url).toBe('http://httpbin.org/patch') + done() + }) + + it('does basic http options request with keepAlive true', async done => { + let res: httpm.HttpClientResponse = await _http.options( + 'http://httpbin.org' + ) + expect(res.message.statusCode).toBe(200) + let body: string = await res.readBody() + done() + }) +}) diff --git a/__tests__/proxy.test.ts b/__tests__/proxy.test.ts index c085a53..f484909 100644 --- a/__tests__/proxy.test.ts +++ b/__tests__/proxy.test.ts @@ -1,201 +1,208 @@ import * as http from 'http' -import * as httpm from '../_out'; -import * as pm from '../_out/proxy'; +import * as httpm from '../_out' +import * as pm from '../_out/proxy' import * as proxy from 'proxy' -import * as url from 'url'; +import * as url from 'url' 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('getProxyUrl does not return proxyUrl if variables not set', () => { - let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); - expect(proxyUrl).toBeUndefined(); - }) - - it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => { - process.env["https_proxy"] = "https://myproxysvr"; - let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); - expect(proxyUrl).toBeDefined(); - }) - - it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => { - process.env["http_proxy"] = "https://myproxysvr"; - let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); - expect(proxyUrl).toBeUndefined(); - }) - - it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => { - process.env["http_proxy"] = "http://myproxysvr"; - let proxyUrl = pm.getProxyUrl(url.parse('http://github.com')); - expect(proxyUrl).toBeDefined(); - }) - - it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => { - process.env["https_proxy"] = "https://myproxysvr"; - process.env["no_proxy"] = "otherserver,myserver,anotherserver:8080" - let proxyUrl = pm.getProxyUrl(url.parse('https://myserver')); - expect(proxyUrl).toBeUndefined(); - }) - - it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => { - process.env["https_proxy"] = "https://myproxysvr"; - process.env["no_proxy"] = "otherserver,myserver,anotherserver:8080" - let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')); - expect(proxyUrl).toBeDefined(); - }) - - it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => { - process.env["http_proxy"] = "http://myproxysvr"; - process.env["no_proxy"] = "otherserver,myserver,anotherserver:8080" - let proxyUrl = pm.getProxyUrl(url.parse('http://myserver')); - expect(proxyUrl).toBeUndefined(); - }) - - it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => { - process.env["http_proxy"] = "http://myproxysvr"; - process.env["no_proxy"] = "otherserver,myserver,anotherserver:8080" - let proxyUrl = pm.getProxyUrl(url.parse('http://github.com')); - expect(proxyUrl).toBeDefined(); - }) - - it('checkBypass returns true if host as no_proxy list', () => { - process.env["no_proxy"] = "myserver" - let bypass = pm.checkBypass(url.parse('https://myserver')); - expect(bypass).toBeTruthy(); - }) - - it('checkBypass returns true if host in no_proxy list', () => { - process.env["no_proxy"] = "otherserver,myserver,anotherserver:8080" - let bypass = pm.checkBypass(url.parse('https://myserver')); - expect(bypass).toBeTruthy(); - }) - - it('checkBypass returns true if host in no_proxy list with spaces', () => { - process.env["no_proxy"] = "otherserver, myserver ,anotherserver:8080" - let bypass = pm.checkBypass(url.parse('https://myserver')); - expect(bypass).toBeTruthy(); - }) - - it('checkBypass returns true if host in no_proxy list with port', () => { - process.env["no_proxy"] = "otherserver, myserver:8080 ,anotherserver" - let bypass = pm.checkBypass(url.parse('https://myserver:8080')); - expect(bypass).toBeTruthy(); - }) - - it('checkBypass returns true if host with port in no_proxy list without port', () => { - process.env["no_proxy"] = "otherserver, myserver ,anotherserver" - let bypass = pm.checkBypass(url.parse('https://myserver:8080')); - expect(bypass).toBeTruthy(); - }) - - it('checkBypass returns true if host in no_proxy list with default https port', () => { - process.env["no_proxy"] = "otherserver, myserver:443 ,anotherserver" - let bypass = pm.checkBypass(url.parse('https://myserver')); - expect(bypass).toBeTruthy(); - }) - - it('checkBypass returns true if host in no_proxy list with default http port', () => { - process.env["no_proxy"] = "otherserver, myserver:80 ,anotherserver" - let bypass = pm.checkBypass(url.parse('http://myserver')); - expect(bypass).toBeTruthy(); - }) - - it('checkBypass returns false if host not in no_proxy list', () => { - process.env["no_proxy"] = "otherserver, myserver ,anotherserver:8080" - let bypass = pm.checkBypass(url.parse('https://github.com')); - expect(bypass).toBeFalsy(); - }) - - it('checkBypass returns false if empty no_proxy', () => { - process.env["no_proxy"] = "" - let bypass = pm.checkBypass(url.parse('https://github.com')); - expect(bypass).toBeFalsy(); - }) - - it('HttpClient 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("http://httpbin.org/get"); - expect(_proxyConnects).toEqual(['httpbin.org:80']) - }) - - it('HttoClient 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("http://httpbin.org/get"); - expect(_proxyConnects).toHaveLength(0) - }) - - it('HttpClient 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('HttpClient 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) - }) + 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('getProxyUrl does not return proxyUrl if variables not set', () => { + let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')) + expect(proxyUrl).toBeUndefined() + }) + + it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => { + process.env['https_proxy'] = 'https://myproxysvr' + let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')) + expect(proxyUrl).toBeDefined() + }) + + it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => { + process.env['http_proxy'] = 'https://myproxysvr' + let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')) + expect(proxyUrl).toBeUndefined() + }) + + it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => { + process.env['http_proxy'] = 'http://myproxysvr' + let proxyUrl = pm.getProxyUrl(url.parse('http://github.com')) + expect(proxyUrl).toBeDefined() + }) + + it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => { + process.env['https_proxy'] = 'https://myproxysvr' + process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' + let proxyUrl = pm.getProxyUrl(url.parse('https://myserver')) + expect(proxyUrl).toBeUndefined() + }) + + it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => { + process.env['https_proxy'] = 'https://myproxysvr' + process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' + let proxyUrl = pm.getProxyUrl(url.parse('https://github.com')) + expect(proxyUrl).toBeDefined() + }) + + it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => { + process.env['http_proxy'] = 'http://myproxysvr' + process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' + let proxyUrl = pm.getProxyUrl(url.parse('http://myserver')) + expect(proxyUrl).toBeUndefined() + }) + + it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => { + process.env['http_proxy'] = 'http://myproxysvr' + process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' + let proxyUrl = pm.getProxyUrl(url.parse('http://github.com')) + expect(proxyUrl).toBeDefined() + }) + + it('checkBypass returns true if host as no_proxy list', () => { + process.env['no_proxy'] = 'myserver' + let bypass = pm.checkBypass(url.parse('https://myserver')) + expect(bypass).toBeTruthy() + }) + + it('checkBypass returns true if host in no_proxy list', () => { + process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' + let bypass = pm.checkBypass(url.parse('https://myserver')) + expect(bypass).toBeTruthy() + }) + + it('checkBypass returns true if host in no_proxy list with spaces', () => { + process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080' + let bypass = pm.checkBypass(url.parse('https://myserver')) + expect(bypass).toBeTruthy() + }) + + it('checkBypass returns true if host in no_proxy list with port', () => { + process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver' + let bypass = pm.checkBypass(url.parse('https://myserver:8080')) + expect(bypass).toBeTruthy() + }) + + it('checkBypass returns true if host with port in no_proxy list without port', () => { + process.env['no_proxy'] = 'otherserver, myserver ,anotherserver' + let bypass = pm.checkBypass(url.parse('https://myserver:8080')) + expect(bypass).toBeTruthy() + }) + + it('checkBypass returns true if host in no_proxy list with default https port', () => { + process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver' + let bypass = pm.checkBypass(url.parse('https://myserver')) + expect(bypass).toBeTruthy() + }) + + it('checkBypass returns true if host in no_proxy list with default http port', () => { + process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver' + let bypass = pm.checkBypass(url.parse('http://myserver')) + expect(bypass).toBeTruthy() + }) + + it('checkBypass returns false if host not in no_proxy list', () => { + process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080' + let bypass = pm.checkBypass(url.parse('https://github.com')) + expect(bypass).toBeFalsy() + }) + + it('checkBypass returns false if empty no_proxy', () => { + process.env['no_proxy'] = '' + let bypass = pm.checkBypass(url.parse('https://github.com')) + expect(bypass).toBeFalsy() + }) + + it('HttpClient 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('http://httpbin.org/get') + expect(_proxyConnects).toEqual(['httpbin.org:80']) + }) + + it('HttoClient 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('http://httpbin.org/get') + expect(_proxyConnects).toHaveLength(0) + }) + + it('HttpClient 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('HttpClient 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() { - delete process.env.http_proxy; - delete process.env.HTTP_PROXY; - delete process.env.https_proxy; - delete process.env.HTTPS_PROXY; - delete process.env.no_proxy; - delete process.env.NO_PROXY; -} \ No newline at end of file + delete process.env.http_proxy + delete process.env.HTTP_PROXY + delete process.env.https_proxy + delete process.env.HTTPS_PROXY + delete process.env.no_proxy + delete process.env.NO_PROXY +} diff --git a/auth.ts b/auth.ts index ae00211..4b97e5e 100644 --- a/auth.ts +++ b/auth.ts @@ -1,71 +1,86 @@ - -import ifm = require('./interfaces'); +import ifm = require('./interfaces') export class BasicCredentialHandler implements ifm.IRequestHandler { - username: string; - password: string; - - constructor(username: string, password: string) { - this.username = username; - this.password = password; - } - - prepareRequest(options:any): void { - options.headers['Authorization'] = 'Basic ' + Buffer.from(this.username + ':' + this.password).toString('base64'); - } - - // This handler cannot handle 401 - canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { - return false; - } - - handleAuthentication(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs): Promise { - return null; - } + username: string + password: string + + constructor(username: string, password: string) { + this.username = username + this.password = password + } + + prepareRequest(options: any): void { + options.headers['Authorization'] = + 'Basic ' + + Buffer.from(this.username + ':' + this.password).toString('base64') + } + + // This handler cannot handle 401 + canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { + return false + } + + handleAuthentication( + httpClient: ifm.IHttpClient, + requestInfo: ifm.IRequestInfo, + objs + ): Promise { + return null + } } export class BearerCredentialHandler implements ifm.IRequestHandler { - token: string; - - constructor(token: string) { - this.token = token; - } - - // currently implements pre-authorization - // TODO: support preAuth = false where it hooks on 401 - prepareRequest(options:any): void { - options.headers['Authorization'] = 'Bearer ' + this.token; - } - - // This handler cannot handle 401 - canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { - return false; - } - - handleAuthentication(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs): Promise { - return null; - } + token: string + + constructor(token: string) { + this.token = token + } + + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options: any): void { + options.headers['Authorization'] = 'Bearer ' + this.token + } + + // This handler cannot handle 401 + canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { + return false + } + + handleAuthentication( + httpClient: ifm.IHttpClient, + requestInfo: ifm.IRequestInfo, + objs + ): Promise { + return null + } } -export class PersonalAccessTokenCredentialHandler implements ifm.IRequestHandler { - token: string; - - constructor(token: string) { - this.token = token; - } - - // currently implements pre-authorization - // TODO: support preAuth = false where it hooks on 401 - prepareRequest(options:any): void { - options.headers['Authorization'] = 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64'); - } - - // This handler cannot handle 401 - canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { - return false; - } - - handleAuthentication(httpClient: ifm.IHttpClient, requestInfo: ifm.IRequestInfo, objs): Promise { - return null; - } +export class PersonalAccessTokenCredentialHandler + implements ifm.IRequestHandler { + token: string + + constructor(token: string) { + this.token = token + } + + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options: any): void { + options.headers['Authorization'] = + 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64') + } + + // This handler cannot handle 401 + canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { + return false + } + + handleAuthentication( + httpClient: ifm.IHttpClient, + requestInfo: ifm.IRequestInfo, + objs + ): Promise { + return null + } } diff --git a/index.ts b/index.ts index e6a74c2..a1f5491 100644 --- a/index.ts +++ b/index.ts @@ -1,48 +1,48 @@ -import url = require("url"); -import http = require("http"); -import https = require("https"); -import ifm = require('./interfaces'); -import pm = require('./proxy'); +import url = require('url') +import http = require('http') +import https = require('https') +import ifm = require('./interfaces') +import pm = require('./proxy') -let tunnel: any; +let tunnel: any export enum HttpCodes { - OK = 200, - MultipleChoices = 300, - MovedPermanently = 301, - ResourceMoved = 302, - SeeOther = 303, - NotModified = 304, - UseProxy = 305, - SwitchProxy = 306, - TemporaryRedirect = 307, - PermanentRedirect = 308, - BadRequest = 400, - Unauthorized = 401, - PaymentRequired = 402, - Forbidden = 403, - NotFound = 404, - MethodNotAllowed = 405, - NotAcceptable = 406, - ProxyAuthenticationRequired = 407, - RequestTimeout = 408, - Conflict = 409, - Gone = 410, - TooManyRequests = 429, - InternalServerError = 500, - NotImplemented = 501, - BadGateway = 502, - ServiceUnavailable = 503, - GatewayTimeout = 504, + OK = 200, + MultipleChoices = 300, + MovedPermanently = 301, + ResourceMoved = 302, + SeeOther = 303, + NotModified = 304, + UseProxy = 305, + SwitchProxy = 306, + TemporaryRedirect = 307, + PermanentRedirect = 308, + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Conflict = 409, + Gone = 410, + TooManyRequests = 429, + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + GatewayTimeout = 504 } -export enum Headers { - Accept = "accept", - ContentType = "content-type" +export enum Headers { + Accept = 'accept', + ContentType = 'content-type' } export enum MediaTypes { - ApplicationJson = "application/json" + ApplicationJson = 'application/json' } /** @@ -50,553 +50,701 @@ export enum MediaTypes { * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com */ export function getProxyUrl(serverUrl: string): string { - let proxyUrl = pm.getProxyUrl(url.parse(serverUrl)) - return proxyUrl ? proxyUrl.href : '' + let proxyUrl = pm.getProxyUrl(url.parse(serverUrl)) + return proxyUrl ? proxyUrl.href : '' } -const HttpRedirectCodes: number[] = [HttpCodes.MovedPermanently, HttpCodes.ResourceMoved, HttpCodes.SeeOther, HttpCodes.TemporaryRedirect, HttpCodes.PermanentRedirect]; -const HttpResponseRetryCodes: number[] = [HttpCodes.BadGateway, HttpCodes.ServiceUnavailable, HttpCodes.GatewayTimeout]; -const RetryableHttpVerbs: string[] = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; -const ExponentialBackoffCeiling = 10; -const ExponentialBackoffTimeSlice = 5; +const HttpRedirectCodes: number[] = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +] +const HttpResponseRetryCodes: number[] = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +] +const RetryableHttpVerbs: string[] = ['OPTIONS', 'GET', 'DELETE', 'HEAD'] +const ExponentialBackoffCeiling = 10 +const ExponentialBackoffTimeSlice = 5 export class HttpClientResponse implements ifm.IHttpClientResponse { - constructor(message: http.IncomingMessage) { - this.message = message; - } - - public message: http.IncomingMessage; - readBody(): Promise { - return new Promise(async (resolve, reject) => { - let output = Buffer.alloc(0); - - this.message.on('data', (chunk: Buffer) => { - output = Buffer.concat([output, chunk]); - }); - - this.message.on('end', () => { - resolve(output.toString()); - }); - }); - } + constructor(message: http.IncomingMessage) { + this.message = message + } + + public message: http.IncomingMessage + readBody(): Promise { + return new Promise(async (resolve, reject) => { + let output = Buffer.alloc(0) + + this.message.on('data', (chunk: Buffer) => { + output = Buffer.concat([output, chunk]) + }) + + this.message.on('end', () => { + resolve(output.toString()) + }) + }) + } } export function isHttps(requestUrl: string) { - let parsedUrl: url.Url = url.parse(requestUrl); - return parsedUrl.protocol === 'https:'; + let parsedUrl: url.Url = url.parse(requestUrl) + return parsedUrl.protocol === 'https:' } export class HttpClient { - userAgent: string | undefined; - handlers: ifm.IRequestHandler[]; - requestOptions: ifm.IRequestOptions; - - private _ignoreSslError: boolean = false; - private _socketTimeout: number; - private _allowRedirects: boolean = true; - private _allowRedirectDowngrade: boolean = false; - private _maxRedirects: number = 50; - private _allowRetries: boolean = false; - private _maxRetries: number = 1; - private _agent; - private _proxyAgent; - private _keepAlive: boolean = false; - private _disposed: boolean = false; - - constructor(userAgent?: string, handlers?: ifm.IRequestHandler[], requestOptions?: ifm.IRequestOptions) { - this.userAgent = userAgent; - this.handlers = handlers || []; - this.requestOptions = requestOptions; - if (requestOptions) { - if (requestOptions.ignoreSslError != null) { - this._ignoreSslError = requestOptions.ignoreSslError; - } - - this._socketTimeout = requestOptions.socketTimeout; - - if (requestOptions.allowRedirects != null) { - this._allowRedirects = requestOptions.allowRedirects; - } - - if (requestOptions.allowRedirectDowngrade != null) { - this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; - } - - if (requestOptions.maxRedirects != null) { - this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); - } - - if (requestOptions.keepAlive != null) { - this._keepAlive = requestOptions.keepAlive; - } - - if (requestOptions.allowRetries != null) { - this._allowRetries = requestOptions.allowRetries; - } - - if (requestOptions.maxRetries != null) { - this._maxRetries = requestOptions.maxRetries; - } - } + userAgent: string | undefined + handlers: ifm.IRequestHandler[] + requestOptions: ifm.IRequestOptions + + private _ignoreSslError: boolean = false + private _socketTimeout: number + private _allowRedirects: boolean = true + private _allowRedirectDowngrade: boolean = false + private _maxRedirects: number = 50 + private _allowRetries: boolean = false + private _maxRetries: number = 1 + private _agent + private _proxyAgent + private _keepAlive: boolean = false + private _disposed: boolean = false + + constructor( + userAgent?: string, + handlers?: ifm.IRequestHandler[], + requestOptions?: ifm.IRequestOptions + ) { + this.userAgent = userAgent + this.handlers = handlers || [] + this.requestOptions = requestOptions + if (requestOptions) { + if (requestOptions.ignoreSslError != null) { + this._ignoreSslError = requestOptions.ignoreSslError + } + + this._socketTimeout = requestOptions.socketTimeout + + if (requestOptions.allowRedirects != null) { + this._allowRedirects = requestOptions.allowRedirects + } + + if (requestOptions.allowRedirectDowngrade != null) { + this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade + } + + if (requestOptions.maxRedirects != null) { + this._maxRedirects = Math.max(requestOptions.maxRedirects, 0) + } + + if (requestOptions.keepAlive != null) { + this._keepAlive = requestOptions.keepAlive + } + + if (requestOptions.allowRetries != null) { + this._allowRetries = requestOptions.allowRetries + } + + if (requestOptions.maxRetries != null) { + this._maxRetries = requestOptions.maxRetries + } } - - public options(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise { - return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + } + + public options( + requestUrl: string, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}) + } + + public get( + requestUrl: string, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request('GET', requestUrl, null, additionalHeaders || {}) + } + + public del( + requestUrl: string, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}) + } + + public post( + requestUrl: string, + data: string, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request('POST', requestUrl, data, additionalHeaders || {}) + } + + public patch( + requestUrl: string, + data: string, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}) + } + + public put( + requestUrl: string, + data: string, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request('PUT', requestUrl, data, additionalHeaders || {}) + } + + public head( + requestUrl: string, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}) + } + + public sendStream( + verb: string, + requestUrl: string, + stream: NodeJS.ReadableStream, + additionalHeaders?: ifm.IHeaders + ): Promise { + return this.request(verb, requestUrl, stream, additionalHeaders) + } + + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + public async getJson( + requestUrl: string, + additionalHeaders: ifm.IHeaders = {} + ): Promise> { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( + additionalHeaders, + Headers.Accept, + MediaTypes.ApplicationJson + ) + let res: ifm.IHttpClientResponse = await this.get( + requestUrl, + additionalHeaders + ) + return this._processResponse(res, this.requestOptions) + } + + public async postJson( + requestUrl: string, + obj: any, + additionalHeaders: ifm.IHeaders = {} + ): Promise> { + let data: string = JSON.stringify(obj, null, 2) + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( + additionalHeaders, + Headers.Accept, + MediaTypes.ApplicationJson + ) + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader( + additionalHeaders, + Headers.ContentType, + MediaTypes.ApplicationJson + ) + let res: ifm.IHttpClientResponse = await this.post( + requestUrl, + data, + additionalHeaders + ) + return this._processResponse(res, this.requestOptions) + } + + public async putJson( + requestUrl: string, + obj: any, + additionalHeaders: ifm.IHeaders = {} + ): Promise> { + let data: string = JSON.stringify(obj, null, 2) + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( + additionalHeaders, + Headers.Accept, + MediaTypes.ApplicationJson + ) + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader( + additionalHeaders, + Headers.ContentType, + MediaTypes.ApplicationJson + ) + let res: ifm.IHttpClientResponse = await this.put( + requestUrl, + data, + additionalHeaders + ) + return this._processResponse(res, this.requestOptions) + } + + public async patchJson( + requestUrl: string, + obj: any, + additionalHeaders: ifm.IHeaders = {} + ): Promise> { + let data: string = JSON.stringify(obj, null, 2) + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( + additionalHeaders, + Headers.Accept, + MediaTypes.ApplicationJson + ) + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader( + additionalHeaders, + Headers.ContentType, + MediaTypes.ApplicationJson + ) + let res: ifm.IHttpClientResponse = await this.patch( + requestUrl, + data, + additionalHeaders + ) + return this._processResponse(res, this.requestOptions) + } + + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + public async request( + verb: string, + requestUrl: string, + data: string | NodeJS.ReadableStream, + headers: ifm.IHeaders + ): Promise { + if (this._disposed) { + throw new Error('Client has already been disposed.') } - public get(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise { - return this.request('GET', requestUrl, null, additionalHeaders || {}); - } + let parsedUrl = url.parse(requestUrl) + let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers) + + // Only perform retries on reads since writes may not be idempotent. + let maxTries: number = + this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1 + ? this._maxRetries + 1 + : 1 + let numTries: number = 0 + + let response: HttpClientResponse + while (numTries < maxTries) { + response = await this.requestRaw(info, data) + + // Check if it's an authentication challenge + if ( + response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized + ) { + let authenticationHandler: ifm.IRequestHandler + + for (let i = 0; i < this.handlers.length; i++) { + if (this.handlers[i].canHandleAuthentication(response)) { + authenticationHandler = this.handlers[i] + break + } + } - public del(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise { - return this.request('DELETE', requestUrl, null, additionalHeaders || {}); - } + if (authenticationHandler) { + return authenticationHandler.handleAuthentication(this, info, data) + } else { + // We have received an unauthorized response but have no handlers to handle it. + // Let the response return to the caller. + return response + } + } + + let redirectsRemaining: number = this._maxRedirects + while ( + HttpRedirectCodes.indexOf(response.message.statusCode) != -1 && + this._allowRedirects && + redirectsRemaining > 0 + ) { + const redirectUrl: string | null = response.message.headers['location'] + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break + } + let parsedRedirectUrl = url.parse(redirectUrl) + if ( + parsedUrl.protocol == 'https:' && + parsedUrl.protocol != parsedRedirectUrl.protocol && + !this._allowRedirectDowngrade + ) { + throw new Error( + 'Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.' + ) + } - public post(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise { - return this.request('POST', requestUrl, data, additionalHeaders || {}); - } + // we need to finish reading the response before reassigning response + // which will leak the open socket. + await response.readBody() - public patch(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise { - return this.request('PATCH', requestUrl, data, additionalHeaders || {}); - } + // let's make the request with the new redirectUrl + info = this._prepareRequest(verb, parsedRedirectUrl, headers) + response = await this.requestRaw(info, data) + redirectsRemaining-- + } - public put(requestUrl: string, data: string, additionalHeaders?: ifm.IHeaders): Promise { - return this.request('PUT', requestUrl, data, additionalHeaders || {}); - } + if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) { + // If not a retry code, return immediately instead of retrying + return response + } - public head(requestUrl: string, additionalHeaders?: ifm.IHeaders): Promise { - return this.request('HEAD', requestUrl, null, additionalHeaders || {}); - } + numTries += 1 - public sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: ifm.IHeaders): Promise { - return this.request(verb, requestUrl, stream, additionalHeaders); + if (numTries < maxTries) { + await response.readBody() + await this._performExponentialBackoff(numTries) + } } - /** - * Gets a typed object from an endpoint - * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise - */ - public async getJson(requestUrl: string, additionalHeaders: ifm.IHeaders = {}): Promise> { - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson) - let res: ifm.IHttpClientResponse = await this.get(requestUrl, additionalHeaders); - return this._processResponse(res, this.requestOptions); - } - - public async postJson(requestUrl: string, obj: any, additionalHeaders: ifm.IHeaders = {}): Promise> { - let data: string = JSON.stringify(obj, null, 2); - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson) - additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); - let res: ifm.IHttpClientResponse = await this.post(requestUrl, data, additionalHeaders); - return this._processResponse(res, this.requestOptions); - } + return response + } - public async putJson(requestUrl: string, obj: any, additionalHeaders: ifm.IHeaders = {}): Promise> { - let data: string = JSON.stringify(obj, null, 2); - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson) - additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); - let res: ifm.IHttpClientResponse = await this.put(requestUrl, data, additionalHeaders); - return this._processResponse(res, this.requestOptions); - } - - public async patchJson(requestUrl: string, obj: any, additionalHeaders: ifm.IHeaders = {}): Promise> { - let data: string = JSON.stringify(obj, null, 2); - additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson) - additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); - let res: ifm.IHttpClientResponse = await this.patch(requestUrl, data, additionalHeaders); - return this._processResponse(res, this.requestOptions); - } - - /** - * Makes a raw http request. - * All other methods such as get, post, patch, and request ultimately call this. - * Prefer get, del, post and patch - */ - public async request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: ifm.IHeaders): Promise { - if (this._disposed) { - throw new Error("Client has already been disposed."); - } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + public dispose() { + if (this._agent) { + this._agent.destroy() + } - let parsedUrl = url.parse(requestUrl); - let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers); - - // Only perform retries on reads since writes may not be idempotent. - let maxTries: number = (this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1) ? this._maxRetries + 1 : 1; - let numTries: number = 0; - - let response: HttpClientResponse; - while (numTries < maxTries) { - response = await this.requestRaw(info, data); - - // Check if it's an authentication challenge - if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { - let authenticationHandler: ifm.IRequestHandler; - - for (let i = 0; i < this.handlers.length; i++) { - if (this.handlers[i].canHandleAuthentication(response)) { - authenticationHandler = this.handlers[i]; - break; - } - } - - if (authenticationHandler) { - return authenticationHandler.handleAuthentication(this, info, data); - } - else { - // We have received an unauthorized response but have no handlers to handle it. - // Let the response return to the caller. - return response; - } - } - - let redirectsRemaining: number = this._maxRedirects; - while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1 - && this._allowRedirects - && redirectsRemaining > 0) { - - const redirectUrl: string | null = response.message.headers["location"]; - if (!redirectUrl) { - // if there's no location to redirect to, we won't - break; - } - let parsedRedirectUrl = url.parse(redirectUrl); - if (parsedUrl.protocol == 'https:' && parsedUrl.protocol != parsedRedirectUrl.protocol && !this._allowRedirectDowngrade) { - throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true."); - } - - // we need to finish reading the response before reassigning response - // which will leak the open socket. - await response.readBody(); - - // let's make the request with the new redirectUrl - info = this._prepareRequest(verb, parsedRedirectUrl, headers); - response = await this.requestRaw(info, data); - redirectsRemaining--; - } - - if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) { - // If not a retry code, return immediately instead of retrying - return response; - } - - numTries += 1; - - if (numTries < maxTries) { - await response.readBody(); - await this._performExponentialBackoff(numTries); - } + this._disposed = true + } + + /** + * Raw request. + * @param info + * @param data + */ + public requestRaw( + info: ifm.IRequestInfo, + data: string | NodeJS.ReadableStream + ): Promise { + return new Promise((resolve, reject) => { + let callbackForResult = function ( + err: any, + res: ifm.IHttpClientResponse + ) { + if (err) { + reject(err) } - return response; + resolve(res) + } + + this.requestRawWithCallback(info, data, callbackForResult) + }) + } + + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + public requestRawWithCallback( + info: ifm.IRequestInfo, + data: string | NodeJS.ReadableStream, + onResult: (err: any, res: ifm.IHttpClientResponse) => void + ): void { + let socket + + if (typeof data === 'string') { + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8') } - /** - * Needs to be called if keepAlive is set to true in request options. - */ - public dispose() { - if (this._agent) { - this._agent.destroy(); - } - - this._disposed = true; + let callbackCalled: boolean = false + let handleResult = (err: any, res: HttpClientResponse) => { + if (!callbackCalled) { + callbackCalled = true + onResult(err, res) + } } - /** - * Raw request. - * @param info - * @param data - */ - public requestRaw(info: ifm.IRequestInfo, data: string | NodeJS.ReadableStream): Promise { - return new Promise((resolve, reject) => { - let callbackForResult = function (err: any, res: ifm.IHttpClientResponse) { - if (err) { - reject(err); - } - - resolve(res); - }; - - this.requestRawWithCallback(info, data, callbackForResult); - }); + let req: http.ClientRequest = info.httpModule.request( + info.options, + (msg: http.IncomingMessage) => { + let res: HttpClientResponse = new HttpClientResponse(msg) + handleResult(null, res) + } + ) + + req.on('socket', sock => { + socket = sock + }) + + // If we ever get disconnected, we want the socket to timeout eventually + req.setTimeout(this._socketTimeout || 3 * 60000, () => { + if (socket) { + socket.end() + } + handleResult(new Error('Request timeout: ' + info.options.path), null) + }) + + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err, null) + }) + + if (data && typeof data === 'string') { + req.write(data, 'utf8') } - /** - * Raw request with callback. - * @param info - * @param data - * @param onResult - */ - public requestRawWithCallback(info: ifm.IRequestInfo, data: string | NodeJS.ReadableStream, onResult: (err: any, res: ifm.IHttpClientResponse) => void): void { - let socket; - - if (typeof (data) === 'string') { - info.options.headers["Content-Length"] = Buffer.byteLength(data, 'utf8'); - } + if (data && typeof data !== 'string') { + data.on('close', function () { + req.end() + }) - let callbackCalled: boolean = false; - let handleResult = (err: any, res: HttpClientResponse) => { - if (!callbackCalled) { - callbackCalled = true; - onResult(err, res); - } - }; - - let req: http.ClientRequest = info.httpModule.request(info.options, (msg: http.IncomingMessage) => { - let res: HttpClientResponse = new HttpClientResponse(msg); - handleResult(null, res); - }); - - req.on('socket', (sock) => { - socket = sock; - }); - - // If we ever get disconnected, we want the socket to timeout eventually - req.setTimeout(this._socketTimeout || 3 * 60000, () => { - if (socket) { - socket.end(); - } - handleResult(new Error('Request timeout: ' + info.options.path), null); - }); - - req.on('error', function (err) { - // err has statusCode property - // res should have headers - handleResult(err, null); - }); - - if (data && typeof (data) === 'string') { - req.write(data, 'utf8'); - } + data.pipe(req) + } else { + req.end() + } + } + + /** + * 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 = {} + + info.parsedUrl = requestUrl + const usingSsl: boolean = info.parsedUrl.protocol === 'https:' + info.httpModule = usingSsl ? https : http + const defaultPort: number = usingSsl ? 443 : 80 + + info.options = {} + info.options.host = info.parsedUrl.hostname + info.options.port = info.parsedUrl.port + ? parseInt(info.parsedUrl.port) + : defaultPort + info.options.path = + (info.parsedUrl.pathname || '') + (info.parsedUrl.search || '') + info.options.method = method + info.options.headers = this._mergeHeaders(headers) + if (this.userAgent != null) { + info.options.headers['user-agent'] = this.userAgent + } - if (data && typeof (data) !== 'string') { - data.on('close', function () { - req.end(); - }); + info.options.agent = this._getAgent(info.parsedUrl) - data.pipe(req); - } - else { - req.end(); - } + // gives handlers an opportunity to participate + if (this.handlers) { + this.handlers.forEach(handler => { + handler.prepareRequest(info.options) + }) } - /** - * 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) - } + return info + } - private _prepareRequest(method: string, requestUrl: url.Url, headers: ifm.IHeaders): ifm.IRequestInfo { - const info: ifm.IRequestInfo = {}; - - info.parsedUrl = requestUrl; - const usingSsl: boolean = info.parsedUrl.protocol === 'https:'; - info.httpModule = usingSsl ? https : http; - const defaultPort: number = usingSsl ? 443 : 80; - - info.options = {}; - info.options.host = info.parsedUrl.hostname; - info.options.port = info.parsedUrl.port ? parseInt(info.parsedUrl.port) : defaultPort; - info.options.path = (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); - info.options.method = method; - info.options.headers = this._mergeHeaders(headers); - if (this.userAgent != null) { - info.options.headers["user-agent"] = this.userAgent; - } - - info.options.agent = this._getAgent(info.parsedUrl); - - // gives handlers an opportunity to participate - if (this.handlers) { - this.handlers.forEach((handler) => { - handler.prepareRequest(info.options); - }); - } + private _mergeHeaders(headers: ifm.IHeaders): ifm.IHeaders { + const lowercaseKeys = obj => + Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}) - return info; + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign( + {}, + lowercaseKeys(this.requestOptions.headers), + lowercaseKeys(headers) + ) } - private _mergeHeaders(headers: ifm.IHeaders) : ifm.IHeaders { - const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {}); + return lowercaseKeys(headers || {}) + } - if (this.requestOptions && this.requestOptions.headers) { - return Object.assign( - {}, - lowercaseKeys(this.requestOptions.headers), - lowercaseKeys(headers) - ); - } + private _getExistingOrDefaultHeader( + additionalHeaders: ifm.IHeaders, + header: string, + _default: string + ) { + const lowercaseKeys = obj => + Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}) - return lowercaseKeys(headers || {}); + let clientHeader: string + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header] } + return additionalHeaders[header] || clientHeader || _default + } - private _getExistingOrDefaultHeader(additionalHeaders: ifm.IHeaders, header: string, _default: string) { - const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {}); + private _getAgent(parsedUrl: url.Url): http.Agent { + let agent + let proxyUrl: url.Url = pm.getProxyUrl(parsedUrl) + let useProxy = proxyUrl && proxyUrl.hostname - let clientHeader: string; - if(this.requestOptions && this.requestOptions.headers) { - clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; - } - return additionalHeaders[header] || clientHeader || _default; + if (this._keepAlive && useProxy) { + agent = this._proxyAgent } - private _getAgent(parsedUrl: url.Url): http.Agent { - let agent; - let proxyUrl: url.Url = pm.getProxyUrl(parsedUrl); - let useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && !useProxy) { + agent = this._agent + } - if (this._keepAlive && useProxy) { - agent = this._proxyAgent; - } + // if agent is already assigned use that agent. + if (!!agent) { + return agent + } - if (this._keepAlive && !useProxy) { - agent = this._agent; - } + const usingSsl = parsedUrl.protocol === 'https:' + let maxSockets = 100 + if (!!this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets + } - // if agent is already assigned use that agent. - if (!!agent) { - return agent; + if (useProxy) { + // If using proxy, need tunnel + if (!tunnel) { + tunnel = require('tunnel') + } + + const agentOptions = { + maxSockets: maxSockets, + keepAlive: this._keepAlive, + proxy: { + proxyAuth: proxyUrl.auth, + host: proxyUrl.hostname, + port: proxyUrl.port } + } + + let tunnelAgent: Function + const overHttps = proxyUrl.protocol === 'https:' + if (usingSsl) { + tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp + } else { + tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp + } + + agent = tunnelAgent(agentOptions) + this._proxyAgent = agent + } - const usingSsl = parsedUrl.protocol === 'https:'; - let maxSockets = 100; - if (!!this.requestOptions) { - maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets - } + // if reusing agent across request and tunneling agent isn't assigned create a new agent + if (this._keepAlive && !agent) { + const options = {keepAlive: this._keepAlive, maxSockets: maxSockets} + agent = usingSsl ? new https.Agent(options) : new http.Agent(options) + this._agent = agent + } - if (useProxy) { - // If using proxy, need tunnel - if (!tunnel) { - tunnel = require('tunnel'); - } - - const agentOptions = { - maxSockets: maxSockets, - keepAlive: this._keepAlive, - proxy: { - proxyAuth: proxyUrl.auth, - host: proxyUrl.hostname, - port: proxyUrl.port - }, - }; - - let tunnelAgent: Function; - const overHttps = proxyUrl.protocol === 'https:'; - if (usingSsl) { - tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; - } else { - tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; - } - - agent = tunnelAgent(agentOptions); - this._proxyAgent = agent; - } + // if not using private agent and tunnel agent isn't setup then use global agent + if (!agent) { + agent = usingSsl ? https.globalAgent : http.globalAgent + } - // if reusing agent across request and tunneling agent isn't assigned create a new agent - if (this._keepAlive && !agent) { - const options = { keepAlive: this._keepAlive, maxSockets: maxSockets }; - agent = usingSsl ? new https.Agent(options) : new http.Agent(options); - this._agent = agent; - } + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options || {}, { + rejectUnauthorized: false + }) + } - // if not using private agent and tunnel agent isn't setup then use global agent - if (!agent) { - agent = usingSsl ? https.globalAgent : http.globalAgent; - } + return agent + } + + private _performExponentialBackoff(retryNumber: number): Promise { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber) + const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber) + return new Promise(resolve => setTimeout(() => resolve(), ms)) + } + + private static dateTimeDeserializer(key: any, value: any): any { + if (typeof value === 'string') { + let a = new Date(value) + if (!isNaN(a.valueOf())) { + return a + } + } - if (usingSsl && this._ignoreSslError) { - // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process - // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options - // we have to cast it to any and change it directly - agent.options = Object.assign(agent.options || {}, { rejectUnauthorized: false }); + return value + } + + private async _processResponse( + res: ifm.IHttpClientResponse, + options: ifm.IRequestOptions + ): Promise> { + return new Promise>(async (resolve, reject) => { + const statusCode: number = res.message.statusCode + + const response: ifm.ITypedResponse = { + statusCode: statusCode, + result: null, + headers: {} + } + + // not found leads to null obj returned + if (statusCode == HttpCodes.NotFound) { + resolve(response) + } + + let obj: any + let contents: string + + // get the result from the body + try { + contents = await res.readBody() + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, HttpClient.dateTimeDeserializer) + } else { + obj = JSON.parse(contents) + } + + response.result = obj } - return agent; - } + response.headers = res.message.headers + } catch (err) { + // Invalid resource (contents not json); leaving result obj null + } + + // note that 3xx redirects are handled by the http layer. + if (statusCode > 299) { + let msg: string + + // if exception/error in body, attempt to get better error + if (obj && obj.message) { + msg = obj.message + } else if (contents && contents.length > 0) { + // it may be the case that the exception is in the body message as string + msg = contents + } else { + msg = 'Failed request: (' + statusCode + ')' + } - private _performExponentialBackoff(retryNumber: number): Promise { - retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); - const ms: number = ExponentialBackoffTimeSlice*Math.pow(2, retryNumber); - return new Promise(resolve => setTimeout(()=>resolve(), ms)); - } + let err: Error = new Error(msg) - private static dateTimeDeserializer(key: any, value: any): any { - if (typeof value === 'string'){ - let a = new Date(value); - if (!isNaN(a.valueOf())) { - return a; - } + // attach statusCode and body obj (if available) to the error object + err['statusCode'] = statusCode + if (response.result) { + err['result'] = response.result } - return value; - } - - private async _processResponse(res: ifm.IHttpClientResponse, options: ifm.IRequestOptions): Promise> { - return new Promise>(async (resolve, reject) => { - const statusCode: number = res.message.statusCode; - - const response: ifm.ITypedResponse = { - statusCode: statusCode, - result: null, - headers: {} - }; - - // not found leads to null obj returned - if (statusCode == HttpCodes.NotFound) { - resolve(response); - } - - let obj: any; - let contents: string; - - // get the result from the body - try { - contents = await res.readBody(); - if (contents && contents.length > 0) { - if (options && options.deserializeDates) { - obj = JSON.parse(contents, HttpClient.dateTimeDeserializer); - } else { - obj = JSON.parse(contents); - } - - response.result = obj; - } - - response.headers = res.message.headers; - } - catch (err) { - // Invalid resource (contents not json); leaving result obj null - } - - // note that 3xx redirects are handled by the http layer. - if (statusCode > 299) { - let msg: string; - - // if exception/error in body, attempt to get better error - if (obj && obj.message) { - msg = obj.message; - } else if (contents && contents.length > 0) { - // it may be the case that the exception is in the body message as string - msg = contents; - } else { - msg = "Failed request: (" + statusCode + ")"; - } - - let err: Error = new Error(msg); - - // attach statusCode and body obj (if available) to the error object - err['statusCode'] = statusCode; - if (response.result) { - err['result'] = response.result; - } - - reject(err); - } else { - resolve(response); - } - }); - } + reject(err) + } else { + resolve(response) + } + }) + } } diff --git a/interfaces.ts b/interfaces.ts index 733d76b..84d9fdb 100644 --- a/interfaces.ts +++ b/interfaces.ts @@ -1,55 +1,99 @@ -import http = require("http"); -import url = require("url"); +import http = require('http') +import url = require('url') -export interface IHeaders { [key: string]: any }; +export interface IHeaders { + [key: string]: any +} export interface IHttpClient { - options(requestUrl: string, additionalHeaders?: IHeaders): Promise; - get(requestUrl: string, additionalHeaders?: IHeaders): Promise; - del(requestUrl: string, additionalHeaders?: IHeaders): Promise; - post(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise; - patch(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise; - put(requestUrl: string, data: string, additionalHeaders?: IHeaders): Promise; - sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: IHeaders): Promise; - request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: IHeaders): Promise; - requestRaw(info: IRequestInfo, data: string | NodeJS.ReadableStream): Promise; - requestRawWithCallback(info: IRequestInfo, data: string | NodeJS.ReadableStream, onResult: (err: any, res: IHttpClientResponse) => void): void; + options( + requestUrl: string, + additionalHeaders?: IHeaders + ): Promise + get( + requestUrl: string, + additionalHeaders?: IHeaders + ): Promise + del( + requestUrl: string, + additionalHeaders?: IHeaders + ): Promise + post( + requestUrl: string, + data: string, + additionalHeaders?: IHeaders + ): Promise + patch( + requestUrl: string, + data: string, + additionalHeaders?: IHeaders + ): Promise + put( + requestUrl: string, + data: string, + additionalHeaders?: IHeaders + ): Promise + sendStream( + verb: string, + requestUrl: string, + stream: NodeJS.ReadableStream, + additionalHeaders?: IHeaders + ): Promise + request( + verb: string, + requestUrl: string, + data: string | NodeJS.ReadableStream, + headers: IHeaders + ): Promise + requestRaw( + info: IRequestInfo, + data: string | NodeJS.ReadableStream + ): Promise + requestRawWithCallback( + info: IRequestInfo, + data: string | NodeJS.ReadableStream, + onResult: (err: any, res: IHttpClientResponse) => void + ): void } export interface IRequestHandler { - prepareRequest(options: http.RequestOptions): void; - canHandleAuthentication(response: IHttpClientResponse): boolean; - handleAuthentication(httpClient: IHttpClient, requestInfo: IRequestInfo, objs): Promise; + prepareRequest(options: http.RequestOptions): void + canHandleAuthentication(response: IHttpClientResponse): boolean + handleAuthentication( + httpClient: IHttpClient, + requestInfo: IRequestInfo, + objs + ): Promise } export interface IHttpClientResponse { - message: http.IncomingMessage; - readBody(): Promise; + message: http.IncomingMessage + readBody(): Promise } export interface IRequestInfo { - options: http.RequestOptions; - parsedUrl: url.Url; - httpModule: any; + options: http.RequestOptions + parsedUrl: url.Url + httpModule: any } export interface IRequestOptions { - headers?: IHeaders; - socketTimeout?: number; - ignoreSslError?: boolean; - allowRedirects?: boolean; - allowRedirectDowngrade?: boolean; - maxRedirects?: number; - maxSockets?: number; - keepAlive?: boolean; - deserializeDates?: boolean; - // Allows retries only on Read operations (since writes may not be idempotent) - allowRetries?: boolean; - maxRetries?: number; + headers?: IHeaders + socketTimeout?: number + ignoreSslError?: boolean + allowRedirects?: boolean + allowRedirectDowngrade?: boolean + maxRedirects?: number + maxSockets?: number + keepAlive?: boolean + deserializeDates?: boolean + // Allows retries only on Read operations (since writes may not be idempotent) + allowRetries?: boolean + maxRetries?: number } export interface ITypedResponse { - statusCode: number, - result: T | null, - headers: Object + statusCode: number + result: T | null + headers: Object } diff --git a/package-lock.json b/package-lock.json index 25a3f03..7a48ba6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3416,7 +3416,7 @@ "prettier": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz", - "integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==", + "integrity": "sha1-LRuuFz41WZbuNV7Jgwp6HuBUV+8=", "dev": true }, "pretty-format": { diff --git a/package.json b/package.json index e873fd2..49f2e08 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "scripts": { "build": "rm -Rf ./_out && tsc && cp package*.json ./_out && cp *.md ./_out && cp LICENSE ./_out && cp actions.png ./_out", "test": "jest", - "format": "prettier --write packages/**/*.ts", - "format-check": "prettier --check packages/**/*.ts", + "format": "prettier --write *.ts && prettier --write **/*.ts", + "format-check": "prettier --check *.ts && prettier --check **/*.ts", "audit-check": "npm audit --audit-level=moderate" }, "repository": { diff --git a/proxy.ts b/proxy.ts index 76c59b0..1965cb7 100644 --- a/proxy.ts +++ b/proxy.ts @@ -1,65 +1,62 @@ -import * as url from 'url'; +import * as url from 'url' export function getProxyUrl(reqUrl: url.Url): url.Url | undefined { - let usingSsl = reqUrl.protocol === 'https:'; + let usingSsl = reqUrl.protocol === 'https:' - let proxyUrl: url.Url; - if (checkBypass(reqUrl)) { - return proxyUrl; - } + let proxyUrl: url.Url + if (checkBypass(reqUrl)) { + return proxyUrl + } - let proxyVar: string; - if (usingSsl) { - proxyVar = process.env["https_proxy"] || - process.env["HTTPS_PROXY"]; - - } else { - proxyVar = process.env["http_proxy"] || - process.env["HTTP_PROXY"]; - } + let proxyVar: string + if (usingSsl) { + proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'] + } else { + proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'] + } - if (proxyVar) { - proxyUrl = url.parse(proxyVar); - } + if (proxyVar) { + proxyUrl = url.parse(proxyVar) + } - return proxyUrl; + return proxyUrl } - export function checkBypass(reqUrl: url.Url): boolean { - if (!reqUrl.hostname) { - return false - } - - let noProxy = process.env["no_proxy"] || process.env["NO_PROXY"] || ''; - if (!noProxy) { - return false - } - - // Determine the request port - let reqPort: number - if (reqUrl.port) { - reqPort = Number(reqUrl.port) - } - else if (reqUrl.protocol === 'http:') { - reqPort = 80 - } - else if (reqUrl.protocol === 'https:') { - reqPort = 443 - } - - // Format the request hostname and hostname with port - let upperReqHosts = [reqUrl.hostname.toUpperCase()] - if (typeof reqPort === 'number') { - upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`) - } - - // Compare request host against noproxy - for (let upperNoProxyItem of noProxy.split(',').map(x => x.trim().toUpperCase()).filter(x => x)) { - if (upperReqHosts.some(x => x === upperNoProxyItem)) { - return true - } - } + if (!reqUrl.hostname) { + return false + } + let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '' + if (!noProxy) { return false -} \ No newline at end of file + } + + // Determine the request port + let reqPort: number + if (reqUrl.port) { + reqPort = Number(reqUrl.port) + } else if (reqUrl.protocol === 'http:') { + reqPort = 80 + } else if (reqUrl.protocol === 'https:') { + reqPort = 443 + } + + // Format the request hostname and hostname with port + let upperReqHosts = [reqUrl.hostname.toUpperCase()] + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`) + } + + // Compare request host against noproxy + for (let upperNoProxyItem of noProxy + .split(',') + .map(x => x.trim().toUpperCase()) + .filter(x => x)) { + if (upperReqHosts.some(x => x === upperNoProxyItem)) { + return true + } + } + + return false +}