Proxy com express.js

168

Para evitar problemas de AJAX no mesmo domínio, desejo que meu servidor da web node.js. encaminhe todas as solicitações da URL /api/BLABLApara outro servidor, por exemplo other_domain.com:3000/BLABLA, e retorne ao usuário a mesma coisa que esse servidor remoto retornou de forma transparente.

Todos os outros URLs (ao lado /api/*) devem ser veiculados diretamente, sem proxy.

Como conseguir isso com o node.js + express.js? Você pode dar um exemplo de código simples?

(o servidor da Web e o 3000servidor remoto estão sob meu controle, ambos executando o node.js com o express.js)


Até agora, encontrei este https://github.com/http-party/node-http-proxy , mas ler a documentação lá não me deixou mais sábio. Acabei com

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

mas nada é retornado ao servidor Web original (ou ao usuário final), portanto, não há sorte.

user124114
fonte
a maneira de fazer isso está funcionando para mim, sem qualquer modificação
Saule
1
Embora tenha sido um pouco tarde para responder, mas estava enfrentando um problema semelhante e o resolveu removendo o analisador de corpo, para que o corpo da solicitação não seja analisado antes de executá-lo novamente.
VyvIT 12/04

Respostas:

52

Você deseja usar http.requestpara criar uma solicitação semelhante à API remota e retornar sua resposta.

Algo assim:

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

Aviso: eu realmente não tentei o acima, por isso pode conter erros de análise, espero que isso lhe dê uma dica de como fazê-lo funcionar.

mekwall
fonte
5
Sim, algumas modificações foram necessárias, mas eu gosto disso melhor do que introduzir uma nova dependência de módulo "Proxy" extra. Um pouco detalhado, mas pelo menos eu sei exatamente o que está acontecendo. Felicidades.
User124114
Parece que você precisa fazer o res.writeHead antes da gravação de dados, caso contrário, você receberá um erro (os cabeçalhos não podem ser escritos após o corpo).
setec 4/06
3
@ user124114 - coloque a solução completa que você usou #
Michal Tsadok 10/10
1
parece que você terá problemas ao definir cabeçalhos dessa maneira. Cannot render headers after they are sent to the client
Shnd 25/06/19
1
Atualizei a resposta para a sintaxe es6 e corrigi o problema
writeHead
219

solicitação foi descontinuada em fevereiro de 2020, deixarei a resposta abaixo por motivos históricos, mas considere a possibilidade de mudar para uma alternativa listada nesta edição .

Arquivo

Fiz algo semelhante, mas usei 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);
});

Espero que isso ajude, demorei um pouco para perceber que eu poderia fazer isso :)

trigoman
fonte
5
Graças, muito mais simples do que usando Node.js' HTTP pedido
Alex Turpin
17
Ainda mais simples, se você também canalizar a solicitação: stackoverflow.com/questions/7559862/…
Stephan Hoyer
1
Solução agradável e limpa. Postei uma resposta para fazê-lo funcionar também com a solicitação POST (caso contrário, não encaminhará o corpo da postagem para a API). Se você editar sua resposta, ficarei feliz em remover a minha.
Henrik Peinar
Consulte também esta resposta para melhorar o tratamento de erros.
Tamlyn
sempre que tento fazer roteamento semelhante (ou exatamente o mesmo), acabo com o seguinte: stream.js: 94 throw er; // Erro de fluxo não tratado no pipe. ^ Erro: getaddrinfo ENOTFOUND google.com at errnoException (dns.js: 44: 10) em GetAddrInfoReqWrap.onlookup [como incompleto] (dns.js: 94: 26) alguma idéia?
Keinabel # 20/15
82

Encontrei uma solução mais curta e direta que funciona perfeitamente e com autenticação, usando 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
});

E então simplesmente:

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

Nota: conforme mencionado por @MaxPRafferty, use req.originalUrlno lugar de baseUrlpara preservar a string de consulta :

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

Atualização: Como mencionado por Andrew (obrigado!), Há uma solução pronta usando o mesmo princípio:

npm i --save http-proxy-middleware

E depois:

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

Documentação: http-proxy-middleware no Github

Sei que estou atrasado para participar desta festa, mas espero que isso ajude alguém.

Egoísta
fonte
3
O req.url não tem a url completa, então atualizou a resposta para usar req.baseUrl vez de req.url
Vinoth Kumar
1
Também gosto de usar req.originalUrl no lugar de baseUrl para preservar as strings de consulta, mas esse nem sempre é o comportamento desejado.
MaxRafferty
@MaxPRafferty - comentário vaid. Vale nada. Obrigado.
Selfish
4
essa é a melhor solução. Estou usando o http-proxy-middleware, mas é o mesmo conceito. Não gire sua própria solução de proxy quando já houver soluções excelentes por aí.
Andrew
1
@tannerburton thanks! Eu atualizei a resposta.
egoísta
46

Para estender a resposta do trigoman (créditos completos para ele) para trabalhar com o POST (também pode funcionar com o 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);
});
Henrik Peinar
fonte
1
Não foi possível fazê-lo funcionar com o PUT. Mas funciona muito bem para GET e POST. Obrigado!!
Mariano Desanze
5
O @Protron para solicitações PUT usa apenas algo comoif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil
Se você precisar passar pelos cabeçalhos como parte de uma solicitação PUT ou POST, exclua o cabeçalho de tamanho do conteúdo para que a solicitação possa calculá-lo. Caso contrário, o servidor de recebimento poderá truncar os dados, o que levará a um erro.
Carlos Rymer
@Henrik Peinar, isso ajudará quando eu fizer uma solicitação de login e esperar redirecionar de web.com/api/login para web.com/
valik
22

Usei a seguinte configuração para direcionar tudo /restpara o servidor back-end (na porta 8080) e todas as outras solicitações ao servidor front-end (um servidor webpack na porta 3001). Ele suporta todos os métodos HTTP, não perde nenhuma meta-informação de solicitação e suporta websockets (que eu preciso para recarregar a quente)

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);
Anthony De Smet
fonte
1
Este é o único que também lida com soquetes da web.
precisa saber é o seguinte
11

Primeiro instale express e http-proxy-middleware

npm install express http-proxy-middleware --save

Em seguida, no seu 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');
});

Neste exemplo, servimos o site na porta 3000, mas quando uma solicitação termina com / api, a redirecionamos para localhost: 8080.

http: // localhost: 3000 / api / login redirecione para http: // localhost: 8080 / api / login

C. Dupetit
fonte
6

Ok, aqui está uma resposta pronta para copiar e colar usando o módulo require ('request') npm e uma variável de ambiente * em vez de um proxy codificado):

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);
});
coderofsalvation
fonte
2
Em vez de uma declaração de caso, que faz a mesma coisa, exceto usar uma função de solicitação diferente), você pode higienizar primeiro (por exemplo, uma instrução if que chama seu padrão se o método não estiver na lista de métodos aprovados) e, em seguida, basta r = request [method] (/ * o restante * /);
Paul
2

Encontrei uma solução mais curta que faz exatamente o que eu quero https://github.com/http-party/node-http-proxy

Depois de instalar http-proxy

npm install http-proxy --save

Use-o como abaixo em seu 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: ['/']
}));

Eu realmente passei dias procurando em todos os lugares para evitar esse problema, tentei muitas soluções e nenhuma delas funcionou além desta.

Espero que ajude alguém também :)

hzitoun
fonte
0

Não tenho uma amostra expressa, mas uma com http-proxyembalagem simples . Uma versão simplificada do proxy que usei no meu blog.

Em resumo, todos os pacotes proxy proxy nodejs funcionam no nível do protocolo http, não no nível tcp (soquete). Isso também se aplica ao middleware expresso e a todos os expressos. Nenhum deles pode fazer proxy transparente, nem NAT, o que significa manter o IP da origem do tráfego de entrada no pacote enviado ao servidor da web de back-end.

No entanto, o servidor da Web pode coletar o IP original de cabeçalhos http x encaminhados e adicioná-lo ao log.

O recurso xfwd: truein proxyOptionenable x-forward header para 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();

Referência para o cabeçalho X-Forwarded: https://en.wikipedia.org/wiki/X-Forwarded-For

Versão completa do meu proxy: https://github.com/J-Siu/ghost-https-nodejs-proxy

John Siu
fonte