Proxy with express.js

2019-01-08 03:10发布

问题:

To avoid same-domain AJAX issues, I want my node.js web server to forward all requests from URL /api/BLABLA to another server, for example other_domain.com:3000/BLABLA, and return to user the same thing that this remote server returned, transparently.

All other URLs (beside /api/*) are to be served directly, no proxying.

How do I achieve this with node.js + express.js? Can you give a simple code example?

(both the web server and the remote 3000 server are under my control, both running node.js with express.js)


So far I found this https://github.com/nodejitsu/node-http-proxy/ , but reading the documentation there didn't make me any wiser. I ended up with

var proxy = new httpProxy.RoutingProxy();
app.all("/api/*", function(req, res) {
    console.log("old request url " + req.url)
    req.url = '/' + req.url.split('/').slice(2).join('/'); // remove the '/api' part
    console.log("new request url " + req.url)
    proxy.proxyRequest(req, res, {
        host: "other_domain.com",
        port: 3000
    });
});

but nothing is returned to the original web server (or to the end user), so no luck.

回答1:

You want to use http.request to create a similar request to the remote API and return its response.

Something like this:

var http = require('http');

/* your app config here */

app.post('/api/BLABLA', function(req, res) {

  var options = {
    // host to forward to
    host:   'www.google.com',
    // port to forward to
    port:   80,
    // path to forward to
    path:   '/api/BLABLA',
    // request method
    method: 'POST',
    // headers to send
    headers: req.headers
  };

  var creq = http.request(options, function(cres) {

    // set encoding
    cres.setEncoding('utf8');

    // wait for data
    cres.on('data', function(chunk){
      res.write(chunk);
    });

    cres.on('close', function(){
      // closed, let's end client request as well 
      res.writeHead(cres.statusCode);
      res.end();
    });

    cres.on('end', function(){
      // finished, let's finish client request as well 
      res.writeHead(cres.statusCode);
      res.end();
    });

  }).on('error', function(e) {
    // we got an error, return 500 error to client and log error
    console.log(e.message);
    res.writeHead(500);
    res.end();
  });

  creq.end();

});

Notice: I haven't really tried the above, so it might contain parse errors hopefully this will give you a hint as to how to get it to work.



回答2:

I did something similar but I used request instead:

var request = require('request');
app.get('/', function(req,res) {
  //modify the url in any way you want
  var newurl = 'http://google.com/';
  request(newurl).pipe(res);
});

I hope this helps, took me a while to realize that I could do this :)



回答3:

To extend trigoman's answer (full credits to him) to work with POST (could also make work with PUT etc):

app.use('/api', function(req, res) {
  var url = 'YOUR_API_BASE_URL'+ req.url;
  var r = null;
  if(req.method === 'POST') {
     r = request.post({uri: url, json: req.body});
  } else {
     r = request(url);
  }

  req.pipe(r).pipe(res);
});


回答4:

I found a shorter and very straightforward solution which works seamlessly, and with authentication as well, using express-http-proxy:

const url = require('url');
const proxy = require('express-http-proxy');

// New hostname+path as specified by question:
const apiProxy = proxy('other_domain.com:3000/BLABLA', {
    forwardPath: req => url.parse(req.baseUrl).path
});

And then simply:

app.use('/api/*', apiProxy);

Note: as mentioned by @MaxPRafferty, use req.originalUrl in place of baseUrl to preserve the querystring:

    forwardPath: req => url.parse(req.baseUrl).path

Update: As mentioned by Andrew (thank you!), there's a ready-made solution using the same principle:

npm i --save http-proxy-middleware

And then:

const proxy = require('http-proxy-middleware')
var apiProxy = proxy('/api', {target: 'http://www.example.org/api'});
app.use(apiProxy)

Documentation: http-proxy-middleware on Github

I know I'm late to join this party, but I hope this helps someone.



回答5:

I used the following setup to direct everything on /rest to my backend server (on port 8080), and all other requests to the frontend server (a webpack server on port 3001). It supports all HTTP-methods, doesn't lose any request meta-info and supports websockets (which I need for hot reloading)

