用express.js代理


168

为避免同域AJAX问题,我希望我的node.js Web服务器转发来自URL的所有请求 /api/BLABLA到另一个服务器,例如other_domain.com:3000/BLABLA,并透明地将与该远程服务器返回的相同的内容返回给用户。

所有其他网址(旁边 /api/*)均应直接提供,不能进行代理。

如何使用node.js + express.js实现此目的?您可以举一个简单的代码示例吗?

(Web服务器和远程3000服务器都在我的控制之下,都运行带有express.js的node.js)


到目前为止,我已经找到了这个https://github.com/http-party/node-http-proxy,但是阅读那里的文档并没有使我更加明智。我最终以

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
    });
});

但没有任何内容返回到原始Web服务器(或最终用户),因此没有运气。


您的操作方式对我有效,没有任何修改
Saule 2016年

1
尽管现在回答还为时已晚,但面临类似的问题,并通过删除正文解析器解决了该问题,以便在进一步代理之前不对请求正文进行解析。
VyvIT

Answers:


52

你想用 http.request创建与远程API类似的请求并返回其响应。

像这样:

const http = require('http');
// or use import http from 'http';


/* your app config here */

app.post('/api/BLABLA', (oreq, ores) => {
  const 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: oreq.headers,
  };

  const creq = http
    .request(options, pres => {
      // set encoding
      pres.setEncoding('utf8');

      // set http status code based on proxied response
      ores.writeHead(pres.statusCode);

      // wait for data
      pres.on('data', chunk => {
        ores.write(chunk);
      });

      pres.on('close', () => {
        // closed, let's end client request as well
        ores.end();
      });

      pres.on('end', () => {
        // finished, let's finish client request as well
        ores.end();
      });
    })
    .on('error', e => {
      // we got an error
      console.log(e.message);
      try {
        // attempt to set error message and http status
        ores.writeHead(500);
        ores.write(e.message);
      } catch (e) {
        // ignore
      }
      ores.end();
    });

  creq.end();
});

注意:我还没有真正尝试过上面的方法,因此它可能包含解析错误,希望这会提示您如何使其工作。


5
是的,需要进行一些修改,但是我比引入一个额外的新“代理”模块依赖项更好。有点冗长,但至少我确切地知道发生了什么。干杯。
user124114

似乎您需要在写入数据chink之前执行res.writeHead,否则会出错(无法在正文之后写入标题)。
setec 2015年

3
@ user124114-请输入您使用过的完整解决方案
Michal Tsadok

1
似乎您将无法通过这种方式设置标题。Cannot render headers after they are sent to the client
Shnd

1
我已经更新了es6语法的答案,并修复了writeHead问题
mekwall

219

自2020年2月起,该请求已被弃用,出于历史原因,我将在下面保留答案,但请考虑转到此问题中列出的替代方法。

封存

我做了类似的事情,但我改用request

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);
});

我希望这会有所帮助,花了我一段时间才意识到我可以做到这一点:)


5
谢谢,比使用Node.js的HTTP请求简单得多
Alex Turpin 2013年

17
就更简单了,如果你还管的要求: stackoverflow.com/questions/7559862/...
斯蒂芬·霍耶

1
干净的解决方案。我发布了一个答案,使其也可以与POST请求一起使用(否则,它不会将您的帖子正文转发到API)。如果您编辑答案,我很乐意删除我的答案。
亨里克·皮纳尔

另请参阅此答案以改进错误处理。
坦林2014年

每当我尝试进行类似的路由(或完全相同)时,都会得到以下结果:stream.js:94 throw er; //管道中未处理的流错误。^错误:在errnoException上的getaddrinfo ENOTFOUND google.com(dns.js:44:10)在GetAddrInfoReqWrap.onlookup处[作为未完成](dns.js:94:26)有什么想法吗?
keinabel

82

