Proxy con express.js


168

Per evitare problemi con lo stesso dominio AJAX, desidero che il mio server web node.js inoltri tutte le richieste dall'URL /api/BLABLAa un altro server, ad esempio other_domain.com:3000/BLABLA, e ritorni all'utente la stessa cosa che questo server remoto ha restituito, in modo trasparente.

Tutti gli altri URL (accanto /api/*) devono essere offerti direttamente, senza proxy.

Come posso ottenere questo risultato con node.js + express.js? Puoi fare un semplice esempio di codice?

(sia il server Web che il 3000server remoto sono sotto il mio controllo, entrambi eseguono node.js con express.js)


Finora ho trovato questo https://github.com/http-party/node-http-proxy , ma leggere la documentazione non mi ha reso più saggio. Ho finito 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
    });
});

ma nulla viene restituito al server Web originale (o all'utente finale), quindi nessuna fortuna.


il modo in cui lo fai funziona per me, senza alcuna modifica
— Saule

1
Anche se un po 'troppo tardi per rispondere, ma stava affrontando un problema simile e lo ha risolto rimuovendo il parser del corpo in modo che il corpo della richiesta non venga analizzato prima di inviarlo ulteriormente.
— VyvIT,

Risposte:


52

Si desidera utilizzare http.requestper creare una richiesta simile all'API remota e restituire la sua risposta.

Qualcosa come questo:

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

Avviso: non ho davvero provato quanto sopra, quindi potrebbe contenere errori di analisi, spero che questo ti darà un suggerimento su come farlo funzionare.


5
Sì, alcune modifiche erano necessarie, ma mi piace di più che introdurre una nuova dipendenza del modulo "Proxy". Un po 'prolisso, ma almeno so esattamente cosa sta succedendo. Saluti.
— user124114,

Sembra che tu debba fare res.writeHead prima che venga scritto il chink dei dati, altrimenti otterrai un errore (le intestazioni non possono essere scritte dopo il corpo).
— setec,

3
@ user124114 - inserisci la soluzione completa che hai utilizzato
— Michal Tsadok,

1
sembra che avrai problemi a impostare le intestazioni in questo modo. Cannot render headers after they are sent to the client
— Shnd

1
Ho aggiornato la risposta alla sintassi es6 e risolto il problema
— writeHead

219

la richiesta è stata ritirata dal febbraio 2020, lascerò la risposta di seguito per motivi storici, ma ti preghiamo di considerare di passare a un'alternativa elencata in questo numero .

Archivio

Ho fatto qualcosa di simile ma ho usato invece la richiesta :

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

Spero che questo aiuti, mi ci è voluto un po 'per capire che avrei potuto farlo :)


5
Grazie, molto più semplice rispetto all'utilizzo della richiesta HTTP di Node.js
— Alex Turpin,

17
Ancora più semplice, se convogli anche la richiesta: stackoverflow.com/questions/7559862/…
— Stephan Hoyer,

1
Soluzione piacevole e pulita. Ho pubblicato una risposta per farlo funzionare anche con la richiesta POST (altrimenti non inoltra il tuo corpo post all'API). Se modifichi la tua risposta sarei felice di rimuovere la mia.
— Henrik Peinar,

Vedi anche questa risposta per una migliore gestione degli errori.
— Tamlyn,

ogni volta che provo a fare un routing simile (o esattamente lo stesso) finisco con il seguente: stream.js: 94 throw er; // Errore flusso non gestito nella pipe. ^ Errore: getaddrinfo ENOTFOUND google.com su errnoException (dns.js: 44: 10) su GetAddrInfoReqWrap.onlookup [come incompleto] (dns.js: 94: 26) qualche idea?
— keinabel,

82

Ho trovato una soluzione più breve e molto semplice che funziona senza problemi e anche con l'autenticazione, 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 poi semplicemente:

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

Nota: come indicato da @MaxPRafferty, utilizzare req.originalUrlal posto di baseUrlper preservare la stringa di querys :

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

Aggiornamento: come menzionato da Andrew (grazie!), Esiste una soluzione pronta che utilizza lo stesso principio:

npm i --save http-proxy-middleware

E poi:

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

Documentazione: http-proxy-middleware su Github

So che sono in ritardo per unirmi a questa festa, ma spero che questo aiuti qualcuno.


3
Req.url non ha l'URL completo, quindi ho aggiornato la risposta per utilizzare req.baseUrl invece di req.url
— Vinoth Kumar il

1
Mi piace anche usare req.originalUrl al posto di baseUrl per preservare le stringhe di query, ma questo potrebbe non essere sempre il comportamento desiderato.
— MaxPRafferty

@MaxPRafferty - vaid comment. Vale la pena notare. Grazie.
— Egoista

4
Questa è la soluzione migliore Sto usando http-proxy-middleware, ma è lo stesso concetto. Non girare la tua soluzione proxy quando ce ne sono già di fantastici là fuori.
— Andrew,

1
@tannerburton grazie! Ho aggiornato la risposta.
— Egoista il

46

Per estendere la risposta del trigoman (crediti a lui pieni) al lavoro con POST (potrebbe anche funzionare con PUT ecc.)

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
Impossibile farlo funzionare con PUT. Ma funziona benissimo per GET e POST. Grazie!!
— Mariano Desanze,

5
@Protron per richieste PUT basta usare qualcosa del genereif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
— davnicwil,

Se è necessario passare attraverso le intestazioni come parte di una richiesta PUT o POST, assicurarsi di eliminare l'intestazione della lunghezza del contenuto in modo che la richiesta possa calcolarla. Altrimenti, il server ricevente potrebbe troncare i dati, il che provocherà un errore.
— Carlos Rymer,

@Henrik Peinar, sarà di aiuto quando faccio una richiesta di post di accesso e mi aspetto di reindirizzare da web.com/api/login a web.com/
— valik

22

Ho usato la seguente configurazione per indirizzare tutto sul /restmio server back-end (sulla porta 8080) e tutte le altre richieste al server front-end (un server webpack sulla porta 3001). Supporta tutti i metodi HTTP, non perde alcuna meta-informazione richiesta e supporta i websocket (di cui ho bisogno per ricaricare a caldo)

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
Questo è l'unico che si occupa anche dei socket web.
— sec0ndHand

11

Prima installa express e http-proxy-middleware

npm install express http-proxy-middleware --save

Quindi nel tuo 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 questo esempio serviamo il sito sulla porta 3000, ma quando una richiesta termina con / api lo reindirizziamo a localhost: 8080.

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


6

Ok, ecco una risposta pronta per essere incollata usando il modulo npm request ('request') e una variabile d'ambiente * invece di un proxy hardcoded):

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

2
Piuttosto che un'istruzione case che fa tutte la stessa cosa tranne che utilizzare una funzione di richiesta diversa) è possibile disinfettare prima (ad esempio un'istruzione if che chiama l'impostazione predefinita se il metodo non è nell'elenco dei metodi approvati), quindi r = request [metodo] (/ * il resto * /);
— Paul,

2

Ho trovato una soluzione più breve che fa esattamente quello che voglio https://github.com/http-party/node-http-proxy

Dopo l'installazione http-proxy

npm install http-proxy --save

Usalo come di seguito nel tuo 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: ['/']
}));

Ho davvero passato giorni a cercare ovunque per evitare questo problema, ho provato molte soluzioni e nessuna di esse ha funzionato tranne questa.

Spero che possa aiutare anche qualcun altro :)


0

Non ho un campione espresso, ma uno con un http-proxypacchetto semplice . Una versione molto ridotta del proxy che ho usato per il mio blog.

In breve, tutti i pacchetti proxy http nodejs funzionano a livello di protocollo http, non a livello di tcp (socket). Questo vale anche per il middleware express e all express. Nessuno di loro può fare proxy trasparente, né NAT, il che significa mantenere l'IP di origine del traffico in entrata nel pacchetto inviato al server Web back-end.

Tuttavia, il server Web può prelevare l'IP originale dalle intestazioni http x-forwarded e aggiungerlo nel registro.

La funzione xfwd: truedi proxyOptionattivazione dell'intestazione x-forward in per 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();

Riferimento per X-Forwarded Header: https://en.wikipedia.org/wiki/X-Forwarded-For

Versione completa del mio proxy: https://github.com/J-Siu/ghost-https-nodejs-proxy

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.