Skip to content

Commit

Permalink
Fix tests & add a few more pull requests
Browse files Browse the repository at this point in the history
Added Pull Request from:
 - http-party/node-http-proxy#1634
- http-party/node-http-proxy#1638
- http-party/node-http-proxy#1650

fix tests that were having issues with the ports (if it fails the tests needs to be reran, seems to be an async issue)
  • Loading branch information
Denoder committed Oct 22, 2023
1 parent aaf0dd2 commit 4dbda7a
Show file tree
Hide file tree
Showing 9 changed files with 592 additions and 4,050 deletions.
11 changes: 6 additions & 5 deletions README.md
Expand Up @@ -74,14 +74,15 @@ $ npm run test
- **mergeCookies:** `boolean` - allows to merge `set-cookie` headers from passed response and response from target. Default: false.
- **headers:** `object` - object with extra headers to be added to target requests.
- **outgoingHeaders:** `object` - object with extra headers to be added to proxy requests.
- **proxyTimeout:** `number` timeout (in millis) for outgoing proxy requests
- **timeout:** `number` timeout (in millis) for incoming requests
- **proxyTimeout:** `number` - timeout (in millis) for outgoing proxy requests
- **proxyTimeoutCustomError**: `boolean` - specify whether you want to throw a custom `ETIMEDOUT` error when the `proxyTimeout` is reached. If false then the default `ECONNRESET` error will be thrown. Default: false.
- **timeout:** `number` - timeout (in millis) for incoming requests
- **followRedirects:** `boolean` - Default: false - specify whether you want to follow redirects
- **forcePasses:** `boolean` - if set to true the web passes will be run even if `selfHandleResponse` is also set to true. (Default: false)
- **selfHandleResponse:** `boolean` - if set to true, none of the webOutgoing passes are called and it's your responsibility to appropriately return the response by listening and acting on the `proxyRes` event
- **createWsClientTransformStream:** `function|null` if set, this function will be called with three arguments `req`, `proxyReq` and `proxyRes` and should return a Duplex stream, data from the client websocket will be piped through this stream before being piped to the server, allowing you to influence the request data.
- **createWsServerTransformStream:** `function|null` if set, this function will be called with three arguments `req`, `proxyReq` and `proxyRes` and should return a Duplex stream, data from the server websocket will be piped through this stream before being piped to the client, allowing you to influence the response data.
- **buffer:** `Buffer` stream of data to send as the request body. Maybe you have some middleware that consumes the request stream before proxying it on e.g. If you read the body of a request into a field called 'req.rawbody' you could restream this field in the buffer option:
- **createWsClientTransformStream:** `function|null` - if set, this function will be called with three arguments `req`, `proxyReq` and `proxyRes` and should return a Duplex stream, data from the client websocket will be piped through this stream before being piped to the server, allowing you to influence the request data.
- **createWsServerTransformStream:** `function|null` - if set, this function will be called with three arguments `req`, `proxyReq` and `proxyRes` and should return a Duplex stream, data from the server websocket will be piped through this stream before being piped to the client, allowing you to influence the response data.
- **buffer:** `Buffer` - stream of data to send as the request body. Maybe you have some middleware that consumes the request stream before proxying it on e.g. If you read the body of a request into a field called 'req.rawbody' you could restream this field in the buffer option:
```ts
import streamify from 'stream-array'
import { ProxyServer } from '@refactorjs/http-proxy'
Expand Down
4,500 changes: 496 additions & 4,004 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "@refactorjs/http-proxy",
"version": "0.1.5",
"version": "0.1.8",
"description": "http-proxy alternative",
"repository": {
"type": "git",
Expand All @@ -26,24 +26,24 @@
"test": "vitest run"
},
"dependencies": {
"follow-redirects": "^1.15.2",
"follow-redirects": "^1.15.3",
"requires-port": "^1.0.0"
},
"devDependencies": {
"@types/follow-redirects": "^1.14.1",
"@types/node": "^20.5.9",
"@types/requires-port": "^1.0.0",
"@types/follow-redirects": "^1.14.3",
"@types/node": "^18",
"@types/requires-port": "^1.0.2",
"async": "^3.2.4",
"concat-stream": "^2.0.0",
"nyc": "^15.1.0",
"semver": "^7.3.8",
"socket.io": "^4.5.4",
"socket.io-client": "^4.5.4",
"semver": "^7.5.4",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"sse": "0.0.8",
"typescript": "^5.0.2",
"typescript": "^5.2.2",
"unbuild": "^2.0.0",
"vitest": "^0.34.1",
"ws": "^8.12.0"
"vitest": "^0.34.6",
"ws": "^8.14.2"
},
"engines": {
"node": ">=14.13.1"
Expand Down
7 changes: 5 additions & 2 deletions src/proxy/common.ts
Expand Up @@ -9,7 +9,7 @@ const upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i;
/**
* Simple Regex for testing if protocol is https
*/
export const isSSL = /^https|wss/;
export const isSSL = /^(?:http|ws)s/;

/**
* Copies the right headers from `options` and `req` to
Expand Down Expand Up @@ -62,7 +62,10 @@ export function setupOutgoing(outgoing: OutgoingOptions, options: OutgoingOption
if (options.ca) outgoing.ca = options.ca;

if (sslEnabled) {
outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? true : options.secure;
// Respect `NODE_TLS_REJECT_UNAUTHORIZED` environment variable (https://nodejs.org/docs/latest/api/cli.html#node_tls_reject_unauthorizedvalue)
const NODE_TLS_REJECT_UNAUTHORIZED = process.env['NODE_TLS_REJECT_UNAUTHORIZED'];
const rejectUnauthorizedEnv = typeof NODE_TLS_REJECT_UNAUTHORIZED !== 'undefined' ? NODE_TLS_REJECT_UNAUTHORIZED.toString() : undefined;
outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? (rejectUnauthorizedEnv !== '0') : options.secure;
}

outgoing.agent = options.agent || false;
Expand Down
20 changes: 14 additions & 6 deletions src/proxy/passes/web.incoming.ts
Expand Up @@ -53,16 +53,18 @@ export function timeout(req: IncomingMessage, res: ServerResponse, options: Serv
export function XHeaders(req: IncomingMessage, res: ServerResponse, options: Server.ServerOptions): void | boolean {
if (!options.xfwd) return;

let encrypted = hasEncryptedConnection(req);
let values: Record<string, string | string[] | undefined> = {
const encrypted = hasEncryptedConnection(req);
const values: Record<string, string | string[] | undefined> = {
for: req.socket?.remoteAddress,
port: getPort(req),
proto: encrypted ? 'https' : 'http'
};

for (let header of ['for', 'port', 'proto']) {
for (const header of ['for', 'port', 'proto']) {
const headerName = 'x-forwarded-' + header;
if (!req.headers[headerName]) {
if (req.headers?.[headerName]) {
req.headers[headerName] += `, ${values[header]}`;
} else {
req.headers[headerName] = values[header];
}
}
Expand Down Expand Up @@ -126,6 +128,12 @@ export function stream(req: IncomingMessage, res: ServerResponse, options: Serve
// show an error page at the initial request
if (options.proxyTimeout) {
proxyReq.setTimeout(options.proxyTimeout, function () {
if (options.proxyTimeoutCustomError) {
let timeoutError = new Error('The proxy request timed out');
// @ts-ignore - NodeJs does not export code
timeoutError.code = 'ETIMEDOUT';
return proxyReq.destroy(timeoutError);
}
proxyReq.destroy();
});
}
Expand All @@ -145,7 +153,7 @@ export function stream(req: IncomingMessage, res: ServerResponse, options: Serve

function createErrorHandler(proxyReq: httpNative.ClientRequest, target: Server.ServerOptions['target']) {
return function proxyError(err: any) {
if (req.socket.destroyed && err.code === 'ECONNRESET') {
if (req.socket?.destroyed && err.code === 'ECONNRESET') {
server.emit('econnreset', err, req, res, target);
proxyReq.destroy();
return;
Expand Down Expand Up @@ -193,4 +201,4 @@ export function stream(req: IncomingMessage, res: ServerResponse, options: Serve
}
}
});
}
}
2 changes: 1 addition & 1 deletion src/proxy/passes/web.outgoing.ts
Expand Up @@ -128,7 +128,7 @@ export function writeHeaders(req: IncomingMessage, res: ServerResponse, proxyRes
// https://nodejs.org/api/http.html#http_message_rawheaders
if (preserveHeaderKeyCase && proxyRes.rawHeaders != undefined) {
for (let i = 0; i < proxyRes.rawHeaders.length; i += 2) {
let key = proxyRes.rawHeaders[i];
const key = proxyRes.rawHeaders[i];
rawHeaderKeyMap[key.toLowerCase()] = key;
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Expand Up @@ -105,6 +105,8 @@ export declare namespace Server {
outgoingHeaders?: Record<string, string | number | readonly string[]> | http.IncomingHttpHeaders;
/** Timeout (in milliseconds) when proxy receives no response from target. Default: 120000 (2 minutes) */
proxyTimeout?: number;
/** specify whether you want to throw a custom `ETIMEDOUT` error when the `proxyTimeout` is reached. If false then the default `ECONNRESET` error will be thrown. Default: false */
proxyTimeoutCustomError?: boolean;
/** Timeout (in milliseconds) for incoming requests */
timeout?: number;
/** Specify whether you want to follow redirects. Default: false */
Expand Down
72 changes: 54 additions & 18 deletions test/lib-http-proxy-passes-web-incoming-test.js
Expand Up @@ -314,13 +314,49 @@ describe('#createProxyServer.web() using own http server', () => {
).end();
});

it('should proxy the request with custom timeout errors (proxyTimeoutCustomError)', (done) => {
const proxy = createProxyServer({
target: 'http://127.0.0.1:45003',
proxyTimeout: 100,
proxyTimeoutCustomError: true
});

createServer().listen(45003);

const proxyServer = createServer(requestHandler);

const started = new Date().getTime();

function requestHandler(req, res) {
proxy.once('error', function (err, errReq, errRes) {
proxyServer.close();
expect(err).to.be.an(Error);
expect(errReq).to.be.equal(req);
expect(errRes).to.be.equal(res);
expect(new Date().getTime() - started).to.be.greaterThan(99);
expect(err.code).to.be('ETIMEDOUT');
done();
});

proxy.web(req, res);
}

proxyServer.listen('8074');

request({
hostname: '127.0.0.1',
port: '8074',
method: 'GET',
}, () => {}).end();
});

it('should proxy the request and handle timeout error', async () => {
const proxy = createProxyServer({
target: 'http://127.0.0.1:45001',
target: 'http://127.0.0.1:45009',
timeout: 100,
});

net.createServer().listen(45001);
net.createServer().listen(45009);

const proxyServer = createServer(requestHandler);

Expand All @@ -338,12 +374,12 @@ describe('#createProxyServer.web() using own http server', () => {
proxy.web(req, res);
}

proxyServer.listen('8085');
proxyServer.listen('8075');

const req = request(
{
hostname: '127.0.0.1',
port: '8085',
port: '8075',
method: 'GET',
}, function () { });

Expand Down Expand Up @@ -505,9 +541,9 @@ describe('#createProxyServer.web() using own http server', () => {
await waitForClosed(proxyServer, source);
});

it.skipIf(semver.gte(process.version, '18.0.0'))('should proxy the request with the Authorization header set', async () => {
it('should proxy the request with the Authorization header set', async () => {
const proxy = createProxyServer({
target: 'http://127.0.0.1:8080',
target: 'http://127.0.0.1:8091',
auth: 'user:pass',
});

Expand All @@ -527,13 +563,13 @@ describe('#createProxyServer.web() using own http server', () => {
});

proxyServer.listen('8081');
source.listen('8080');
source.listen('8091');

request('http://127.0.0.1:8081', function () { }).end();
await waitForClosed(proxyServer, source);
});

it.skipIf(semver.gte(process.version, '18.0.0'))('should proxy requests to multiple servers with different options', async () => {
it('should proxy requests to multiple servers with different options', async () => {
const proxy = createProxyServer();

// proxies to two servers depending on url, rewriting the url as well
Expand All @@ -543,11 +579,11 @@ describe('#createProxyServer.web() using own http server', () => {
if (req.url.indexOf('/s1/') === 0) {
proxy.web(req, res, {
ignorePath: true,
target: 'http://127.0.0.1:8081' + req.url.substring(3),
target: 'http://127.0.0.1:8092' + req.url.substring(3),
});
} else {
proxy.web(req, res, {
target: 'http://127.0.0.1:8082',
target: 'http://127.0.0.1:8093',
});
}
}
Expand All @@ -558,24 +594,24 @@ describe('#createProxyServer.web() using own http server', () => {
res.end();
source1.close();
expect(req.method).toEqual('GET');
expect(req.headers.host.split(':')[1]).toEqual('8080');
expect(req.headers.host.split(':')[1]).toEqual('8090');
expect(req.url).toEqual('/test1');
});

const source2 = createServer(function (req, res) {
res.end();
source2.close();
expect(req.method).toEqual('GET');
expect(req.headers.host.split(':')[1]).toEqual('8080');
expect(req.headers.host.split(':')[1]).toEqual('8090');
expect(req.url).toEqual('/test2');
});

proxyServer.listen('8080');
source1.listen('8081');
source2.listen('8082');
proxyServer.listen('8090');
source1.listen('8092');
source2.listen('8093');

request('http://127.0.0.1:8080/s1/test1', function () { }).end();
request('http://127.0.0.1:8080/test2', function () { }).end();
request('http://127.0.0.1:8090/s1/test1', function () { }).end();
request('http://127.0.0.1:8090/test2', function () { }).end();

await waitForClosed(source1, source2);
proxyServer.close();
Expand All @@ -584,7 +620,7 @@ describe('#createProxyServer.web() using own http server', () => {
});

describe('#followRedirects', () => {
it.skipIf(semver.gte(process.version, '18.0.0'))('should proxy the request follow redirects', async () => {
it('should proxy the request follow redirects', async () => {
const proxy = createProxyServer({
target: 'http://127.0.0.1:8080',
followRedirects: {},
Expand Down
6 changes: 3 additions & 3 deletions test/lib-http-proxy-passes-web-outgoing-test.js
Expand Up @@ -570,16 +570,16 @@ describe('src/proxy/passes/web.outgoing.ts', () => {
how: 'are you?'
}
};

const res = {
setHeader: function (k, v) {
this.headers[k] = v;
},
headers: {}
};

attachOutgoingHeaders({}, res, proxyRes, { outgoingHeaders: { billy: 'sally' } });

expect(res.headers.hey).toBeUndefined();
expect(res.headers.how).toBeUndefined();
expect(res.headers.billy).toEqual('sally');
Expand Down

0 comments on commit 4dbda7a

Please sign in to comment.