axios-axios/tests/unit/adapters/http.test.js
Kai Lee 947f7091d8
Fixes #10610 Deprecation Warning : url.parse() is deprecated in Node.… (#10625)
* Fixes #10610 Deprecation Warning : url.parse() is deprecated in Node.js v22 (via follow-redirects)

* Fixes #10610 Deprecation Warning : fixed again

* Apply suggestion from @cubic-dev-ai[bot]

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: tona jose <tona00jose@gmail.com>
Co-authored-by: Jay <jasonsaayman@gmail.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-04-02 09:02:58 +02:00

3724 lines
103 KiB
JavaScript

import { describe, it } from 'vitest';
import assert from 'assert';
import {
startHTTPServer,
stopHTTPServer,
SERVER_HANDLER_STREAM_ECHO,
handleFormData,
setTimeoutAsync,
generateReadable,
} from '../../setup/server.js';
import axios from '../../../index.js';
import AxiosError from '../../../lib/core/AxiosError.js';
import { __setProxy } from '../../../lib/adapters/http.js';
import http from 'http';
import https from 'https';
import net from 'net';
import stream from 'stream';
import zlib from 'zlib';
import fs from 'fs';
import os from 'os';
import path from 'path';
import devNull from 'dev-null';
import FormDataLegacy from 'form-data';
import { IncomingForm } from 'formidable';
import { FormData as FormDataPolyfill, Blob as BlobPolyfill } from 'formdata-node';
import express from 'express';
import multer from 'multer';
import getStream from 'get-stream';
import bodyParser from 'body-parser';
import { AbortController } from 'abortcontroller-polyfill/dist/cjs-ponyfill.js';
import { lookup } from 'dns';
const OPEN_WEB_PORT = 80;
const SERVER_PORT = 8020;
const PROXY_PORT = 8030;
const ALTERNATE_SERVER_PORT = 8040;
describe('supports http with nodejs', () => {
const adaptersTestsDir = path.join(process.cwd(), 'tests/unit/adapters');
const thisTestFilePath = path.join(adaptersTestsDir, 'http.test.js');
const FormDataSpecCompliant = typeof FormData !== 'undefined' ? FormData : FormDataPolyfill;
const BlobSpecCompliant = typeof Blob !== 'undefined' ? Blob : BlobPolyfill;
const isBlobSupported = typeof Blob !== 'undefined';
function toleranceRange(positive, negative) {
const p = 1 + positive / 100;
const n = 1 - negative / 100;
return (actualValue, value) => {
return actualValue > value ? actualValue <= value * p : actualValue >= value * n;
};
}
it('should support IPv4 literal strings', async () => {
const data = {
firstName: 'Fred',
lastName: 'Flintstone',
emailAddr: 'fred@example.com',
};
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
},
{ port: SERVER_PORT }
);
try {
const { data: responseData } = await axios.get(`http://127.0.0.1:${server.address().port}`);
assert.deepStrictEqual(responseData, data);
} finally {
await stopHTTPServer(server);
}
});
it('should support IPv6 literal strings', async () => {
var data = {
firstName: 'Fred',
lastName: 'Flintstone',
emailAddr: 'fred@example.com',
};
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
},
{ port: SERVER_PORT }
);
try {
const { data: responseData } = await axios.get(`http://[::1]:${server.address().port}`, {
proxy: false,
});
assert.deepStrictEqual(responseData, data);
} finally {
await stopHTTPServer(server);
}
});
it('should throw an error if the timeout property is not parsable as a number', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end();
}, 1000);
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}`, {
timeout: { strangeTimeout: 250 },
}),
(error) => {
assert.strictEqual(error.code, AxiosError.ERR_BAD_OPTION_VALUE);
assert.strictEqual(error.message, 'error trying to parse `config.timeout` to int');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should parse the timeout property', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end();
}, 1000);
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}`, {
timeout: '250',
}),
(error) => {
assert.strictEqual(error.code, 'ECONNABORTED');
assert.strictEqual(error.message, 'timeout of 250ms exceeded');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should respect the timeout property', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end();
}, 1000);
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}`, {
timeout: 250,
}),
(error) => {
assert.strictEqual(error.code, 'ECONNABORTED');
assert.strictEqual(error.message, 'timeout of 250ms exceeded');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should respect the timeoutErrorMessage property', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end();
}, 1000);
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}`, {
timeout: 250,
timeoutErrorMessage: 'oops, timeout',
}),
(error) => {
assert.strictEqual(error.code, 'ECONNABORTED');
assert.strictEqual(error.message, 'oops, timeout');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should allow passing JSON', async () => {
const data = {
firstName: 'Fred',
lastName: 'Flintstone',
emailAddr: 'fred@example.com',
};
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
},
{ port: SERVER_PORT }
);
try {
const { data: responseData } = await axios.get(`http://localhost:${server.address().port}`);
assert.deepStrictEqual(responseData, data);
} finally {
await stopHTTPServer(server);
}
});
it('should allow passing JSON with BOM', async () => {
const data = {
firstName: 'Fred',
lastName: 'Flintstone',
emailAddr: 'fred@example.com',
};
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'application/json');
const bomBuffer = Buffer.from([0xef, 0xbb, 0xbf]);
const jsonBuffer = Buffer.from(JSON.stringify(data));
res.end(Buffer.concat([bomBuffer, jsonBuffer]));
},
{ port: SERVER_PORT }
);
try {
const { data: responseData } = await axios.get(`http://localhost:${server.address().port}`);
assert.deepStrictEqual(responseData, data);
} finally {
await stopHTTPServer(server);
}
});
it('should redirect', async () => {
const expectedResponse = 'test response';
const server = await startHTTPServer(
(req, res) => {
if (req.url === '/one') {
res.setHeader('Location', '/two');
res.statusCode = 302;
res.end();
return;
}
res.end(expectedResponse);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/one`, {
maxRedirects: 1,
});
assert.strictEqual(response.data, expectedResponse);
assert.strictEqual(response.request.path, '/two');
} finally {
await stopHTTPServer(server);
}
});
it('should not redirect', async () => {
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Location', '/foo');
res.statusCode = 302;
res.end();
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/one`, {
maxRedirects: 0,
});
assert.strictEqual(response.status, 302);
assert.strictEqual(response.headers.location, '/foo');
} catch (error) {
assert.strictEqual(error.message, 'Request failed with status code 302');
assert.strictEqual(error.response.status, 302);
assert.strictEqual(error.response.headers.location, '/foo');
} finally {
await stopHTTPServer(server);
}
});
it('should support max redirects', async () => {
var i = 1;
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Location', `/${i}`);
res.statusCode = 302;
res.end();
i++;
},
{ port: SERVER_PORT }
);
try {
await axios.get(`http://localhost:${server.address().port}`, {
maxRedirects: 3,
});
} catch (error) {
assert.strictEqual(error.code, AxiosError.ERR_FR_TOO_MANY_REDIRECTS);
assert.strictEqual(error.message, 'Maximum number of redirects exceeded');
} finally {
await stopHTTPServer(server);
}
});
it('should support beforeRedirect', async () => {
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Location', '/foo');
res.statusCode = 302;
res.end();
},
{ port: SERVER_PORT }
);
try {
await axios.get(`http://localhost:${server.address().port}/one`, {
maxRedirects: 3,
beforeRedirect: (options, responseDetails) => {
if (options.path === '/foo' && responseDetails.headers.location === '/foo') {
throw new Error('Provided path is not allowed');
}
},
});
} catch (error) {
assert.strictEqual(error.message, 'Redirected request failed: Provided path is not allowed');
} finally {
await stopHTTPServer(server);
}
});
it('should support beforeRedirect and proxy with redirect', async () => {
let requestCount = 0;
let proxyUseCount = 0;
let totalRedirectCount = 5;
let configBeforeRedirectCount = 0;
const server = await startHTTPServer(
(req, res) => {
requestCount += 1;
if (requestCount <= totalRedirectCount) {
res.setHeader('Location', `http://localhost:${SERVER_PORT}`);
res.writeHead(302);
}
res.end();
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(req, res) => {
proxyUseCount += 1;
const targetUrl = new URL(req.url, `http://localhost:${server.address().port}`);
const opts = {
host: targetUrl.hostname,
port: targetUrl.port,
path: targetUrl.path,
method: req.method,
};
const request = http.get(opts, (response) => {
res.writeHead(response.statusCode, response.headers);
stream.pipeline(response, res, () => {});
});
request.on('error', (err) => {
console.warn('request error', err);
res.statusCode = 500;
res.end();
});
},
{ port: PROXY_PORT }
);
await axios.get(`http://localhost:${server.address().port}/`, {
proxy: {
host: 'localhost',
port: PROXY_PORT,
},
maxRedirects: totalRedirectCount,
beforeRedirect: (options) => {
configBeforeRedirectCount += 1;
},
});
assert.strictEqual(totalRedirectCount, configBeforeRedirectCount);
assert.strictEqual(totalRedirectCount + 1, proxyUseCount);
await stopHTTPServer(server);
await stopHTTPServer(proxy);
});
it('should wrap HTTP errors and keep stack', async () => {
const server = await startHTTPServer(
(req, res) => {
res.statusCode = 400;
res.end();
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
async function stackTraceTest() {
await axios.get(`http://localhost:${server.address().port}/`);
},
(error) => {
const matches = [...error.stack.matchAll(/stackTraceTest/g)];
assert.strictEqual(error.name, 'AxiosError');
assert.strictEqual(error.isAxiosError, true);
assert.strictEqual(error.code, AxiosError.ERR_BAD_REQUEST);
assert.strictEqual(error.message, 'Request failed with status code 400');
assert.strictEqual(matches.length, 1, error.stack);
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should wrap interceptor errors and keep stack', async () => {
const axiosInstance = axios.create();
axiosInstance.interceptors.request.use((res) => {
throw new Error('from request interceptor');
});
const server = await startHTTPServer(
(req, res) => {
res.end();
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
async function stackTraceTest() {
await axiosInstance.get(`http://localhost:${server.address().port}/one`);
},
(error) => {
const matches = [...error.stack.matchAll(/stackTraceTest/g)];
assert.strictEqual(error.name, 'Error');
assert.strictEqual(error.message, 'from request interceptor');
assert.strictEqual(matches.length, 1, error.stack);
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should preserve the HTTP verb on redirect', async () => {
const server = await startHTTPServer(
(req, res) => {
if (req.method.toLowerCase() !== 'head') {
res.statusCode = 400;
res.end();
return;
}
var parsed = new URL(req.url, 'http://localhost');
if (parsed.pathname === '/one') {
res.setHeader('Location', '/two');
res.statusCode = 302;
res.end();
} else {
res.end();
}
},
{ port: SERVER_PORT }
);
try {
const response = await axios.head(`http://localhost:${server.address().port}/one`);
assert.strictEqual(response.status, 200);
} finally {
await stopHTTPServer(server);
}
});
describe('compression', async () => {
it('should support transparent gunzip', async () => {
const data = {
firstName: 'Fred',
lastName: 'Flintstone',
emailAddr: 'fred@example.com',
};
const zipped = await new Promise((resolve, reject) => {
zlib.gzip(JSON.stringify(data), (error, compressed) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
});
});
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Encoding', 'gzip');
res.end(zipped);
},
{ port: SERVER_PORT }
);
try {
const { data: responseData } = await axios.get(
`http://localhost:${server.address().port}/`
);
assert.deepStrictEqual(responseData, data);
} finally {
await stopHTTPServer(server);
}
});
it('should support gunzip error handling', async () => {
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Encoding', 'gzip');
res.end('invalid response');
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(async () => {
await axios.get(`http://localhost:${server.address().port}/`);
});
} finally {
await stopHTTPServer(server);
}
});
it('should support disabling automatic decompression of response data', async () => {
const data = 'Test data';
const zipped = await new Promise((resolve, reject) => {
zlib.gzip(data, (error, compressed) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
});
});
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html;charset=utf-8');
res.setHeader('Content-Encoding', 'gzip');
res.end(zipped);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/`, {
decompress: false,
responseType: 'arraybuffer',
});
assert.strictEqual(response.data.toString('base64'), zipped.toString('base64'));
} finally {
await stopHTTPServer(server);
}
});
describe('algorithms', () => {
const responseBody = 'str';
const gzip = (value) =>
new Promise((resolve, reject) => {
zlib.gzip(value, (error, compressed) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
});
});
const deflate = (value) =>
new Promise((resolve, reject) => {
zlib.deflate(value, (error, compressed) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
});
});
const deflateRaw = (value) =>
new Promise((resolve, reject) => {
zlib.deflateRaw(value, (error, compressed) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
});
});
const brotliCompress = (value) =>
new Promise((resolve, reject) => {
zlib.brotliCompress(value, (error, compressed) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
});
});
for (const [typeName, zipped] of Object.entries({
gzip: gzip(responseBody),
GZIP: gzip(responseBody),
compress: gzip(responseBody),
deflate: deflate(responseBody),
'deflate-raw': deflateRaw(responseBody),
br: brotliCompress(responseBody),
})) {
const type = typeName.split('-')[0];
describe(`${typeName} decompression`, () => {
it('should support decompression', async () => {
const server = await startHTTPServer(
async (req, res) => {
res.setHeader('Content-Encoding', type);
res.end(await zipped);
},
{ port: SERVER_PORT }
);
try {
const { data } = await axios.get(`http://localhost:${server.address().port}`);
assert.strictEqual(data, responseBody);
} finally {
await stopHTTPServer(server);
}
});
it(`should not fail if response content-length header is missing (${type})`, async () => {
const server = await startHTTPServer(
async (req, res) => {
res.setHeader('Content-Encoding', type);
res.removeHeader('Content-Length');
res.end(await zipped);
},
{ port: SERVER_PORT }
);
try {
const { data } = await axios.get(`http://localhost:${server.address().port}`);
assert.strictEqual(data, responseBody);
} finally {
await stopHTTPServer(server);
}
});
it('should not fail with chunked responses (without Content-Length header)', async () => {
const server = await startHTTPServer(
async (req, res) => {
res.setHeader('Content-Encoding', type);
res.setHeader('Transfer-Encoding', 'chunked');
res.removeHeader('Content-Length');
res.write(await zipped);
res.end();
},
{ port: SERVER_PORT }
);
try {
const { data } = await axios.get(`http://localhost:${server.address().port}`);
assert.strictEqual(data, responseBody);
} finally {
await stopHTTPServer(server);
}
});
it('should not fail with an empty response without content-length header (Z_BUF_ERROR)', async () => {
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Encoding', type);
res.removeHeader('Content-Length');
res.end();
},
{ port: SERVER_PORT }
);
try {
const { data } = await axios.get(`http://localhost:${server.address().port}`);
assert.strictEqual(data, '');
} finally {
await stopHTTPServer(server);
}
});
it('should not fail with an empty response with content-length header (Z_BUF_ERROR)', async () => {
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Encoding', type);
res.end();
},
{ port: SERVER_PORT }
);
try {
await axios.get(`http://localhost:${server.address().port}`);
} finally {
await stopHTTPServer(server);
}
});
});
}
});
});
it('should support UTF8', async () => {
const str = Array(100000).join('ж');
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end(str);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/`);
assert.strictEqual(response.data, str);
} finally {
await stopHTTPServer(server);
}
});
it('should support basic auth', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end(req.headers.authorization);
},
{ port: SERVER_PORT }
);
try {
const user = 'foo';
const headers = { Authorization: 'Bearer 1234' };
const response = await axios.get(`http://${user}@localhost:${server.address().port}/`, {
headers,
});
const base64 = Buffer.from(`${user}:`, 'utf8').toString('base64');
assert.strictEqual(response.data, `Basic ${base64}`);
} finally {
await stopHTTPServer(server);
}
});
it('should support basic auth with a header', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end(req.headers.authorization);
},
{ port: SERVER_PORT }
);
try {
const auth = { username: 'foo', password: 'bar' };
const headers = { AuThOrIzAtIoN: 'Bearer 1234' }; // wonky casing to ensure caseless comparison
const response = await axios.get(`http://localhost:${server.address().port}/`, {
auth,
headers,
});
const base64 = Buffer.from('foo:bar', 'utf8').toString('base64');
assert.strictEqual(response.data, `Basic ${base64}`);
} finally {
await stopHTTPServer(server);
}
});
it('should provides a default User-Agent header', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end(req.headers['user-agent']);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/`);
assert.ok(
/^axios\/[\d.]+[-]?[a-z]*[.]?[\d]+$/.test(response.data),
`User-Agent header does not match: ${response.data}`
);
} finally {
await stopHTTPServer(server);
}
});
it('should allow the User-Agent header to be overridden', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end(req.headers['user-agent']);
},
{ port: SERVER_PORT }
);
try {
const headers = { 'UsEr-AgEnT': 'foo bar' }; // wonky casing to ensure caseless comparison
const response = await axios.get(`http://localhost:${server.address().port}/`, { headers });
assert.strictEqual(response.data, 'foo bar');
} finally {
await stopHTTPServer(server);
}
});
it('should allow the Content-Length header to be overridden', async () => {
const server = await startHTTPServer(
(req, res) => {
assert.strictEqual(req.headers['content-length'], '42');
res.end();
},
{ port: SERVER_PORT }
);
try {
const headers = { 'CoNtEnT-lEnGtH': '42' }; // wonky casing to ensure caseless comparison
await axios.post(`http://localhost:${server.address().port}/`, 'foo', { headers });
} finally {
await stopHTTPServer(server);
}
});
it('should support max content length', async () => {
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end(Array(5000).join('#'));
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}/`, {
maxContentLength: 2000,
maxRedirects: 0,
}),
/maxContentLength size of 2000 exceeded/
);
} finally {
await stopHTTPServer(server);
}
});
it('should support max content length for redirected', async () => {
const str = Array(100000).join('ж');
const server = await startHTTPServer(
(req, res) => {
const parsed = new URL(req.url, 'http://localhost');
if (parsed.pathname === '/two') {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end(str);
return;
}
res.setHeader('Location', '/two');
res.statusCode = 302;
res.end();
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}/one`, {
maxContentLength: 2000,
}),
(error) => {
assert.strictEqual(error.message, 'maxContentLength size of 2000 exceeded');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should support max body length', async () => {
const data = Array(100000).join('ж');
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end();
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.post(
`http://localhost:${server.address().port}/`,
{
data,
},
{
maxBodyLength: 2000,
}
),
(error) => {
assert.strictEqual(error.message, 'Request body larger than maxBodyLength limit');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should properly support default max body length (follow-redirects as well)', async () => {
// Taken from follow-redirects defaults.
const followRedirectsMaxBodyDefaults = 10 * 1024 * 1024;
const data = Array(2 * followRedirectsMaxBodyDefaults).join('ж');
const server = await startHTTPServer(
(req, res) => {
// Consume the req stream before responding to avoid ECONNRESET.
req.on('data', () => {});
req.on('end', () => {
res.end('OK');
});
},
{ port: SERVER_PORT }
);
try {
const response = await axios.post(`http://localhost:${server.address().port}/`, {
data,
});
assert.strictEqual(response.data, 'OK', 'should handle response');
} finally {
await stopHTTPServer(server);
}
});
it('should display error while parsing params', async () => {
const server = await startHTTPServer(() => {}, { port: SERVER_PORT });
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}/`, {
params: {
errorParam: new Date(undefined),
},
}),
(error) => {
assert.deepStrictEqual(error.exists, true);
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should support sockets', async () => {
let socketName = path.join(
os.tmpdir(),
`axios-test-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.sock`
);
if (process.platform === 'win32') {
socketName = '\\\\.\\pipe\\libuv-test';
}
let server;
try {
server = await new Promise((resolve, reject) => {
const socketServer = net
.createServer((socket) => {
socket.on('data', () => {
socket.end('HTTP/1.1 200 OK\r\n\r\n');
});
})
.listen(socketName, () => resolve(socketServer));
socketServer.on('error', reject);
});
} catch (error) {
if (error && error.code === 'EPERM') {
return;
}
throw error;
}
try {
const response = await axios({
socketPath: socketName,
url: 'http://localhost:4444/socket',
});
assert.strictEqual(response.status, 200);
assert.strictEqual(response.statusText, 'OK');
} finally {
await new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
});
describe('streams', () => {
it('should support streams', async () => {
const server = await startHTTPServer(
(req, res) => {
req.pipe(res);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.post(
`http://localhost:${server.address().port}/`,
fs.createReadStream(thisTestFilePath),
{
responseType: 'stream',
}
);
const responseText = await new Promise((resolve, reject) => {
const chunks = [];
response.data.on('data', (chunk) => {
chunks.push(chunk);
});
response.data.on('end', () => {
resolve(Buffer.concat(chunks).toString('utf8'));
});
response.data.on('error', reject);
});
assert.strictEqual(responseText, fs.readFileSync(thisTestFilePath, 'utf8'));
} finally {
await stopHTTPServer(server);
}
});
it('should pass errors for a failed stream', async () => {
const server = await startHTTPServer(() => {}, { port: SERVER_PORT });
const notExistPath = path.join(adaptersTestsDir, 'does_not_exist');
try {
await assert.rejects(
axios.post(
`http://localhost:${server.address().port}/`,
fs.createReadStream(notExistPath)
),
(error) => {
assert.strictEqual(
error.message,
`ENOENT: no such file or directory, open '${notExistPath}'`
);
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should destroy the response stream with an error on request stream destroying', async () => {
const server = await startHTTPServer();
const requestStream = generateReadable();
setTimeout(() => {
requestStream.destroy();
}, 1000);
const { data } = await axios.post(
`http://localhost:${server.address().port}/`,
requestStream,
{
responseType: 'stream',
}
);
let streamError;
data.on('error', (error) => {
streamError = error;
});
try {
await new Promise((resolve, reject) => {
stream.pipeline(data, devNull(), (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
assert.fail('stream was not aborted');
} catch (error) {
// Expected: the request stream is destroyed before completion.
} finally {
assert.strictEqual(streamError && streamError.code, 'ERR_CANCELED');
await stopHTTPServer(server);
}
});
});
it('should support buffers', async () => {
const buf = Buffer.alloc(1024, 'x'); // Unsafe buffer < Buffer.poolSize (8192 bytes)
const server = await startHTTPServer(
(req, res) => {
assert.strictEqual(req.headers['content-length'], buf.length.toString());
req.pipe(res);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.post(`http://localhost:${server.address().port}/`, buf, {
responseType: 'stream',
});
const responseText = await new Promise((resolve, reject) => {
const chunks = [];
response.data.on('data', (chunk) => {
chunks.push(chunk);
});
response.data.on('end', () => {
resolve(Buffer.concat(chunks).toString('utf8'));
});
response.data.on('error', reject);
});
assert.strictEqual(responseText, buf.toString());
} finally {
await stopHTTPServer(server);
}
});
it('should support HTTP proxies', async () => {
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('12345');
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(request, response) => {
const parsed = new URL(request.url);
const opts = {
host: parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`,
};
http.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(body + '6789');
});
});
},
{ port: PROXY_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/`, {
proxy: {
host: 'localhost',
port: proxy.address().port,
},
});
assert.strictEqual(Number(response.data), 123456789, 'should pass through proxy');
} finally {
await stopHTTPServer(server);
await stopHTTPServer(proxy);
}
});
it('should support HTTPS proxies', async () => {
const tlsOptions = {
key: fs.readFileSync(path.join(adaptersTestsDir, 'key.pem')),
cert: fs.readFileSync(path.join(adaptersTestsDir, 'cert.pem')),
};
const closeServer = (server) =>
new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
const server = await new Promise((resolve, reject) => {
const httpsServer = https
.createServer(
tlsOptions,
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('12345');
},
{ port: SERVER_PORT }
)
.listen(SERVER_PORT, () => resolve(httpsServer));
httpsServer.on('error', reject);
});
const proxy = await new Promise((resolve, reject) => {
const httpsProxy = https
.createServer(
tlsOptions,
(request, response) => {
const targetUrl = new URL(request.url);
const opts = {
host: targetUrl.hostname,
port: targetUrl.port,
path: `${targetUrl.pathname}${targetUrl.search}`,
protocol: targetUrl.protocol,
rejectUnauthorized: false,
};
const proxyRequest = https.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(body + '6789');
});
});
proxyRequest.on('error', () => {
response.statusCode = 502;
response.end();
});
},
{ port: PROXY_PORT }
)
.listen(PROXY_PORT, () => resolve(httpsProxy));
httpsProxy.on('error', reject);
});
try {
const response = await axios.get(`https://localhost:${server.address().port}/`, {
proxy: {
host: 'localhost',
port: proxy.address().port,
protocol: 'https:',
},
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
});
assert.strictEqual(Number(response.data), 123456789, 'should pass through proxy');
} finally {
await Promise.all([closeServer(server), closeServer(proxy)]);
}
});
it('should not pass through disabled proxy', async () => {
const originalHttpProxy = process.env.http_proxy;
process.env.http_proxy = 'http://does-not-exists.example.com:4242/';
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('123456789');
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/`, {
proxy: false,
});
assert.strictEqual(Number(response.data), 123456789, 'should not pass through proxy');
} finally {
await stopHTTPServer(server);
if (originalHttpProxy === undefined) {
delete process.env.http_proxy;
} else {
process.env.http_proxy = originalHttpProxy;
}
}
});
it('should support proxy set via env var', async () => {
const originalHttpProxy = process.env.http_proxy;
const originalHTTPProxy = process.env.HTTP_PROXY;
const originalNoProxy = process.env.no_proxy;
const originalNOProxy = process.env.NO_PROXY;
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('4567');
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(request, response) => {
const parsed = new URL(request.url);
const opts = {
host: parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`,
};
http.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(body + '1234');
});
});
},
{ port: PROXY_PORT }
);
const proxyUrl = `http://localhost:${proxy.address().port}/`;
process.env.http_proxy = proxyUrl;
process.env.HTTP_PROXY = proxyUrl;
process.env.no_proxy = '';
process.env.NO_PROXY = '';
try {
const response = await axios.get(`http://localhost:${server.address().port}/`);
assert.strictEqual(
String(response.data),
'45671234',
'should use proxy set by process.env.http_proxy'
);
} finally {
await stopHTTPServer(server);
await stopHTTPServer(proxy);
if (originalHttpProxy === undefined) {
delete process.env.http_proxy;
} else {
process.env.http_proxy = originalHttpProxy;
}
if (originalHTTPProxy === undefined) {
delete process.env.HTTP_PROXY;
} else {
process.env.HTTP_PROXY = originalHTTPProxy;
}
if (originalNoProxy === undefined) {
delete process.env.no_proxy;
} else {
process.env.no_proxy = originalNoProxy;
}
if (originalNOProxy === undefined) {
delete process.env.NO_PROXY;
} else {
process.env.NO_PROXY = originalNOProxy;
}
}
});
it('should support HTTPS proxy set via env var', async () => {
const originalHttpsProxy = process.env.https_proxy;
const originalHTTPSProxy = process.env.HTTPS_PROXY;
const originalNoProxy = process.env.no_proxy;
const originalNOProxy = process.env.NO_PROXY;
const tlsOptions = {
key: fs.readFileSync(path.join(adaptersTestsDir, 'key.pem')),
cert: fs.readFileSync(path.join(adaptersTestsDir, 'cert.pem')),
};
const closeServer = (server) =>
new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
const server = await new Promise((resolve, reject) => {
const httpsServer = https
.createServer(
tlsOptions,
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('12345');
},
{ port: SERVER_PORT }
)
.listen(SERVER_PORT, () => resolve(httpsServer));
httpsServer.on('error', reject);
});
const proxy = await new Promise((resolve, reject) => {
const httpsProxy = https
.createServer(
tlsOptions,
(request, response) => {
const targetUrl = new URL(request.url);
const opts = {
host: targetUrl.hostname,
port: targetUrl.port,
path: `${targetUrl.pathname}${targetUrl.search}`,
protocol: targetUrl.protocol,
rejectUnauthorized: false,
};
const proxyRequest = https.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(body + '6789');
});
});
proxyRequest.on('error', () => {
response.statusCode = 502;
response.end();
});
},
{ port: PROXY_PORT }
)
.listen(PROXY_PORT, () => resolve(httpsProxy));
httpsProxy.on('error', reject);
});
const proxyUrl = `https://localhost:${proxy.address().port}/`;
process.env.https_proxy = proxyUrl;
process.env.HTTPS_PROXY = proxyUrl;
process.env.no_proxy = '';
process.env.NO_PROXY = '';
try {
const response = await axios.get(`https://localhost:${server.address().port}/`, {
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
});
assert.equal(response.data, '123456789', 'should pass through proxy');
} finally {
await Promise.all([closeServer(server), closeServer(proxy)]);
if (originalHttpsProxy === undefined) {
delete process.env.https_proxy;
} else {
process.env.https_proxy = originalHttpsProxy;
}
if (originalHTTPSProxy === undefined) {
delete process.env.HTTPS_PROXY;
} else {
process.env.HTTPS_PROXY = originalHTTPSProxy;
}
if (originalNoProxy === undefined) {
delete process.env.no_proxy;
} else {
process.env.no_proxy = originalNoProxy;
}
if (originalNOProxy === undefined) {
delete process.env.NO_PROXY;
} else {
process.env.NO_PROXY = originalNOProxy;
}
}
});
it('should re-evaluate proxy on redirect when proxy set via env var', async () => {
const originalHttpProxy = process.env.http_proxy;
const originalHTTPProxy = process.env.HTTP_PROXY;
const originalNoProxy = process.env.no_proxy;
const originalNOProxy = process.env.NO_PROXY;
let proxyUseCount = 0;
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Location', `http://localhost:${proxy.address().port}/redirected`);
res.statusCode = 302;
res.end();
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(request, response) => {
const parsed = new URL(request.url, 'http://localhost');
if (parsed.pathname === '/redirected') {
response.statusCode = 200;
response.end();
return;
}
proxyUseCount += 1;
const opts = {
host: parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`,
protocol: parsed.protocol,
};
http.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.setHeader('Location', res.headers.location);
response.end(body);
});
});
},
{ port: PROXY_PORT }
);
const proxyUrl = `http://localhost:${proxy.address().port}`;
process.env.http_proxy = proxyUrl;
process.env.HTTP_PROXY = proxyUrl;
process.env.no_proxy = `localhost:${proxy.address().port}`;
process.env.NO_PROXY = `localhost:${proxy.address().port}`;
try {
const response = await axios.get(`http://localhost:${server.address().port}/`);
assert.equal(response.status, 200);
assert.equal(proxyUseCount, 1);
} finally {
await stopHTTPServer(server);
await stopHTTPServer(proxy);
if (originalHttpProxy === undefined) {
delete process.env.http_proxy;
} else {
process.env.http_proxy = originalHttpProxy;
}
if (originalHTTPProxy === undefined) {
delete process.env.HTTP_PROXY;
} else {
process.env.HTTP_PROXY = originalHTTPProxy;
}
if (originalNoProxy === undefined) {
delete process.env.no_proxy;
} else {
process.env.no_proxy = originalNoProxy;
}
if (originalNOProxy === undefined) {
delete process.env.NO_PROXY;
} else {
process.env.NO_PROXY = originalNOProxy;
}
}
});
it('should not use proxy for domains in no_proxy', async () => {
const originalHttpProxy = process.env.http_proxy;
const originalHTTPProxy = process.env.HTTP_PROXY;
const originalNoProxy = process.env.no_proxy;
const originalNOProxy = process.env.NO_PROXY;
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('4567');
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(request, response) => {
const parsed = new URL(request.url);
const opts = {
host: parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`,
};
http.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(body + '1234');
});
});
},
{ port: PROXY_PORT }
);
const noProxyValue = 'foo.com, localhost,bar.net , , quix.co';
const proxyUrl = `http://localhost:${proxy.address().port}/`;
process.env.http_proxy = proxyUrl;
process.env.HTTP_PROXY = proxyUrl;
process.env.no_proxy = noProxyValue;
process.env.NO_PROXY = noProxyValue;
try {
const response = await axios.get(`http://localhost:${server.address().port}/`);
assert.equal(response.data, '4567', 'should not use proxy for domains in no_proxy');
} finally {
await stopHTTPServer(server);
await stopHTTPServer(proxy);
if (originalHttpProxy === undefined) {
delete process.env.http_proxy;
} else {
process.env.http_proxy = originalHttpProxy;
}
if (originalHTTPProxy === undefined) {
delete process.env.HTTP_PROXY;
} else {
process.env.HTTP_PROXY = originalHTTPProxy;
}
if (originalNoProxy === undefined) {
delete process.env.no_proxy;
} else {
process.env.no_proxy = originalNoProxy;
}
if (originalNOProxy === undefined) {
delete process.env.NO_PROXY;
} else {
process.env.NO_PROXY = originalNOProxy;
}
}
});
it('should use proxy for domains not in no_proxy', async () => {
const originalHttpProxy = process.env.http_proxy;
const originalHTTPProxy = process.env.HTTP_PROXY;
const originalNoProxy = process.env.no_proxy;
const originalNOProxy = process.env.NO_PROXY;
const server = await startHTTPServer(
(req, res) => {
res.setHeader('Content-Type', 'text/html; charset=UTF-8');
res.end('4567');
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(request, response) => {
const parsed = new URL(request.url);
const opts = {
host: parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`,
};
http.get(opts, (res) => {
let body = '';
res.on('data', (data) => {
body += data;
});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(body + '1234');
});
});
},
{ port: PROXY_PORT }
);
const noProxyValue = 'foo.com, ,bar.net , quix.co';
const proxyUrl = `http://localhost:${proxy.address().port}/`;
process.env.http_proxy = proxyUrl;
process.env.HTTP_PROXY = proxyUrl;
process.env.no_proxy = noProxyValue;
process.env.NO_PROXY = noProxyValue;
try {
const response = await axios.get(`http://localhost:${server.address().port}/`);
assert.equal(response.data, '45671234', 'should use proxy for domains not in no_proxy');
} finally {
await stopHTTPServer(server);
await stopHTTPServer(proxy);
if (originalHttpProxy === undefined) {
delete process.env.http_proxy;
} else {
process.env.http_proxy = originalHttpProxy;
}
if (originalHTTPProxy === undefined) {
delete process.env.HTTP_PROXY;
} else {
process.env.HTTP_PROXY = originalHTTPProxy;
}
if (originalNoProxy === undefined) {
delete process.env.no_proxy;
} else {
process.env.no_proxy = originalNoProxy;
}
if (originalNOProxy === undefined) {
delete process.env.NO_PROXY;
} else {
process.env.NO_PROXY = originalNOProxy;
}
}
});
it('should support HTTP proxy auth', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end();
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(request, response) => {
const parsed = new URL(request.url);
const opts = {
host: parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`,
};
const proxyAuth = request.headers['proxy-authorization'];
http.get(opts, (res) => {
res.on('data', () => {});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(proxyAuth);
});
});
},
{ port: PROXY_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}/`, {
proxy: {
host: 'localhost',
port: proxy.address().port,
auth: {
username: 'user',
password: 'pass',
},
},
});
const base64 = Buffer.from('user:pass', 'utf8').toString('base64');
assert.equal(response.data, `Basic ${base64}`, 'should authenticate to the proxy');
} finally {
await stopHTTPServer(server);
await stopHTTPServer(proxy);
}
});
it('should support proxy auth from env', async () => {
const originalHttpProxy = process.env.http_proxy;
const originalHTTPProxy = process.env.HTTP_PROXY;
const originalNoProxy = process.env.no_proxy;
const originalNOProxy = process.env.NO_PROXY;
const server = await startHTTPServer(
(req, res) => {
res.end();
},
{ port: SERVER_PORT }
);
const proxy = await startHTTPServer(
(request, response) => {
const parsed = new URL(request.url);
const opts = {
host: parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`,
};
const proxyAuth = request.headers['proxy-authorization'];
http.get(opts, (res) => {
res.on('data', () => {});
res.on('end', () => {
response.setHeader('Content-Type', 'text/html; charset=UTF-8');
response.end(proxyAuth);
});
});
},
{ port: PROXY_PORT }
);
const proxyUrl = `http://user:pass@localhost:${proxy.address().port}/`;
process.env.http_proxy = proxyUrl;
process.env.HTTP_PROXY = proxyUrl;
process.env.no_proxy = '';
process.env.NO_PROXY = '';
try {
const response = await axios.get(`http://localhost:${server.address().port}/`);
const base64 = Buffer.from('user:pass', 'utf8').toString('base64');
assert.equal(
response.data,
`Basic ${base64}`,
'should authenticate to the proxy set by process.env.http_proxy'
);
} finally {
await stopHTTPServer(server);
await stopHTTPServer(proxy);
if (originalHttpProxy === undefined) {
delete process.env.http_proxy;
} else {
process.env.http_proxy = originalHttpProxy;
}
if (originalHTTPProxy === undefined) {
delete process.env.HTTP_PROXY;
} else {
process.env.HTTP_PROXY = originalHTTPProxy;
}
if (originalNoProxy === undefined) {
delete process.env.no_proxy;
} else {
process.env.no_proxy = originalNoProxy;
}
if (originalNOProxy === undefined) {
delete process.env.NO_PROXY;
} else {
process.env.NO_PROXY = originalNOProxy;
}
}
});
describe('when invalid proxy options are provided', () => {
it('should throw error', async () => {
const proxy = {
protocol: 'http:',
host: 'hostname.abc.xyz',
port: PROXY_PORT,
auth: {
username: '',
password: '',
},
};
await assert.rejects(axios.get('https://test-domain.abc', { proxy }), (error) => {
assert.strictEqual(error.message, 'Invalid proxy authorization');
assert.strictEqual(error.code, 'ERR_BAD_OPTION');
assert.deepStrictEqual(error.config.proxy, proxy);
return true;
});
});
});
describe('different options for direct proxy configuration (without env variables)', () => {
const destination = 'www.example.com';
const testCases = [
{
description: 'hostname and trailing colon in protocol',
proxyConfig: { hostname: '127.0.0.1', protocol: 'http:', port: OPEN_WEB_PORT },
expectedOptions: {
host: '127.0.0.1',
protocol: 'http:',
port: OPEN_WEB_PORT,
path: destination,
},
},
{
description: 'hostname and no trailing colon in protocol',
proxyConfig: { hostname: '127.0.0.1', protocol: 'http', port: OPEN_WEB_PORT },
expectedOptions: {
host: '127.0.0.1',
protocol: 'http:',
port: OPEN_WEB_PORT,
path: destination,
},
},
{
description: 'both hostname and host -> hostname takes precedence',
proxyConfig: {
hostname: '127.0.0.1',
host: '0.0.0.0',
protocol: 'http',
port: OPEN_WEB_PORT,
},
expectedOptions: {
host: '127.0.0.1',
protocol: 'http:',
port: OPEN_WEB_PORT,
path: destination,
},
},
{
description: 'only host and https protocol',
proxyConfig: { host: '0.0.0.0', protocol: 'https', port: OPEN_WEB_PORT },
expectedOptions: {
host: '0.0.0.0',
protocol: 'https:',
port: OPEN_WEB_PORT,
path: destination,
},
},
];
for (const test of testCases) {
it(test.description, () => {
const options = { headers: {}, beforeRedirects: {} };
__setProxy(options, test.proxyConfig, destination);
for (const [key, expected] of Object.entries(test.expectedOptions)) {
assert.strictEqual(options[key], expected);
}
});
}
});
it('should support cancel', async () => {
const source = axios.CancelToken.source();
const server = await startHTTPServer(
(req, res) => {
// Call cancel() when the request has been sent but no response received.
source.cancel('Operation has been canceled.');
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
async function stackTraceTest() {
await axios.get(`http://localhost:${server.address().port}/`, {
cancelToken: source.token,
});
},
(thrown) => {
assert.ok(
thrown instanceof axios.Cancel,
'Promise must be rejected with a CanceledError object'
);
assert.equal(thrown.message, 'Operation has been canceled.');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should combine baseURL and url', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end();
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get('/foo', {
baseURL: `http://localhost:${server.address().port}/`,
});
assert.equal(response.config.baseURL, `http://localhost:${server.address().port}/`);
assert.equal(response.config.url, '/foo');
} finally {
await stopHTTPServer(server);
}
});
it('should support HTTP protocol', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end();
}, 1000);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.get(`http://localhost:${server.address().port}`);
assert.equal(response.request.agent.protocol, 'http:');
} finally {
await stopHTTPServer(server);
}
});
it('should support HTTPS protocol', async () => {
const tlsOptions = {
key: fs.readFileSync(path.join(adaptersTestsDir, 'key.pem')),
cert: fs.readFileSync(path.join(adaptersTestsDir, 'cert.pem')),
};
const server = await new Promise((resolve, reject) => {
const httpsServer = https
.createServer(
tlsOptions,
(req, res) => {
setTimeout(() => {
res.end();
}, 1000);
},
{ port: SERVER_PORT }
)
.listen(SERVER_PORT, () => resolve(httpsServer));
httpsServer.on('error', reject);
});
try {
const response = await axios.get(`https://localhost:${server.address().port}`, {
httpsAgent: new https.Agent({
rejectUnauthorized: false,
}),
});
assert.equal(response.request.agent.protocol, 'https:');
} finally {
await new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
});
it('should return malformed URL', async () => {
await assert.rejects(axios.get('tel:484-695-3408'), (error) => {
assert.equal(error.message, 'Unsupported protocol tel:');
return true;
});
});
it('should return unsupported protocol', async () => {
await assert.rejects(axios.get('ftp:google.com'), (error) => {
assert.equal(error.message, 'Unsupported protocol ftp:');
return true;
});
});
it('should supply a user-agent if one is not specified', async () => {
const server = await startHTTPServer(
(req, res) => {
assert.equal(req.headers['user-agent'], `axios/${axios.VERSION}`);
res.end();
},
{ port: SERVER_PORT }
);
try {
await axios.get(`http://localhost:${server.address().port}/`);
} finally {
await stopHTTPServer(server);
}
});
it('should omit a user-agent if one is explicitly disclaimed', async () => {
const server = await startHTTPServer(
(req, res) => {
assert.equal('user-agent' in req.headers, false);
assert.equal('User-Agent' in req.headers, false);
res.end();
},
{ port: SERVER_PORT }
);
try {
await axios.get(`http://localhost:${server.address().port}/`, {
headers: {
'User-Agent': null,
},
});
} finally {
await stopHTTPServer(server);
}
});
it('should throw an error if http server that aborts a chunked request', async () => {
const server = await startHTTPServer(
(req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('chunk 1');
setTimeout(() => {
res.write('chunk 2');
}, 100);
setTimeout(() => {
res.destroy();
}, 200);
},
{ port: SERVER_PORT }
);
try {
await assert.rejects(
axios.get(`http://localhost:${server.address().port}/aborted`, {
timeout: 500,
}),
(error) => {
assert.strictEqual(error.code, 'ERR_BAD_RESPONSE');
assert.strictEqual(error.message, 'stream has been aborted');
return true;
}
);
} finally {
await stopHTTPServer(server);
}
});
it('should able to cancel multiple requests with CancelToken', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end('ok');
},
{ port: SERVER_PORT }
);
try {
const source = axios.CancelToken.source();
const canceledStack = [];
const requests = [1, 2, 3, 4, 5].map(async (id) => {
try {
await axios.get('/foo/bar', {
baseURL: `http://localhost:${server.address().port}`,
cancelToken: source.token,
});
} catch (error) {
if (!axios.isCancel(error)) {
throw error;
}
canceledStack.push(id);
}
});
source.cancel('Aborted by user');
await Promise.all(requests);
assert.deepStrictEqual(canceledStack.sort(), [1, 2, 3, 4, 5]);
} finally {
await stopHTTPServer(server);
}
});
describe('FormData', () => {
describe('form-data instance (https://www.npmjs.com/package/form-data)', () => {
it('should allow passing FormData', async () => {
const form = new FormDataLegacy();
const file1 = Buffer.from('foo', 'utf8');
const image = path.resolve(adaptersTestsDir, './axios.png');
const fileStream = fs.createReadStream(image);
const stat = fs.statSync(image);
form.append('foo', 'bar');
form.append('file1', file1, {
filename: 'bar.jpg',
filepath: 'temp/bar.jpg',
contentType: 'image/jpeg',
});
form.append('fileStream', fileStream);
const server = await startHTTPServer(
(req, res) => {
const receivedForm = new IncomingForm();
assert.ok(req.rawHeaders.some((header) => header.toLowerCase() === 'content-length'));
receivedForm.parse(req, (error, fields, files) => {
if (error) {
res.statusCode = 500;
res.end(error.message);
return;
}
res.end(
JSON.stringify({
fields,
files,
})
);
});
},
{ port: SERVER_PORT }
);
try {
const response = await axios.post(`http://localhost:${server.address().port}/`, form, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
assert.deepStrictEqual(response.data.fields, { foo: ['bar'] });
assert.strictEqual(response.data.files.file1[0].mimetype, 'image/jpeg');
assert.strictEqual(response.data.files.file1[0].originalFilename, 'temp/bar.jpg');
assert.strictEqual(response.data.files.file1[0].size, 3);
assert.strictEqual(response.data.files.fileStream[0].mimetype, 'image/png');
assert.strictEqual(response.data.files.fileStream[0].originalFilename, 'axios.png');
assert.strictEqual(response.data.files.fileStream[0].size, stat.size);
} finally {
await stopHTTPServer(server);
}
});
});
describe('SpecCompliant FormData', () => {
it('should allow passing FormData', async () => {
const server = await startHTTPServer(
async (req, res) => {
const { fields, files } = await handleFormData(req);
res.end(
JSON.stringify({
fields,
files,
})
);
},
{ port: SERVER_PORT }
);
try {
const form = new FormDataSpecCompliant();
const blobContent = 'blob-content';
const blob = new BlobSpecCompliant([blobContent], { type: 'image/jpeg' });
form.append('foo1', 'bar1');
form.append('foo2', 'bar2');
form.append('file1', blob);
const { data } = await axios.post(`http://localhost:${server.address().port}`, form, {
maxRedirects: 0,
});
assert.deepStrictEqual(data.fields, { foo1: ['bar1'], foo2: ['bar2'] });
assert.deepStrictEqual(typeof data.files.file1[0], 'object');
const { size, mimetype, originalFilename } = data.files.file1[0];
assert.deepStrictEqual(
{ size, mimetype, originalFilename },
{
mimetype: 'image/jpeg',
originalFilename: 'blob',
size: Buffer.from(blobContent).byteLength,
}
);
} finally {
await stopHTTPServer(server);
}
});
});
});
describe('toFormData helper', () => {
it('should properly serialize nested objects for parsing with multer.js (express.js)', async () => {
const app = express();
const obj = {
arr1: ['1', '2', '3'],
arr2: ['1', ['2'], '3'],
obj: { x: '1', y: { z: '1' } },
users: [
{ name: 'Peter', surname: 'griffin' },
{ name: 'Thomas', surname: 'Anderson' },
],
};
app.post('/', multer().none(), (req, res) => {
res.send(JSON.stringify(req.body));
});
const server = await new Promise(
(resolve, reject) => {
const expressServer = app.listen(0, () => resolve(expressServer));
expressServer.on('error', reject);
},
{ port: SERVER_PORT }
);
try {
await Promise.all(
[null, false, true].map((mode) =>
axios
.postForm(`http://localhost:${server.address().port}/`, obj, {
formSerializer: { indexes: mode },
})
.then((response) => {
assert.deepStrictEqual(response.data, obj, `Index mode ${mode}`);
})
)
);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
});
it('should only match explicit routes for express 5 form handlers', async () => {
const app = express();
app.post('/', multer().none(), (req, res) => {
res.status(200).send(JSON.stringify({ route: 'root', body: req.body }));
});
app.post('/unexpected', multer().none(), (req, res) => {
res.status(418).send('wrong-route');
});
const server = await new Promise(
(resolve, reject) => {
const expressServer = app.listen(0, () => resolve(expressServer));
expressServer.on('error', reject);
},
{ port: SERVER_PORT }
);
const rootUrl = `http://localhost:${server.address().port}`;
try {
const rootResponse = await axios.postForm(rootUrl, { foo: 'bar' });
assert.strictEqual(rootResponse.status, 200);
assert.deepStrictEqual(rootResponse.data, { route: 'root', body: { foo: 'bar' } });
await assert.rejects(
() => axios.postForm(`${rootUrl}/unexpected`, { foo: 'bar' }),
(error) => {
assert.strictEqual(error.response.status, 418);
assert.strictEqual(error.response.data, 'wrong-route');
return true;
}
);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
});
});
describe('Blob', () => {
it('should support Blob', async () => {
const server = await startHTTPServer(
async (req, res) => {
res.end(await getStream(req));
},
{ port: SERVER_PORT }
);
try {
const blobContent = 'blob-content';
const blob = new BlobSpecCompliant([blobContent], { type: 'image/jpeg' });
const { data } = await axios.post(`http://localhost:${server.address().port}`, blob, {
maxRedirects: 0,
});
assert.deepStrictEqual(data, blobContent);
} finally {
await stopHTTPServer(server);
}
});
});
describe('URLEncoded Form', () => {
it('should post object data as url-encoded form regardless of content-type header casing', async () => {
const app = express();
const obj = {
arr1: ['1', '2', '3'],
arr2: ['1', ['2'], '3'],
obj: { x: '1', y: { z: '1' } },
users: [
{ name: 'Peter', surname: 'griffin' },
{ name: 'Thomas', surname: 'Anderson' },
],
};
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/', (req, res) => {
res.send(JSON.stringify(req.body));
});
const server = await new Promise(
(resolve, reject) => {
const expressServer = app.listen(0, () => resolve(expressServer));
expressServer.on('error', reject);
},
{ port: SERVER_PORT }
);
try {
for (const headerName of ['content-type', 'Content-Type']) {
const response = await axios.post(`http://localhost:${server.address().port}/`, obj, {
headers: {
[headerName]: 'application/x-www-form-urlencoded',
},
});
assert.deepStrictEqual(response.data, obj);
}
} finally {
await new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
});
it('should respect formSerializer config', async () => {
const obj = {
arr1: ['1', '2', '3'],
arr2: ['1', ['2'], '3'],
};
const form = new URLSearchParams();
form.append('arr1[0]', '1');
form.append('arr1[1]', '2');
form.append('arr1[2]', '3');
form.append('arr2[0]', '1');
form.append('arr2[1][0]', '2');
form.append('arr2[2]', '3');
const server = await startHTTPServer(
(req, res) => {
req.pipe(res);
},
{ port: SERVER_PORT }
);
try {
const response = await axios.post(`http://localhost:${server.address().port}/`, obj, {
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
formSerializer: {
indexes: true,
},
});
assert.strictEqual(response.data, form.toString());
} finally {
await stopHTTPServer(server);
}
});
it('should parse nested urlencoded payloads and ignore mismatched content-type', async () => {
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/', (req, res) => {
const parserRanBeforeHandler = Boolean(req.body && Object.keys(req.body).length);
res.send(
JSON.stringify({
parserRanBeforeHandler,
body: req.body,
})
);
});
const server = await new Promise(
(resolve, reject) => {
const expressServer = app.listen(0, () => resolve(expressServer));
expressServer.on('error', reject);
},
{ port: SERVER_PORT }
);
const rootUrl = `http://localhost:${server.address().port}/`;
const payload = 'user[name]=Peter&tags[]=a&tags[]=b';
try {
const parsedResponse = await axios.post(rootUrl, payload, {
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
});
assert.deepStrictEqual(parsedResponse.data, {
parserRanBeforeHandler: true,
body: {
user: { name: 'Peter' },
tags: ['a', 'b'],
},
});
const ignoredResponse = await axios.post(rootUrl, payload, {
headers: {
'content-type': 'text/plain',
},
});
assert.strictEqual(ignoredResponse.data.parserRanBeforeHandler, false);
assert.notDeepStrictEqual(ignoredResponse.data.body, {
user: { name: 'Peter' },
tags: ['a', 'b'],
});
} finally {
await new Promise((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
});
});
describe('Data URL', () => {
it('should support requesting data URL as a Buffer', async () => {
const buffer = Buffer.from('123');
const dataURI = `data:application/octet-stream;base64,${buffer.toString('base64')}`;
const { data } = await axios.get(dataURI);
assert.deepStrictEqual(data, buffer);
});
it('should support requesting data URL as a Blob (if supported by the environment)', async () => {
if (!isBlobSupported) {
return;
}
const buffer = Buffer.from('123');
const dataURI = `data:application/octet-stream;base64,${buffer.toString('base64')}`;
const { data } = await axios.get(dataURI, { responseType: 'blob' });
assert.strictEqual(data.type, 'application/octet-stream');
assert.deepStrictEqual(await data.text(), '123');
});
it('should support requesting data URL as a String (text)', async () => {
const buffer = Buffer.from('123', 'utf-8');
const dataURI = `data:application/octet-stream;base64,${buffer.toString('base64')}`;
const { data } = await axios.get(dataURI, { responseType: 'text' });
assert.deepStrictEqual(data, '123');
});
it('should support requesting data URL as a Stream', async () => {
const buffer = Buffer.from('123', 'utf-8');
const dataURI = `data:application/octet-stream;base64,${buffer.toString('base64')}`;
const { data } = await axios.get(dataURI, { responseType: 'stream' });
assert.strictEqual(await getStream(data), '123');
});
});
describe('progress', () => {
describe('upload', () => {
it('should support upload progress capturing', async () => {
const server = await startHTTPServer(
{
rate: 100 * 1024,
},
{ port: SERVER_PORT }
);
try {
let content = '';
const count = 10;
const chunk = 'test';
const chunkLength = Buffer.byteLength(chunk);
const contentLength = count * chunkLength;
const readable = stream.Readable.from(
(async function* () {
let i = count;
while (i-- > 0) {
await setTimeoutAsync(1100);
content += chunk;
yield chunk;
}
})()
);
const samples = [];
const { data } = await axios.post(`http://localhost:${server.address().port}`, readable, {
onUploadProgress: ({ loaded, total, progress, bytes, upload }) => {
samples.push({
loaded,
total,
progress,
bytes,
upload,
});
},
headers: {
'Content-Length': contentLength,
},
responseType: 'text',
});
assert.strictEqual(data, content);
assert.deepStrictEqual(
samples,
Array.from(
(function* () {
for (let i = 1; i <= 10; i++) {
yield {
loaded: chunkLength * i,
total: contentLength,
progress: (chunkLength * i) / contentLength,
bytes: 4,
upload: true,
};
}
})()
)
);
} finally {
await stopHTTPServer(server);
}
}, 15000);
});
describe('download', () => {
it('should support download progress capturing', async () => {
const server = await startHTTPServer(
{
rate: 100 * 1024,
},
{ port: SERVER_PORT }
);
try {
let content = '';
const count = 10;
const chunk = 'test';
const chunkLength = Buffer.byteLength(chunk);
const contentLength = count * chunkLength;
const readable = stream.Readable.from(
(async function* () {
let i = count;
while (i-- > 0) {
await setTimeoutAsync(1100);
content += chunk;
yield chunk;
}
})()
);
const samples = [];
const { data } = await axios.post(`http://localhost:${server.address().port}`, readable, {
onDownloadProgress: ({ loaded, total, progress, bytes, download }) => {
samples.push({
loaded,
total,
progress,
bytes,
download,
});
},
headers: {
'Content-Length': contentLength,
},
responseType: 'text',
maxRedirects: 0,
});
assert.strictEqual(data, content);
assert.deepStrictEqual(
samples,
Array.from(
(function* () {
for (let i = 1; i <= 10; i++) {
yield {
loaded: chunkLength * i,
total: contentLength,
progress: (chunkLength * i) / contentLength,
bytes: 4,
download: true,
};
}
})()
)
);
} finally {
await stopHTTPServer(server);
}
}, 15000);
});
});
describe('Rate limit', () => {
it('should support upload rate limit', async () => {
const secs = 10;
const configRate = 100000;
const chunkLength = configRate * secs;
const server = await startHTTPServer();
try {
const buf = Buffer.alloc(chunkLength).fill('s');
const samples = [];
const skip = 4;
const compareValues = toleranceRange(50, 50);
const { data } = await axios.post(`http://localhost:${server.address().port}`, buf, {
onUploadProgress: ({ loaded, total, progress, bytes, rate }) => {
samples.push({
loaded,
total,
progress,
bytes,
rate,
});
},
maxRate: [configRate],
responseType: 'text',
maxRedirects: 0,
});
samples.slice(skip).forEach(({ rate, progress }, i, _samples) => {
assert.ok(
compareValues(rate, configRate),
`Rate sample at index ${i} is out of the expected range (${rate} / ${configRate}) [${_samples
.map((sample) => sample.rate)
.join(', ')}]`
);
const progressTicksRate = 2;
const expectedProgress = (i + skip) / secs / progressTicksRate;
assert.ok(
Math.abs(expectedProgress - progress) < 0.25,
`Progress sample at index ${i} is out of the expected range (${progress} / ${expectedProgress}) [${_samples
.map((sample) => sample.progress)
.join(', ')}]`
);
});
assert.strictEqual(data, buf.toString(), 'content corrupted');
} finally {
await stopHTTPServer(server);
}
}, 30000);
it('should support download rate limit', async () => {
const secs = 10;
const configRate = 100000;
const chunkLength = configRate * secs;
const server = await startHTTPServer();
try {
const buf = Buffer.alloc(chunkLength).fill('s');
const samples = [];
const skip = 4;
const compareValues = toleranceRange(50, 50);
const { data } = await axios.post(`http://localhost:${server.address().port}`, buf, {
onDownloadProgress: ({ loaded, total, progress, bytes, rate }) => {
samples.push({
loaded,
total,
progress,
bytes,
rate,
});
},
maxRate: [0, configRate],
responseType: 'text',
maxRedirects: 0,
});
samples.slice(skip).forEach(({ rate, progress }, i, _samples) => {
assert.ok(
compareValues(rate, configRate),
`Rate sample at index ${i} is out of the expected range (${rate} / ${configRate}) [${_samples
.map((sample) => sample.rate)
.join(', ')}]`
);
const progressTicksRate = 3;
const expectedProgress = (i + skip) / secs / progressTicksRate;
assert.ok(
Math.abs(expectedProgress - progress) < 0.25,
`Progress sample at index ${i} is out of the expected range (${progress} / ${expectedProgress}) [${_samples
.map((sample) => sample.progress)
.join(', ')}]`
);
});
assert.strictEqual(data, buf.toString(), 'content corrupted');
} finally {
await stopHTTPServer(server);
}
}, 30000);
});
describe('request aborting', () => {
it('should be able to abort the response stream', async () => {
const server = await startHTTPServer(
{
rate: 100000,
useBuffering: true,
},
{ port: SERVER_PORT }
);
try {
const buf = Buffer.alloc(1024 * 1024);
const controller = new AbortController();
const { data } = await axios.post(`http://localhost:${server.address().port}`, buf, {
responseType: 'stream',
signal: controller.signal,
maxRedirects: 0,
});
setTimeout(() => {
controller.abort();
}, 500);
let streamError;
data.on('error', (error) => {
streamError = error;
});
await assert.rejects(
new Promise((resolve, reject) => {
stream.pipeline(data, devNull(), (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
})
);
assert.strictEqual(streamError && streamError.code, 'ERR_CANCELED');
} finally {
await stopHTTPServer(server);
}
});
});
it('should properly handle synchronous errors inside the adapter', async () => {
await assert.rejects(() => axios.get('http://192.168.0.285'), /Invalid URL/);
});
it('should support function as paramsSerializer value', async () => {
const server = await startHTTPServer((req, res) => res.end(req.url), { port: SERVER_PORT });
try {
const { data } = await axios.post(`http://localhost:${server.address().port}`, 'test', {
params: {
x: 1,
},
paramsSerializer: () => 'foo',
maxRedirects: 0,
});
assert.strictEqual(data, '/?foo');
} finally {
await stopHTTPServer(server);
}
});
describe('DNS', () => {
it('should support a custom DNS lookup function', async () => {
const server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
const payload = 'test';
let isCalled = false;
try {
const { data } = await axios.post(
`http://fake-name.axios:${server.address().port}`,
payload,
{
lookup: (hostname, opt, cb) => {
isCalled = true;
cb(null, '127.0.0.1', 4);
},
}
);
assert.ok(isCalled);
assert.strictEqual(data, payload);
} finally {
await stopHTTPServer(server);
}
});
it('should support a custom DNS lookup function with address entry passing', async () => {
const server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
const payload = 'test';
let isCalled = false;
try {
const { data } = await axios.post(
`http://fake-name.axios:${server.address().port}`,
payload,
{
lookup: (hostname, opt, cb) => {
isCalled = true;
cb(null, { address: '127.0.0.1', family: 4 });
},
}
);
assert.ok(isCalled);
assert.strictEqual(data, payload);
} finally {
await stopHTTPServer(server);
}
});
it('should support a custom DNS lookup function (async)', async () => {
const server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
const payload = 'test';
let isCalled = false;
try {
const { data } = await axios.post(
`http://fake-name.axios:${server.address().port}`,
payload,
{
lookup: async (hostname, opt) => {
isCalled = true;
return ['127.0.0.1', 4];
},
}
);
assert.ok(isCalled);
assert.strictEqual(data, payload);
} finally {
await stopHTTPServer(server);
}
});
it('should support a custom DNS lookup function with address entry (async)', async () => {
const server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
const payload = 'test';
let isCalled = false;
try {
const { data } = await axios.post(
`http://fake-name.axios:${server.address().port}`,
payload,
{
lookup: async (hostname, opt) => {
isCalled = true;
return { address: '127.0.0.1', family: 4 };
},
}
);
assert.ok(isCalled);
assert.strictEqual(data, payload);
} finally {
await stopHTTPServer(server);
}
});
it('should support a custom DNS lookup function that returns only IP address (async)', async () => {
const server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
const payload = 'test';
let isCalled = false;
try {
const { data } = await axios.post(
`http://fake-name.axios:${server.address().port}`,
payload,
{
lookup: async (hostname, opt) => {
isCalled = true;
return '127.0.0.1';
},
}
);
assert.ok(isCalled);
assert.strictEqual(data, payload);
} finally {
await stopHTTPServer(server);
}
});
it('should handle errors', async () => {
await assert.rejects(async () => {
await axios.get('https://no-such-domain-987654.com', {
lookup,
});
}, /ENOTFOUND/);
});
});
describe('JSON', () => {
it('should support reviver on JSON.parse', async () => {
const server = await startHTTPServer(
async (_, res) => {
res.end(
JSON.stringify({
foo: 'bar',
})
);
},
{ port: SERVER_PORT }
);
try {
const { data } = await axios.get(`http://localhost:${server.address().port}`, {
parseReviver: (key, value) => {
return key === 'foo' ? 'success' : value;
},
});
assert.deepStrictEqual(data, { foo: 'success' });
} finally {
await stopHTTPServer(server);
}
});
});
describe('HTTP2', () => {
const createHttp2Axios = (baseURL) =>
axios.create({
baseURL,
httpVersion: 2,
http2Options: {
rejectUnauthorized: false,
},
});
it('should merge request http2Options with its instance config', async () => {
const http2Axios = createHttp2Axios('https://localhost:8080');
const { data } = await http2Axios.get('/', {
http2Options: {
foo: 'test',
},
adapter: async (config) => {
return {
data: config.http2Options,
};
},
});
assert.deepStrictEqual(data, {
rejectUnauthorized: false,
foo: 'test',
});
});
it('should support http2 transport', async () => {
const server = await startHTTPServer(
(req, res) => {
res.end('OK');
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const { data } = await http2Axios.get(localServerURL);
assert.deepStrictEqual(data, 'OK');
} finally {
await stopHTTPServer(server);
}
});
it('should support request payload', async () => {
const server = await startHTTPServer(null, {
useHTTP2: true,
port: SERVER_PORT,
});
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const payload = 'DATA';
const { data } = await http2Axios.post(localServerURL, payload);
assert.deepStrictEqual(data, payload);
} finally {
await stopHTTPServer(server);
}
});
it('should support FormData as a payload', async () => {
if (typeof FormData !== 'function') {
return;
}
const server = await startHTTPServer(
async (req, res) => {
const { fields, files } = await handleFormData(req);
res.end(
JSON.stringify({
fields,
files,
})
);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const form = new FormData();
form.append('x', 'foo');
form.append('y', 'bar');
const { data } = await http2Axios.post(localServerURL, form);
assert.deepStrictEqual(data, {
fields: {
x: ['foo'],
y: ['bar'],
},
files: {},
});
} finally {
await stopHTTPServer(server);
}
});
describe('response types', () => {
const originalData = '{"test": "OK"}';
const fixtures = {
text: (value) => assert.strictEqual(value, originalData),
arraybuffer: (value) => assert.deepStrictEqual(value, Buffer.from(originalData)),
stream: async (value) => assert.deepStrictEqual(await getStream(value), originalData),
json: async (value) => assert.deepStrictEqual(value, JSON.parse(originalData)),
};
for (const [responseType, assertValue] of Object.entries(fixtures)) {
it(`should support ${responseType} response type`, async () => {
const server = await startHTTPServer(
(req, res) => {
res.end(originalData);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const { data } = await http2Axios.get(localServerURL, {
responseType,
});
await assertValue(data);
} finally {
await stopHTTPServer(server);
}
});
}
});
it('should support request timeout', async () => {
let isAborted = false;
let aborted;
const promise = new Promise((resolve) => (aborted = resolve));
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end('OK');
}, 15000);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
server.on('stream', (http2Stream) => {
http2Stream.once('aborted', () => {
isAborted = true;
aborted();
});
});
await assert.rejects(async () => {
await http2Axios.get(localServerURL, {
timeout: 500,
});
}, /timeout/);
await promise;
assert.ok(isAborted);
} finally {
await stopHTTPServer(server);
}
});
it('should support request cancellation', async () => {
if (typeof AbortSignal !== 'function' || !AbortSignal.timeout) {
return;
}
let isAborted = false;
let aborted;
const promise = new Promise((resolve) => (aborted = resolve));
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end('OK');
}, 15000);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
server.on('stream', (http2Stream) => {
http2Stream.once('aborted', () => {
isAborted = true;
aborted();
});
});
await assert.rejects(async () => {
await http2Axios.get(localServerURL, {
signal: AbortSignal.timeout(500),
});
}, /CanceledError: canceled/);
await promise;
assert.ok(isAborted);
} finally {
await stopHTTPServer(server);
}
});
it('should support stream response cancellation', async () => {
let isAborted = false;
const source = axios.CancelToken.source();
let aborted;
const promise = new Promise((resolve) => (aborted = resolve));
const server = await startHTTPServer(
(req, res) => {
generateReadable(10000, 100, 100).pipe(res);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
server.on('stream', (http2Stream) => {
http2Stream.once('aborted', () => {
isAborted = true;
aborted();
});
});
const { data } = await http2Axios.get(localServerURL, {
cancelToken: source.token,
responseType: 'stream',
});
setTimeout(() => source.cancel());
await assert.rejects(
new Promise((resolve, reject) => {
stream.pipeline(data, devNull(), (error) => {
if (error) {
reject(error);
return;
}
resolve();
});
}),
/CanceledError: canceled/
);
await promise;
assert.ok(isAborted);
} finally {
await stopHTTPServer(server);
}
});
describe('session', () => {
it('should reuse session for the target authority', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => res.end('OK'), 1000);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const [response1, response2] = await Promise.all([
http2Axios.get(localServerURL, {
responseType: 'stream',
}),
http2Axios.get(localServerURL, {
responseType: 'stream',
}),
]);
assert.strictEqual(response1.data.session, response2.data.session);
assert.deepStrictEqual(
await Promise.all([getStream(response1.data), getStream(response2.data)]),
['OK', 'OK']
);
} finally {
await stopHTTPServer(server);
}
});
it('should use different sessions for different authorities', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end('OK');
}, 2000);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
const server2 = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end('OK');
}, 2000);
},
{
useHTTP2: true,
port: ALTERNATE_SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const localServerURL2 = `https://localhost:${server2.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const [response1, response2] = await Promise.all([
http2Axios.get(localServerURL, {
responseType: 'stream',
}),
http2Axios.get(localServerURL2, {
responseType: 'stream',
}),
]);
assert.notStrictEqual(response1.data.session, response2.data.session);
assert.deepStrictEqual(
await Promise.all([getStream(response1.data), getStream(response2.data)]),
['OK', 'OK']
);
} finally {
await Promise.all([stopHTTPServer(server), stopHTTPServer(server2)]);
}
});
it('should use different sessions for requests with different http2Options set', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => {
res.end('OK');
}, 1000);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const [response1, response2] = await Promise.all([
http2Axios.get(localServerURL, {
http2Options: {
sessionTimeout: 2000,
},
}),
http2Axios.get(localServerURL, {
http2Options: {
sessionTimeout: 4000,
},
}),
]);
assert.notStrictEqual(response1.request.session, response2.request.session);
assert.deepStrictEqual([response1.data, response2.data], ['OK', 'OK']);
} finally {
await stopHTTPServer(server);
}
});
it('should use the same session for request with the same resolved http2Options set', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => res.end('OK'), 1000);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const responses = await Promise.all([
http2Axios.get(localServerURL, {
responseType: 'stream',
}),
http2Axios.get(localServerURL, {
responseType: 'stream',
http2Options: undefined,
}),
http2Axios.get(localServerURL, {
responseType: 'stream',
http2Options: {},
}),
]);
assert.strictEqual(responses[1].data.session, responses[0].data.session);
assert.strictEqual(responses[2].data.session, responses[0].data.session);
assert.deepStrictEqual(await Promise.all(responses.map(({ data }) => getStream(data))), [
'OK',
'OK',
'OK',
]);
} finally {
await stopHTTPServer(server);
}
});
it('should use different sessions after previous session timeout', async () => {
const server = await startHTTPServer(
(req, res) => {
setTimeout(() => res.end('OK'), 100);
},
{
useHTTP2: true,
port: SERVER_PORT,
}
);
try {
const localServerURL = `https://localhost:${server.address().port}`;
const http2Axios = createHttp2Axios(localServerURL);
const response1 = await http2Axios.get(localServerURL, {
responseType: 'stream',
http2Options: {
sessionTimeout: 1000,
},
});
const session1 = response1.data.session;
const data1 = await getStream(response1.data);
await setTimeoutAsync(5000);
const response2 = await http2Axios.get(localServerURL, {
responseType: 'stream',
http2Options: {
sessionTimeout: 1000,
},
});
const session2 = response2.data.session;
const data2 = await getStream(response2.data);
assert.notStrictEqual(session1, session2);
assert.strictEqual(data1, 'OK');
assert.strictEqual(data2, 'OK');
} finally {
await stopHTTPServer(server);
}
}, 15000);
});
});
it('should not abort stream on settle rejection', async () => {
const server = await startHTTPServer(
(req, res) => {
res.statusCode = 404;
res.end('OK');
},
{ port: SERVER_PORT }
);
try {
let error;
try {
await axios.get(`http://localhost:${server.address().port}`, {
responseType: 'stream',
});
} catch (err) {
error = err;
}
assert.ok(error, 'request should be rejected');
assert.strictEqual(await getStream(error.response.data), 'OK');
} finally {
await stopHTTPServer(server);
}
});
describe('keep-alive', () => {
it('should not fail with "socket hang up" when using timeouts', async () => {
const server = await startHTTPServer(
async (req, res) => {
if (req.url === '/wait') {
await new Promise((resolve) => setTimeout(resolve, 5000));
}
res.end('ok');
},
{ port: SERVER_PORT }
);
try {
const baseURL = `http://localhost:${server.address().port}`;
await axios.get('/1', { baseURL, timeout: 1000 });
await axios.get('/wait', { baseURL, timeout: 0 });
} finally {
await stopHTTPServer(server);
}
}, 15000);
});
});