Search code examples
node.jshttpreverse-proxy

How I can reverse-proxy http(s) requests using nodejs?


I am developing an http "middleman" (aka reverse proxy) using nodejs. So far I made a basic server listening to http and https:

const http = require('node:http');
let https = require('node:https');


const app = (req,res)=>{
  // Forward the request to respecitve http
};

http.createServer(app).listen(80);
https.createServer({
  // typical https options ommited for siplicity
},app).listen(443);

In my case, I just want to forward the incomming request to actual server. I mean, if I receive a http GET request for yahoo.com just forward it to actual yahoo.

Is there a way to do it?


Solution

  • Yes there is:

    
    const http = require('node:http');
    let https = require('node:https');
    
    const getProtocol = (req) => {
        if(req.protocol) return req.protocol;
    
        return req.secure ? 'https':'http';
    };
    
    const app = (req,res)=>{
        const http_handler = getProtocol(req) == 'http'?http:https;
        const http_client = http_handler.request({
            host: req.headers.host,
            path: req.url,
            method: req.method,
            headers: req.headers,
            body: req.body
        },(resp)=>{
            res.writeHead(resp.statusCode,resp.headers);
            resp.pipe(res);
        });
    
        req.pipe(http_client);
    };
    
    http.createServer(app).listen(80);
    https.createServer({
      // typical https options ommited for siplicity
    },app).listen(443);
    
    

    As you can see both http and https can be used as clients as well. I can detect if http or https using the getProtocol that can be used both at expressjs and other frameworks (such as connect).

    Using that at section:

        const http_handler = getProtocol(req) == 'http'?http:https;
    

    I decide whether to use http or https client for better emulation. Then I make the client using http_handler.request and I pipe the request using req.pipe(http_client);

    At callback function provided in http_handler.request I also writhe the appropriate response and heads because by default resp.pipe(res); will send 200 status code, a thing that might not always be true (for example http redirects).

    The resp.pipe(res); forwards only the body instead of the headers as well.Therefore, we must send them first.


    How to test it:

    Using curl you can test it like this:

    curl --resolve yahoo.com:443:172.21.0.2 --resolve yahoo.com:80:172.21.0.2 -vvv https://yahoo.com -k -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1 Camino/2.2.1"
    
    

    Using --resolve I temporally override the dns resolution and I provide manually the appropriate IPS. Also, using the -k parameter I accept any certificate (it's ok for local development). With that, you can test a reverse proxy using custom ssl/tls certificates.