我找到了一个更短,非常简单的解决方案,它可以无缝地与身份验证一起使用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', {
    proxyReqPathResolver: req => url.parse(req.baseUrl).path
});

然后简单地:

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

注意:如@MaxPRafferty所述,请使用req.originalUrl代替baseUrl保留查询字符串:

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

更新:正如安德鲁(谢谢!)所提到的,有一个使用相同原理的现成解决方案:

npm i --save http-proxy-middleware

然后:

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

文档:Github上的http-proxy-middleware

我知道我迟到不能参加这个聚会,但是我希望这对某人有帮助。


3
req.url没有完整的URL,因此将答案更新为使用req.baseUrl而不是req.url
Vinoth Kumar

1
我还喜欢使用req.originalUrl代替baseUrl来保留查询字符串,但这可能并不总是所需的行为。
MaxPRafferty

@MaxPRafferty-无效的评论。值得注意。谢谢。
自私的

4
这是最好的解决方案。我正在使用http-proxy-middleware,但这是相同的概念。如果已有出色的代理解决方案,请不要旋转自己的代理解决方案。
安德鲁(Andrew)

1
@tannerburton谢谢!我更新了答案。
自私的

46

扩展trigoman的答案(对他的功劳全称)以使用POST(也可以使用PUT等):

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);
});

1
无法使其与PUT一起使用。但是非常适合GET和POST。谢谢!!
Mariano Desanze 2014年

5
@Protron用于PUT请求只是使用类似if(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil

如果您需要在PUT或POST请求中传递标头,请确保删除content-length标头,以便请求可以对其进行计算。否则,接收服务器可能会截断数据,这将导致错误。
卡洛斯·赖默

@Henrik Peinar,当我发出登录请求并希望从web.com/api/login重定向到web.com/时,此方法会有所帮助
valik

22

我使用以下设置将所有内容定向/rest到后端服务器(端口8080)上,并将所有其他请求定向到前端服务器(端口3001上的Webpack服务器)。它支持所有HTTP方法,不丢失任何请求元信息,并支持websocket(我需要进行热重装)

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);

1
这是唯一一个也处理网络套接字的程序。
sec0ndHand

11

首先安装Express和http-proxy-middleware

npm install express http-proxy-middleware --save

然后在您的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');
});

在此示例中,我们在端口3000上为站点提供服务,但是当请求以/ api结尾时,我们会将其重定向到localhost:8080。

http:// localhost:3000 / api / login重定向到http:// localhost:8080 / api / login


6

好的,这是使用require('request')npm模块和环境变量(而不是硬编码代理)的易于复制粘贴的答案:

咖啡脚本

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);
});

2
您可以先清除(例如,如果该方法不在批准的方法列表中,则调用默认值的if语句),而不是全部使用相同的请求(除了使用不同的请求函数)以外的所有case语句来做相同的事情r = request [method](/ *其余* /);
保罗

2

我发现了一个更短的解决方案,完全可以满足我的要求 https://github.com/http-party/node-http-proxy

安装后 http-proxy

npm install http-proxy --save

在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: ['/']
}));

我确实花了几天时间到处寻找避免该问题的方法,尝试了很多解决方案,但除了这个解决方案,其他解决方案均无效。

希望它也能帮助别人:)


0

我没有快递样品,但有普通http-proxy包装。我用于博客的代理的简化版本。

简而言之,所有的nodejs http代理程序包都在http协议级别而不是tcp(socket)级别工作。Express和所有Express中间件也是如此。它们都不能做透明代理,也不能做NAT,这意味着将传入流量源IP保留在发送到后端Web服务器的数据包中。

但是,Web服务器可以从http x转发的标头中提取原始IP,并将其添加到日志中。

xfwd: truein proxyOptionenable x-forward标头功能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();

X转发标题的参考: https //en.wikipedia.org/wiki/X-Forwarded-For

我的代理的完整版本:https : //github.com/J-Siu/ghost-https-nodejs-proxy

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.