Skip to content

Commit

Permalink
changeOrigin to changeHost & econnreset fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Denoder committed Jan 17, 2024
1 parent 37be1da commit 5737d29
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 376 deletions.
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ $ npm run test
- **prependPath:** `boolean` - Default: true - specify whether you want to prepend the target's path to the proxy path
- **ignorePath:** `boolean` - Default: false - specify whether you want to ignore the proxy path of the incoming request (note: you will have to append / manually if required).
- **localAddress:** `string` Local interface string to bind for outgoing connections
- ~~**changeOrigin:** `boolean` - Default: false - changes the origin of the host header to the target URL~~ | `deprecated`
- **changeOrigin:** `boolean` - Default: false - changes the origin of the host header to the target URL
- **preserveHeaderKeyCase:** `boolean` - Default: false - specify whether you want to keep letter case of response header key
- **auth:** `string` - Basic authentication i.e. 'user:password' to compute an Authorization header.
- **hostRewrite:** `string` - rewrites the location hostname on (201/301/302/307/308) redirects.
- **autoRewrite:** `boolean` - rewrites the location host/port on (201/301/302/307/308) redirects based on requested host/port. Default: false.
- **hostRewrite:** `string` - rewrites the location hostname on (201/301/302/303/307/308) redirects.
- **autoRewrite:** `boolean` - rewrites the location host/port on (201/301/302/303/307/308) redirects based on requested host/port. Default: false.
- **protocolRewrite:** `http|https|null` - rewrites the location protocol on (201/301/302/307/308) redirects to 'http' or 'https'.
- **cookieDomainRewrite:** `false|string|object` - rewrites domain of `set-cookie` headers. Possible values:
* `false` (default): disable cookie rewriting
Expand Down Expand Up @@ -95,6 +96,20 @@ $ npm run test
}, next);
}
```
- **lookup:** `undefined|function` define a custom dns [lookup](https://nodejs.org/docs/latest-v12.x/api/dns.html#dns_dns_lookup_hostname_options_callback) function to use when resolving target/forward hostnames.
```js
// Example: add dns caching
import dlc from 'dns-lookup-cache'
import { ProxyServer } from '@refactorjs/http-proxy'
const proxy = new ProxyServer();
export function (req, res, next) {
proxy.web(req, res, {
target: 'http://example.com',
lookup: dlc.lookup,
}, next);
}
```

#### NOTE:
`options.ws` and `options.ssl` are optional.
Expand Down
2 changes: 1 addition & 1 deletion examples/http/custom-lookup.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import dns from 'node:dns'

const proxy = createServer({
target: 'http://example.com:80',
changeOrigin: true,
changeHost: true,

// Define custom dns lookup function
lookup: function (host, options, callback) {
Expand Down
685 changes: 349 additions & 336 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@refactorjs/http-proxy",
"version": "0.2.8",
"version": "0.2.11",
"description": "http-proxy alternative",
"repository": {
"type": "git",
Expand Down
5 changes: 2 additions & 3 deletions src/proxy/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function setupOutgoing(outgoing: OutgoingOptions, options: OutgoingOption

outgoing.path = [targetPath, outgoingPath].filter(Boolean).join('/').replace(/\/+/g, '/').replace(/http:\//g, 'http://').replace(/https:\//g, 'https://') + params

if (options.changeOrigin) {
if (options.changeOrigin || options.changeHost) {
outgoing.headers.host = required(outgoing.port, target.protocol!) && !hasPort(outgoing.host.toString()) ? outgoing.host + ':' + outgoing.port : outgoing.host;
}

Expand All @@ -136,10 +136,9 @@ export function setupOutgoing(outgoing: OutgoingOptions, options: OutgoingOption
*
* @api private
*/
export function setupSocket(socket: Socket): Socket {
export function setupSocket(socket: Socket | TLSSocket): Socket | TLSSocket {
socket.setTimeout(0);
socket.setNoDelay(true);

socket.setKeepAlive(true, 0);

return socket;
Expand Down
19 changes: 10 additions & 9 deletions src/proxy/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Server, WebPassthrough, WsPassthrough } from '../types';
import type { Socket } from 'node:net';
import type { TLSSocket } from 'node:tls';
import { Buffer } from 'node:buffer';
import { EventEmitter } from 'node:events';
import http, { IncomingMessage, ServerResponse } from 'node:http';
Expand All @@ -9,7 +10,7 @@ import * as wsIncoming from './passes/ws.incoming';

export class ProxyServer extends EventEmitter {
options: Server.ServerOptions;
server?: http.Server | https.Server = undefined;
#server?: http.Server | https.Server = undefined;
webPasses: WebPassthrough[];
wsPasses: WsPassthrough[];

Expand All @@ -29,7 +30,7 @@ export class ProxyServer extends EventEmitter {
super.on('error', this.onError);
}

onError(err: Error) {
onError(err: NodeJS.ErrnoException) {
if (super.listeners('error').length === 1) {
throw err;
}
Expand All @@ -45,15 +46,15 @@ export class ProxyServer extends EventEmitter {
this.web(req, res);
};

this.server = this.options.ssl ? https.createServer(this.options.ssl, closure) : http.createServer(closure);
this.#server = this.options.ssl ? https.createServer(this.options.ssl, closure) : http.createServer(closure);

if (this.options.ws) {
this.server.on('upgrade', (req: IncomingMessage, socket: Socket, head: Buffer) => {
this.#server.on('upgrade', (req: IncomingMessage, socket: Socket | TLSSocket, head: Buffer) => {
this.ws(req, socket, head);
});
}

this.server.listen(port, hostname);
this.#server.listen(port, hostname);

return this;
}
Expand All @@ -62,9 +63,9 @@ export class ProxyServer extends EventEmitter {
* A function that closes the inner webserver and stops listening on given port
*/
close(callback?: () => void) {
if (this.server) {
this.server.close(() => {
this.server = undefined;
if (this.#server) {
this.#server.close(() => {
this.#server = undefined;
callback?.();
});
}
Expand Down Expand Up @@ -140,7 +141,7 @@ export class ProxyServer extends EventEmitter {
* @param socket - Client socket.
* @param args - Additional arguments for the websocket proxy
*/
ws(req: IncomingMessage, socket: Socket, ...args: any[]) {
ws(req: IncomingMessage, socket: Socket | TLSSocket, ...args: any[]) {
let index = args.length - 1;
let head: Buffer | undefined = undefined;
let callback: Server.ErrorCallback | undefined = undefined;
Expand Down
10 changes: 5 additions & 5 deletions src/proxy/passes/web.incoming.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Server, OutgoingOptions } from '../../types';
import type { ProxyServer } from '../';
import type { Socket } from 'node:net';
import type { TLSSocket } from 'node:tls';
import { hasEncryptedConnection, getPort, isSSL, setupOutgoing } from '../common';
import httpNative, { IncomingMessage, ServerResponse } from 'node:http';
import httpsNative, { RequestOptions } from 'node:https';
Expand Down Expand Up @@ -110,7 +111,7 @@ export function stream(req: IncomingMessage, res: ServerResponse, options: Serve
const proxyReq = (isSSL.test(options.target!['protocol' as keyof Server.ServerOptions['target']]) ? https : http).request(setupOutgoing((options.ssl || {}) as OutgoingOptions, options as OutgoingOptions, req) as RequestOptions);

// Enable developers to modify the proxyReq before headers are sent
proxyReq.on('socket', function (socket: Socket) {
proxyReq.on('socket', function (socket: Socket | TLSSocket) {
if (socket.pending) {
// if not connected, wait till connect to pipe
socket.on('connect', () => {
Expand All @@ -134,8 +135,7 @@ export function stream(req: IncomingMessage, res: ServerResponse, options: Serve
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
let timeoutError: NodeJS.ErrnoException = new Error('The proxy request timed out');
timeoutError.code = 'ETIMEDOUT';
return proxyReq.destroy(timeoutError);
}
Expand All @@ -157,8 +157,8 @@ export function stream(req: IncomingMessage, res: ServerResponse, options: Serve
req.on('error', proxyError);

function createErrorHandler(proxyReq: httpNative.ClientRequest, target: Server.ServerOptions['target']) {
return function proxyError(err: any) {
if (req.socket?.destroyed && err.code === 'ECONNRESET') {
return function proxyError(err: NodeJS.ErrnoException) {
if ((req.destroyed || req.socket?.destroyed) && err.code === 'ECONNRESET') {
server.emit('econnreset', err, req, res, target);
proxyReq.destroy();
return;
Expand Down
4 changes: 1 addition & 3 deletions src/proxy/passes/ws.incoming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,7 @@ export async function stream(req: IncomingMessage, socket: Socket, options: Serv
// Also handles when a header is an array
//
// if only not switch request method, like from connect to websocket
if (socket.writable) {
socket.write(createHttpHeader('HTTP/1.1 101 Switching Protocols', proxyRes.headers));
}
socket.write(createHttpHeader('HTTP/1.1 101 Switching Protocols', proxyRes.headers));

let proxyStream = proxySocket;

Expand Down
29 changes: 18 additions & 11 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as url from 'node:url';
import * as net from 'node:net';
import * as tls from 'node:tls';
import * as http from 'node:http';
import * as https from 'node:https';
import * as buffer from 'node:buffer';
Expand Down Expand Up @@ -42,9 +43,9 @@ export interface WebPassthrough {
}

export interface WsPassthrough {
(req: http.IncomingMessage, socket: net.Socket): boolean | void;
(req: http.IncomingMessage, socket: net.Socket, head?: buffer.Buffer): boolean | void;
(req: http.IncomingMessage, socket: net.Socket, options: Server.ServerOptions, head?: buffer.Buffer, server?: ProxyServer, callback?: Server.ErrorCallback): boolean | void;
(req: http.IncomingMessage, socket: net.Socket | tls.TLSSocket): boolean | void;
(req: http.IncomingMessage, socket: net.Socket | tls.TLSSocket, head?: buffer.Buffer): boolean | void;
(req: http.IncomingMessage, socket: net.Socket | tls.TLSSocket, options: Server.ServerOptions, head?: buffer.Buffer, server?: ProxyServer, callback?: Server.ErrorCallback): boolean | void;
}

export declare namespace Server {
Expand Down Expand Up @@ -74,8 +75,14 @@ export declare namespace Server {
ignorePath?: boolean;
/** Local interface string to bind for outgoing connections. */
localAddress?: string;
/** Changes the origin of the host header to the target URL. */
/**
* @deprecated use `changeHost` instead
*
* Changes the origin of the host header to the target URL.
*/
changeOrigin?: boolean;
/** Changes the origin of the host header to the target URL. */
changeHost?: boolean;
/** specify whether you want to keep letter case of response header key */
preserveHeaderKeyCase?: boolean;
/** Basic authentication i.e. 'user:password' to compute an Authorization header. */
Expand Down Expand Up @@ -109,9 +116,9 @@ export declare namespace Server {
/** 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 */
selfHandleResponse?: boolean | Function;
/** 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. */
createWsClientTransformStream?: (req: http.IncomingMessage, proxyReq: http.ClientRequest, proxyRes: http.IncomingMessage) => net.Socket;
createWsClientTransformStream?: (req: http.IncomingMessage, proxyReq: http.ClientRequest, proxyRes: http.IncomingMessage) => net.Socket | tls.TLSSocket;
/** 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. */
createWsServerTransformStream?: (req: http.IncomingMessage, proxyReq: http.ClientRequest, proxyRes: http.IncomingMessage) => net.Socket;
createWsServerTransformStream?: (req: http.IncomingMessage, proxyReq: http.ClientRequest, proxyRes: http.IncomingMessage) => net.Socket | tls.TLSSocket;
/** Buffer */
buffer?: stream.Stream;
/** Custom lookup function to pass to http(s).request */
Expand All @@ -121,10 +128,10 @@ export declare namespace Server {
type StartCallback<TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (req: TIncomingMessage, res: TServerResponse, target: ProxyTargetUrl) => void;
type ProxyReqCallback<TClientRequest = http.ClientRequest, TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (proxyReq: TClientRequest, req: TIncomingMessage, res: TServerResponse, options: ServerOptions) => void;
type ProxyResCallback<TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (proxyRes: TIncomingMessage, req: TIncomingMessage, res: TServerResponse) => void;
type ProxyReqWsCallback<TClientRequest = http.ClientRequest, TIncomingMessage = http.IncomingMessage> = (proxyReq: TClientRequest, req: TIncomingMessage, socket: net.Socket, options: ServerOptions, head: buffer.Buffer) => void;
type EconnresetCallback<TError = Error, TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (err: TError, req: TIncomingMessage, res: TServerResponse, target: ProxyTargetUrl) => void;
type ProxyReqWsCallback<TClientRequest = http.ClientRequest, TIncomingMessage = http.IncomingMessage> = (proxyReq: TClientRequest, req: TIncomingMessage, socket: net.Socket | tls.TLSSocket, options: ServerOptions, head: buffer.Buffer) => void;
type EconnresetCallback<TError = NodeJS.ErrnoException, TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (err: TError, req: TIncomingMessage, res: TServerResponse, target: ProxyTargetUrl) => void;
type EndCallback<TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (req: TIncomingMessage, res: TServerResponse, proxyRes: TIncomingMessage) => void;
type OpenCallback = (proxySocket: net.Socket) => void;
type CloseCallback<TIncomingMessage = http.IncomingMessage> = (proxyRes: TIncomingMessage, proxySocket: net.Socket, proxyHead: buffer.Buffer) => void;
type ErrorCallback<TError = Error, TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (err: TError, req: TIncomingMessage, res: TServerResponse | net.Socket, target?: Server.ServerOptions['target']) => void;
type OpenCallback = (proxySocket: net.Socket | tls.TLSSocket) => void;
type CloseCallback<TIncomingMessage = http.IncomingMessage> = (proxyRes: TIncomingMessage, proxySocket: net.Socket | tls.TLSSocket, proxyHead: buffer.Buffer) => void;
type ErrorCallback<TError = NodeJS.ErrnoException, TIncomingMessage = http.IncomingMessage, TServerResponse = http.ServerResponse> = (err: TError, req: TIncomingMessage, res: TServerResponse | net.Socket | tls.TLSSocket, target?: Server.ServerOptions['target']) => void;
}
6 changes: 3 additions & 3 deletions test/lib-http-proxy-common-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,15 +347,15 @@ describe('src/proxy/common.ts', () => {
});
});

describe('when using changeOrigin', () => {
describe('when using changeHost', () => {
it('should correctly set the port to the host when it is a non-standard port using new URL()', () => {
const outgoing = {};
const myEndpoint = 'https://myCouch.com:6984';
setupOutgoing(
outgoing,
{
target: new URL(myEndpoint),
changeOrigin: true,
changeHost: true,
},
{ url: '/' },
);
Expand All @@ -373,7 +373,7 @@ describe('src/proxy/common.ts', () => {
host: 'mycouch.com',
port: 6984,
},
changeOrigin: true,
changeHost: true,
},
{ url: '/' },
);
Expand Down
4 changes: 2 additions & 2 deletions test/lib-http-proxy-passes-web-incoming-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -514,10 +514,10 @@ describe('#createProxyServer.web() using own http server', () => {
);
});

it('should proxy the request and handle changeOrigin option', async () => {
it('should proxy the request and handle changeHost option', async () => {
const proxy = createProxyServer({
target: 'http://127.0.0.1:8082',
changeOrigin: true,
changeHost: true,
});

function requestHandler(req, res) {
Expand Down

0 comments on commit 5737d29

Please sign in to comment.