Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for a custom lookup function; #5339

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -514,4 +514,4 @@
- [Marco Weber](https://github.com/mrcwbr)
- [Luca Pizzini](https://github.com/lpizzinidev)
- [Willian Agostini](https://github.com/WillianAgostini)
- [Huyen Nguyen](https://github.com/huyenltnguyen)
- [Huyen Nguyen](https://github.com/huyenltnguyen)
3 changes: 3 additions & 0 deletions index.d.cts
Expand Up @@ -403,6 +403,9 @@ declare namespace axios {
FormData?: new (...args: any[]) => object;
};
formSerializer?: FormSerializerOptions;
family?: 4 | 6 | undefined;
lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: string, family: number) => void) => void) |
((hostname: string, options: object) => Promise<[address: string, family: number] | string>);
}

// Alias
Expand Down
5 changes: 4 additions & 1 deletion index.d.ts
Expand Up @@ -329,7 +329,7 @@ export interface AxiosRequestConfig<D = any> {
maxBodyLength?: number;
maxRedirects?: number;
maxRate?: number | [MaxUploadRate, MaxDownloadRate];
beforeRedirect?: (options: Record<string, any>, responseDetails: {headers: Record<string, string>}) => void;
beforeRedirect?: (options: Record<string, any>, responseDetails: { headers: Record<string, string> }) => void;
socketPath?: string | null;
transport?: any;
httpAgent?: any;
Expand All @@ -344,6 +344,9 @@ export interface AxiosRequestConfig<D = any> {
FormData?: new (...args: any[]) => object;
};
formSerializer?: FormSerializerOptions;
family?: 4 | 6 | undefined;
lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: string, family: number) => void) => void) |
((hostname: string, options: object) => Promise<[address: string, family: number] | string>);
}

// Alias
Expand Down
16 changes: 15 additions & 1 deletion lib/adapters/http.js
Expand Up @@ -23,6 +23,7 @@
import formDataToStream from "../helpers/formDataToStream.js";
import readBlob from "../helpers/readBlob.js";
import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js';
import callbackify from "../helpers/callbackify.js";

const zlibOptions = {
flush: zlib.constants.Z_SYNC_FLUSH,
Expand Down Expand Up @@ -146,13 +147,24 @@
/*eslint consistent-return:0*/
export default isHttpAdapterSupported && function httpAdapter(config) {
return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) {
let {data} = config;
let {data, lookup, family} = config;
const {responseType, responseEncoding} = config;
const method = config.method.toUpperCase();
let isDone;
let rejected = false;
let req;

if (lookup && utils.isAsyncFn(lookup)) {
lookup = callbackify(lookup, (entry) => {
if(utils.isString(entry)) {
entry = [entry, entry.indexOf('.') < 0 ? 6 : 4]
} else if (!utils.isArray(entry)) {
throw new TypeError('lookup async function must return an array [ip: string, family: number]]')
}
return entry;
})

Check notice

Code scanning / CodeQL

Semicolon insertion Note

Avoid automated semicolon insertion (97% of all statements in
the enclosing function
have an explicit semicolon).
}

// temporary internal emitter until the AxiosRequest class will be implemented
const emitter = new EventEmitter();

Expand Down Expand Up @@ -378,6 +390,8 @@
agents: { http: config.httpAgent, https: config.httpsAgent },
auth,
protocol,
family,
lookup,
beforeRedirect: dispatchBeforeRedirect,
beforeRedirects: {}
};
Expand Down
16 changes: 16 additions & 0 deletions lib/helpers/callbackify.js
@@ -0,0 +1,16 @@
import utils from "../utils.js";

const callbackify = (fn, reducer) => {
return utils.isAsyncFn(fn) ? function (...args) {
const cb = args.pop();
fn.apply(this, args).then((value) => {
try {
reducer ? cb(null, ...reducer(value)) : cb(null, value);
} catch (err) {
cb(err);
}
}, cb);
} : fn;
}

export default callbackify;
16 changes: 15 additions & 1 deletion lib/utils.js
Expand Up @@ -662,6 +662,15 @@ const toJSONObject = (obj) => {
return visit(obj, 0);
}

const [isPlainFunction, isAsyncFn, isGeneratorFn, isAsyncGeneratorFn] = (
(...fns) => fns.map(
({constructor})=> (thing) => thing && typeof thing === 'function' && thing.constructor === constructor
)
)(()=> {}, async()=>{}, function*(){}, async function*(){});

const isThenable = (thing) =>
thing && (isObject(thing) || isFunction(thing)) && isFunction(thing.then) && isFunction(thing.catch);

export default {
isArray,
isArrayBuffer,
Expand Down Expand Up @@ -711,5 +720,10 @@ export default {
ALPHABET,
generateString,
isSpecCompliantForm,
toJSONObject
toJSONObject,
isAsyncFn,
isGeneratorFn,
isAsyncGeneratorFn,
isPlainFunction,
isThenable
};
14 changes: 14 additions & 0 deletions test/module/typings/esm/index.ts
Expand Up @@ -627,3 +627,17 @@ for (const [header, value] of headers) {
}
});
}

// lookup
axios.get('/user', {
lookup: (hostname: string, opt: object, cb: (err: Error | null, address: string, family: number) => void) => {
cb(null, '127.0.0.1', 4);
}
});

// lookup async
axios.get('/user', {
lookup: (hostname: string, opt: object) => {
return ['127.0.0.1', 4];
}
});
61 changes: 61 additions & 0 deletions test/unit/adapters/http.js
Expand Up @@ -56,6 +56,8 @@ var noop = ()=> {};

const LOCAL_SERVER_URL = 'http://localhost:4444';

const SERVER_HANDLER_STREAM_ECHO = (req, res) => req.pipe(res);

function startHTTPServer(options) {

const {handler, useBuffering = false, rate = undefined, port = 4444} = typeof options === 'function' ? {
Expand Down Expand Up @@ -2117,4 +2119,63 @@ describe('supports http with nodejs', function () {

assert.strictEqual(data, '/?foo');
});

describe('DNS', function() {
it('should support custom DNS lookup function', async function () {
server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);

const payload = 'test';

let isCalled = false;

const {data} = await axios.post(`http://fake-name.axios:4444`, payload,{
lookup: (hostname, opt, cb) => {
isCalled = true;
cb(null, '127.0.0.1', 4);
}
});

assert.ok(isCalled);

assert.strictEqual(data, payload);
});

it('should support custom DNS lookup function (async)', async function () {
server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);

const payload = 'test';

let isCalled = false;

const {data} = await axios.post(`http://fake-name.axios:4444`, payload,{
lookup: async (hostname, opt) => {
isCalled = true;
return ['127.0.0.1', 4];
}
});

assert.ok(isCalled);

assert.strictEqual(data, payload);
});

it('should support custom DNS lookup function that returns only IP address (async)', async function () {
server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);

const payload = 'test';

let isCalled = false;

const {data} = await axios.post(`http://fake-name.axios:4444`, payload,{
lookup: async (hostname, opt) => {
isCalled = true;
return '127.0.0.1';
}
});

assert.ok(isCalled);

assert.strictEqual(data, payload);
});
});
});