diff --git a/API.md b/API.md index f183bef..83db929 100755 --- a/API.md +++ b/API.md @@ -130,6 +130,13 @@ Initiate an HTTP request. - `timeout` - number of milliseconds to wait without receiving a response before aborting the request. Defaults to `0` (no limit). + - `lookup` - DNS lookup function. see [http.request(url[,options][,callback])](https://nodejs.org/api/http.html#httprequestoptions-callback). Defaults to [dns.lookup()](https://nodejs.org/api/dns.html#dnslookuphostname-options-callback). + + - `family` - IP address family to use when resolving `host` or `hostname`. Valid values are `4` or `6`. When unspecified, both IP v4 and v6 will be used. + + - `hints` - Optional `dns.lookup()` hints. + + Returns a promise that resolves into a node response object. The promise has a `req` property which is the instance of the node.js [ClientRequest](http://nodejs.org/api/http.html#http_class_http_clientrequest) object. ### `read(response, options)` diff --git a/lib/index.d.ts b/lib/index.d.ts index 414e58f..7f808be 100755 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -5,6 +5,7 @@ import * as Http from 'http'; import * as Https from 'https'; import * as Stream from 'stream'; import * as Url from 'url'; +import { LookupFunction } from "node:net" import { Boom } from '@hapi/boom'; @@ -198,6 +199,21 @@ declare namespace Client { */ readonly agent?: Http.Agent | Https.Agent | false; + /** + * Custom lookup function. Default: dns.lookup(). + */ + readonly lookup?: LookupFunction; + + /** + * IP address family to use when resolving host or hostname. Valid values are 4 or 6. When unspecified, both IP v4 and v6 will be used. + */ + readonly family?: number; + + /** + * Optional dns.lookup() hints. + */ + readonly hints?: number; + /** * Fully qualified URL string used as the base URL. */ diff --git a/lib/index.js b/lib/index.js index 20f7a7d..e878f8b 100755 --- a/lib/index.js +++ b/lib/index.js @@ -18,7 +18,8 @@ const Tap = require('./tap'); const internals = { jsonRegex: /^application\/([a-z0-9.]*[+-]json|json)$/, - shallowOptions: ['agent', 'agents', 'beforeRedirect', 'payload', 'redirected'] + shallowOptions: ['agent', 'agents', 'beforeRedirect', 'payload', 'redirected'], + httpOptions: ['secureProtocol', 'ciphers', 'lookup', 'family', 'hints'] }; @@ -153,6 +154,12 @@ internals.Client = class { const client = uri.protocol === 'https:' ? Https : Http; + for (const option of internals.httpOptions) { + if (options[option] !== undefined) { + uri[option] = options[option]; + } + } + if (options.rejectUnauthorized !== undefined && uri.protocol === 'https:') { @@ -167,14 +174,6 @@ internals.Client = class { uri.agent = uri.protocol === 'https:' ? this.agents.https : this.agents.http; } - if (options.secureProtocol !== undefined) { - uri.secureProtocol = options.secureProtocol; - } - - if (options.ciphers !== undefined) { - uri.ciphers = options.ciphers; - } - this._emit('preRequest', uri, options); const start = Date.now(); diff --git a/test/index.js b/test/index.js index 33fbb37..251fe20 100755 --- a/test/index.js +++ b/test/index.js @@ -7,6 +7,7 @@ const Fs = require('fs'); const Events = require('events'); const Stream = require('stream'); const Zlib = require('zlib'); +const Dns = require('dns'); const Boom = require('@hapi/boom'); const Code = require('@hapi/code'); @@ -1196,6 +1197,77 @@ describe('request()', () => { }); }); +describe('options.lookup', () => { + + it('uses the lookup function to resolve the server ip address', async (flags) => { + + let dnsLookupCalled = false; + + const server = await internals.server('ok'); + flags.onCleanup = () => server.close(); + await Wreck.request('get', `http://localhost:${server.address().port}/`, { + lookup: (hostname, options, callback) => { + + dnsLookupCalled = true; + return Dns.lookup(hostname, options, callback); + } + }); + expect(dnsLookupCalled).to.equal(true); + }); + + it('uses the lookup function and fails if the lookup function rejects the domain', async (flags) => { + + const server = await internals.server('ok'); + const promise = Wreck.request('get', `http://localhost:${server.address().port}/`, { + lookup: (_hostname, _options, callback) => callback(new Error('failed lookup')) + }); + await expect(promise).to.reject('Client request error: failed lookup'); + flags.onCleanup = () => server.close(); + }); +}); + +describe('options.hints', () => { + + it('passes the hint parameter to the lookup function to resolve the server ip address', async (flags) => { + + const expectedHints = Dns.ADDRCONFIG; + let actualHints; + + const server = await internals.server('ok'); + flags.onCleanup = () => server.close(); + await Wreck.request('get', `http://localhost:${server.address().port}/`, { + lookup: (hostname, options, callback) => { + + actualHints = options.hints; + return Dns.lookup(hostname, options, callback); + }, + hints: expectedHints + }); + expect(actualHints).to.equal(expectedHints); + }); +}); + +describe('options.family', () => { + + it('passes the family parameter to the lookup function to resolve the server ip address', async (flags) => { + + const expectedFamily = 4; + let actualFamily; + + const server = await internals.server('ok'); + flags.onCleanup = () => server.close(); + await Wreck.request('get', `http://localhost:${server.address().port}/`, { + lookup: (hostname, options, callback) => { + + actualFamily = options.family; + return Dns.lookup(hostname, options, callback); + }, + family: expectedFamily // IPv4 + }); + expect(actualFamily).to.equal(expectedFamily); + }); +}); + describe('options.baseUrl', () => { it('uses path when path is a full URL', async (flags) => {