Proxy avec express.js

168

Pour éviter les problèmes AJAX de même domaine, je veux que mon serveur Web node.js transfère toutes les demandes de l'URL /api/BLABLAvers un autre serveur, par exemple other_domain.com:3000/BLABLA, et renvoie à l'utilisateur la même chose que ce serveur distant a renvoyé, de manière transparente.

Toutes les autres URL (à côté /api/*) doivent être servies directement, pas de proxy.

Comment puis-je y parvenir avec node.js + express.js? Pouvez-vous donner un exemple de code simple?

(le serveur Web et le 3000serveur distant sont sous mon contrôle, tous deux exécutant node.js avec express.js)


Jusqu'à présent, j'ai trouvé cela https://github.com/http-party/node-http-proxy , mais la lecture de la documentation ne m'a pas rendu plus sage. J'ai fini avec

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

mais rien n'est retourné au serveur Web d'origine (ou à l'utilisateur final), donc pas de chance.

user124114
la source
la façon dont vous le faites fonctionne pour moi, sans aucune modification
Saule
1
Bien qu'un peu trop tard pour répondre, il était confronté à un problème similaire et l'a résolu en supprimant l'analyseur de corps afin que le corps de la requête ne soit pas analysé avant de le transmettre davantage.
VyvIT

Réponses:

52

Vous souhaitez utiliser http.requestpour créer une demande similaire à l'API distante et renvoyer sa réponse.

Quelque chose comme ça:

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

Remarque: je n'ai pas vraiment essayé ce qui précède, il peut donc contenir des erreurs d'analyse, j'espère que cela vous donnera un indice sur la façon de le faire fonctionner.

mekwall
la source
5
Oui, certaines modifications ont été nécessaires, mais j'aime mieux cela que l'introduction d'une nouvelle dépendance supplémentaire pour le module "Proxy". Un peu bavard, mais au moins je sais exactement ce qui se passe. À votre santé.
user124114
Il semble que vous deviez faire res.writeHead avant l'écriture de la fracture des données, sinon vous obtiendrez une erreur (les en-têtes ne peuvent pas être écrits après le corps).
setec le
3
@ user124114 - veuillez mettre la solution complète que vous avez utilisée
Michal Tsadok
1
semble que vous aurez des problèmes pour définir les en-têtes de cette façon. Cannot render headers after they are sent to the client
Shnd
1
J'ai mis à jour la réponse à la syntaxe es6 et corrigé le problème
writeHead
219

La demande est obsolète depuis février 2020, je laisserai la réponse ci-dessous pour des raisons historiques, mais veuillez envisager de passer à une alternative répertoriée dans ce numéro .

Archiver

J'ai fait quelque chose de similaire mais j'ai utilisé la requête à la place:

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

J'espère que cela aide, il m'a fallu un certain temps pour réaliser que je pouvais le faire :)

trigoman
la source
5
Merci, beaucoup plus simple que d'utiliser la requête HTTP de Node.js
Alex Turpin
17
Encore plus simple, si vous dirigez également la requête: stackoverflow.com/questions/7559862/…
Stephan Hoyer
1
Solution agréable et propre. J'ai publié une réponse pour que cela fonctionne également avec la requête POST (sinon, le corps de votre message ne sera pas transmis à l'API). Si vous modifiez votre réponse, je serais heureux de supprimer la mienne.
Henrik Peinar
Consultez également cette réponse pour une meilleure gestion des erreurs.
Tamlyn
chaque fois que j'essaie de faire un routage similaire (ou exactement le même), je me retrouve avec ce qui suit: stream.js: 94 throw er; // Erreur de flux non gérée dans le tube. ^ Erreur: getaddrinfo ENOTFOUND google.com à errnoException (dns.js: 44: 10) à GetAddrInfoReqWrap.onlookup [comme oncomplete] (dns.js: 94: 26) des idées?
keinabel
82

J'ai trouvé une solution plus courte et très simple qui fonctionne de manière transparente, et avec l'authentification également, en utilisant 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
});

Et puis simplement:

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

Remarque: comme mentionné par @MaxPRafferty, utilisez req.originalUrlà la place de baseUrlpour conserver la chaîne de requête:

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

Mise à jour: Comme mentionné par Andrew (merci!), Il existe une solution toute faite utilisant le même principe:

npm i --save http-proxy-middleware

Puis:

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

Documentation: http-proxy-middleware sur Github

Je sais que je suis en retard pour rejoindre cette fête, mais j'espère que cela aidera quelqu'un.

Égoïste
la source
3
Le req.url n'a pas l'URL complète, donc mis à jour la réponse pour utiliser req.baseUrl au lieu de req.url
Vinoth Kumar
1
J'aime aussi utiliser req.originalUrl à la place de baseUrl pour préserver les chaînes de requêtes, mais ce n'est pas toujours le comportement souhaité.
MaxPRafferty
@MaxPRafferty - commentaire valide. À noter. Merci.
égoïste du
4
C'est la meilleure solution. J'utilise http-proxy-middleware, mais c'est le même concept. Ne faites pas tourner votre propre solution de proxy alors qu'il y en a déjà d'excellentes.
Andrew
1
@tannerburton merci! J'ai mis à jour la réponse.
égoïste
46

Pour étendre la réponse de trigoman (tous les crédits à lui) pour travailler avec POST (pourrait aussi faire fonctionner avec 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
la source
1
Impossible de le faire fonctionner avec PUT. Mais fonctionne très bien pour GET et POST. Je vous remercie!!
Mariano Desanze
5
@Protron pour les requêtes PUT, utilisez simplement quelque chose commeif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil
Si vous devez passer par des en-têtes dans le cadre d'une requête PUT ou POST, assurez-vous de supprimer l'en-tête content-length afin que la requête puisse le calculer. Sinon, le serveur récepteur pourrait tronquer les données, ce qui entraînerait une erreur.
Carlos Rymer
@Henrik Peinar, cela m'aidera-t-il lorsque je fais une demande de publication de connexion et que je prévois de rediriger de web.com/api/login vers web.com/
valik
22

J'ai utilisé la configuration suivante pour tout diriger /restvers mon serveur principal (sur le port 8080), et toutes les autres demandes vers le serveur frontal (un serveur Webpack sur le port 3001). Il prend en charge toutes les méthodes HTTP, ne perd aucune méta-info de requête et prend en charge les websockets (dont j'ai besoin pour le rechargement à chaud)

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
la source
1
C'est le seul qui traite également des Web-sockets.
sec0ndHand
11

Installez d'abord express et http-proxy-middleware

npm install express http-proxy-middleware --save

Puis dans votre 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');
});

Dans cet exemple, nous servons le site sur le port 3000, mais lorsqu'une requête se termine par / api, nous la redirigeons vers localhost: 8080.

http: // localhost: 3000 / api / login redirection vers http: // localhost: 8080 / api / login

C. Dupetit
la source
6

Ok, voici une réponse prête à copier-coller utilisant le module npm require ('request') et une variable d'environnement * au lieu d'un proxy codé en dur):

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
la source
2
Plutôt qu'une instruction case qui fait toutes la même chose sauf utiliser une fonction de requête différente), vous pouvez d'abord nettoyer (par exemple une instruction if qui appelle votre valeur par défaut si la méthode ne figure pas dans la liste des méthodes approuvées), puis faire simplement r = demande [méthode] (/ * le reste * /);
Paul
2

J'ai trouvé une solution plus courte qui fait exactement ce que je veux https://github.com/http-party/node-http-proxy

Après l'installation http-proxy

npm install http-proxy --save

Utilisez-le comme ci-dessous dans votre serveur / index / app.js

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

J'ai vraiment passé des jours à chercher partout pour éviter ce problème, j'ai essayé de nombreuses solutions et aucune d'entre elles n'a fonctionné à part celle-ci.

J'espère que cela aidera aussi quelqu'un d'autre :)

hzitoun
la source
0

Je n'ai pas d'échantillon express, mais un avec un http-proxyemballage simple . Une version très dépouillée du proxy que j'ai utilisé pour mon blog.

En bref, tous les packages proxy http de nodejs fonctionnent au niveau du protocole http, et non au niveau tcp (socket). Ceci est également vrai pour les middlewares express et tous les middleware express. Aucun d'entre eux ne peut faire de proxy transparent, ni de NAT, ce qui signifie conserver l'adresse IP de la source du trafic entrant dans le paquet envoyé au serveur Web principal.

Cependant, le serveur Web peut récupérer l'adresse IP d'origine des en-têtes http x-forwarded et l'ajouter au journal.

La fonctionnalité d' xfwd: trueen proxyOption-tête in enable x-forward pour 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();

Référence pour l'en-tête X-Forwarded: https://en.wikipedia.org/wiki/X-Forwarded-For

Version complète de mon proxy: https://github.com/J-Siu/ghost-https-nodejs-proxy

John Siu
la source