var express  = require('express');
var app      = express();
var httpProxy = require('http-proxy');
var apiProxy = httpProxy.createProxyServer();
var backend = 'http://localhost:8080',
    frontend = 'http://localhost:3001';

app.all("/rest/*", function(req, res) {
  apiProxy.web(req, res, {target: backend});
});

app.all("/*", function(req, res) {
    apiProxy.web(req, res, {target: frontend});
});

var server = require('http').createServer(app);
server.on('upgrade', function (req, socket, head) {
  apiProxy.ws(req, socket, head, {target: frontend});
});
server.listen(3000);


回答6:

First install express and http-proxy-middleware

npm install express http-proxy-middleware --save

Then in your server.js

const express = require('express');
const proxy = require('http-proxy-middleware');

const app = express();
app.use(express.static('client'));

// Add middleware for http proxying 
const apiProxy = proxy('/api', { target: 'http://localhost:8080' });
app.use('/api', apiProxy);

// Render your site
const renderIndex = (req, res) => {
  res.sendFile(path.resolve(__dirname, 'client/index.html'));
}
app.get('/*', renderIndex);

app.listen(3000, () => {
  console.log('Listening on: http://localhost:3000');
});

In this example we serve the site on port 3000, but when a request end with /api we redirect it to localhost:8080.

http://localhost:3000/api/login redirect to http://localhost:8080/api/login



回答7:

Ok, here's a ready-to-copy-paste answer using the require('request') npm module and an environment variable *instead of an hardcoded proxy):

coffeescript

app.use (req, res, next) ->                                                 
  r = false
  method = req.method.toLowerCase().replace(/delete/, 'del')
  switch method
    when 'get', 'post', 'del', 'put'
      r = request[method](
        uri: process.env.PROXY_URL + req.url
        json: req.body)
    else
      return res.send('invalid method')
  req.pipe(r).pipe res

javascript:

app.use(function(req, res, next) {
  var method, r;
  method = req.method.toLowerCase().replace(/delete/,"del");
  switch (method) {
    case "get":
    case "post":
    case "del":
    case "put":
      r = request[method]({
        uri: process.env.PROXY_URL + req.url,
        json: req.body
      });
      break;
    default:
      return res.send("invalid method");
  }
  return req.pipe(r).pipe(res);
});


回答8:

I've created a extremely simple module that does exactly this: https://github.com/koppelaar/auth-proxy



回答9:

I found a shorter solution that does exactly what I want https://github.com/nodejitsu/node-http-proxy/

After installing http-proxy

npm install http-proxy --save

Use it like below in your server/index/app.js

var proxyServer = require('http-route-proxy');
app.use('/api/BLABLA/', proxyServer.connect({
  to: 'other_domain.com:3000/BLABLA',
  https: true,
  route: ['/']
}));

I really have spent days looking everywhere to avoid this issue, tried plenty of solutions and none of them worked but this one.

Hope it is going to help someone else too :)



回答10:

I don't have have an express sample, but one with plain http-proxy package. A very strip down version of the proxy I used for my blog.

In short, all nodejs http proxy packages work at the http protocol level, not tcp(socket) level. This is also true for express and all express middleware. None of them can do transparent proxy, nor NAT, which means keeping incoming traffic source IP in the packet sent to backend web server.

However, web server can pickup original IP from http x-forwarded headers and add it into the log.

The xfwd: true in proxyOption enable x-forward header feature for http-proxy.

const url = require('url');
const proxy = require('http-proxy');

proxyConfig = {
    httpPort: 8888,
    proxyOptions: {
        target: {
            host: 'example.com',
            port: 80
        },
        xfwd: true // <--- This is what you are looking for.
    }
};

function startProxy() {

    proxy
        .createServer(proxyConfig.proxyOptions)
        .listen(proxyConfig.httpPort, '0.0.0.0');

}

startProxy();

Reference for X-Forwarded Header: https://en.wikipedia.org/wiki/X-Forwarded-For

Full version of my proxy: https://github.com/J-Siu/ghost-https-nodejs-proxy