Proxy con express.js

168

Para evitar problemas de AJAX en el mismo dominio, quiero que mi servidor web node.js reenvíe todas las solicitudes de URL /api/BLABLAa otro servidor, por ejemplo other_domain.com:3000/BLABLA, y devuelva al usuario lo mismo que devolvió este servidor remoto, de forma transparente.

Todas las demás URL (al lado /api/*) se deben servir directamente, sin proxy.

¿Cómo logro esto con node.js + express.js? ¿Puedes dar un ejemplo de código simple?

(tanto el servidor web como el 3000servidor remoto están bajo mi control, ambos ejecutan node.js con express.js)


Hasta ahora encontré este https://github.com/http-party/node-http-proxy , pero leer la documentación allí no me hizo más sabio. Terminé con

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

pero no se devuelve nada al servidor web original (o al usuario final), así que no tuvimos suerte.

usuario124114
fuente
la forma en que lo haces funciona para mí, sin modificaciones
Saule
1
Aunque fue un poco tarde para responder, estaba enfrentando un problema similar y lo resolvió eliminando el analizador del cuerpo para que el cuerpo de la solicitud no se analice antes de enviarlo por correo electrónico.
VyvIT

Respuestas:

52

Quieres usar http.request para crear una solicitud similar a la API remota y devolver su respuesta.

Algo como esto:

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: Realmente no he probado lo anterior, por lo que podría contener errores de análisis, con suerte, esto le dará una pista sobre cómo hacerlo funcionar.

Mekwall
fuente
55
Sí, algunas modificaciones fueron necesarias, pero me gusta más que introducir una nueva dependencia adicional del módulo "Proxy". Un poco detallado, pero al menos sé exactamente lo que está sucediendo. Salud.
user124114
Parece que necesita hacer res.writeHead antes de escribir los datos, de lo contrario obtendrá un error (los encabezados no se pueden escribir después del cuerpo).
setec
3
@ user124114 - por favor ponga la solución completa que ha utilizado
Michal Tsadok
1
parece que tendrás problemas para configurar los encabezados de esta manera. Cannot render headers after they are sent to the client
Shnd
1
He actualizado la respuesta a la sintaxis de es6 y solucioné el problema de
writeHead
219

la solicitud ha quedado en desuso a partir de febrero de 2020, dejaré la respuesta a continuación por razones históricas, pero considere pasar a una alternativa que figura en este número .

Archivo

Hice algo similar pero usé request en su lugar:

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 esto ayude, me tomó un tiempo darme cuenta de que podía hacer esto :)

trigoman
fuente
55
Gracias, mucho más simple que usar la solicitud HTTP de Node.js
Alex Turpin
17
Aún más simple, si también canaliza la solicitud: stackoverflow.com/questions/7559862/…
Stephan Hoyer
1
Solución agradable y limpia. Publiqué una respuesta para que también funcione con la solicitud POST (de lo contrario, no reenvía su cuerpo de publicación a la API). Si edita su respuesta, me complacería eliminar la mía.
Henrik Peinar
Consulte también esta respuesta para mejorar el manejo de errores.
Tamlyn
cada vez que intento hacer un enrutamiento similar (o exactamente el mismo) termino con lo siguiente: stream.js: 94 throw er; // Error de flujo no controlado en la tubería. ^ Error: getaddrinfo ENOTFOUND google.com en errnoException (dns.js: 44: 10) en GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js: 94: 26) ¿alguna idea?
keinabel
82

Encontré una solución más corta y sencilla que funciona a la perfección, y también con autenticación, 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
});

Y luego simplemente:

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

Nota: como lo menciona @MaxPRafferty, use req.originalUrlen lugar de baseUrlpara preservar la cadena de consulta:

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

Actualización: como mencionó Andrew (¡gracias!), Hay una solución preparada que utiliza el mismo principio:

npm i --save http-proxy-middleware

Y entonces:

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

Documentación: http-proxy-middleware en Github

Sé que llego tarde a unirme a esta fiesta, pero espero que esto ayude a alguien.

Egoísta
fuente
3
Req.url no tiene la url completa, así que actualicé la respuesta para usar req.baseUrl en lugar de req.url
Vinoth Kumar
1
También me gusta usar req.originalUrl en lugar de baseUrl para preservar las cadenas de consulta, pero este no siempre es el comportamiento deseado.
MaxPRafferty
@MaxPRafferty - comentario vano. Vale la pena señalar. Gracias.
Egoísta
44
Esta es la mejor solución. Estoy usando http-proxy-middleware, pero es el mismo concepto. No haga girar su propia solución de proxy cuando ya hay excelentes.
Andrew
1
@tannerburton gracias! Actualicé la respuesta.
Egoísta
46

Para extender la respuesta de trigoman (créditos completos para él) para trabajar con POST (también podría hacer que funcione con 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
fuente
1
No se pudo hacer que funcione con PUT. Pero funciona muy bien para GET y POST. ¡¡Gracias!!
Mariano Desanze
55
@Protron para solicitudes PUT solo usa algo comoif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil
Si necesita pasar por encabezados como parte de una solicitud PUT o POST, asegúrese de eliminar el encabezado de longitud de contenido para que la solicitud pueda calcularlo. De lo contrario, el servidor receptor podría truncar los datos, lo que provocará un error.
Carlos Rymer
@Henrik Peinar, esto ayudará cuando haga una solicitud de inicio de sesión y espere redirigir desde web.com/api/login a web.com/
valik
22

Utilicé la siguiente configuración para dirigir todo /resta mi servidor back-end (en el puerto 8080), y todas las demás solicitudes al servidor frontend (un servidor webpack en el puerto 3001). Admite todos los métodos HTTP, no pierde ninguna metainformación de solicitud y admite sockets web (que necesito para la recarga en caliente)

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
fuente
1
Este es el único que también se ocupa de los sockets web.
sec0ndHand
11

Primero instale express y http-proxy-middleware

npm install express http-proxy-middleware --save

Luego en su 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');
});

En este ejemplo, servimos el sitio en el puerto 3000, pero cuando una solicitud finaliza con / api, lo redirigimos a localhost: 8080.

http: // localhost: 3000 / api / login redirige a http: // localhost: 8080 / api / login

C. Dupetit
fuente
6

Ok, aquí hay una respuesta lista para copiar y pegar usando el módulo require ('request') npm y una variable de entorno * en lugar de un 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);
});
coderofsalvación
fuente
2
En lugar de una declaración de caso, todos los cuales hacen lo mismo, excepto que usan una función de solicitud diferente), primero puede desinfectar (por ejemplo, una declaración if que llame a su valor predeterminado si el método no está en la lista de métodos aprobados), y luego simplemente haga r = solicitud [método] (/ * el resto * /);
Paul
2

Encontré una solución más corta que hace exactamente lo que quiero https://github.com/http-party/node-http-proxy

Después de instalar http-proxy

npm install http-proxy --save

Úselo como a continuación en su servidor / index / app.js

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

Realmente he pasado días buscando en todas partes para evitar este problema, probé muchas soluciones y ninguna funcionó excepto esta.

Espero que ayude a alguien más también :)

hzitoun
fuente
0

No tengo una muestra exprés, pero una simple http-proxy paquete . Una versión muy simple del proxy que utilicé para mi blog.

En resumen, todos los paquetes proxy proxy de nodejs funcionan en el nivel de protocolo http, no en el nivel tcp (socket). Esto también es cierto para express y todos los middleware express. Ninguno de ellos puede hacer proxy transparente, ni NAT, lo que significa mantener la IP de la fuente de tráfico entrante en el paquete enviado al servidor web de fondo.

Sin embargo, el servidor web puede recoger la IP original de los encabezados http x-reenviados y agregarla al registro.

La función xfwd: truede proxyOptionactivación de encabezado x-forward 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();

Referencia para el encabezado X-Fordered: https://en.wikipedia.org/wiki/X-Forwards-For

Versión completa de mi proxy: https://github.com/J-Siu/ghost-https-nodejs-proxy

John Siu
fuente