Proxy mit express.js

168

Um AJAX-Probleme mit derselben Domäne zu vermeiden, möchte ich, dass mein node.js-Webserver beispielsweise alle Anforderungen von der URL /api/BLABLAan einen anderen Server weiterleitet other_domain.com:3000/BLABLAund dem Benutzer das gleiche zurückgibt, das dieser Remote-Server transparent zurückgegeben hat.

Alle anderen URLs (neben /api/*) sollen direkt ohne Proxy bereitgestellt werden.

Wie erreiche ich das mit node.js + express.js? Können Sie ein einfaches Codebeispiel geben?

(Sowohl der Webserver als auch der Remote- 3000Server stehen unter meiner Kontrolle. Beide führen node.js mit express.js aus.)


Bisher habe ich diese https://github.com/http-party/node-http-proxy gefunden , aber das Lesen der Dokumentation dort hat mich nicht klüger gemacht. Ich endete mit

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

Es wird jedoch nichts an den ursprünglichen Webserver (oder an den Endbenutzer) zurückgegeben, also kein Glück.

user124114
quelle
Die Art und Weise, wie Sie es tun, funktioniert für mich ohne Änderungen
Saule
1
Es ist zwar etwas zu spät, um zu antworten, aber es gab ein ähnliches Problem und es wurde behoben, indem der Body-Parser entfernt wurde, sodass der Anforderungs-Body nicht analysiert wird, bevor er weiter weitergeleitet wird.
VyvIT

Antworten:

52

Sie möchten http.requesteine ähnliche Anforderung für die Remote-API erstellen und deren Antwort zurückgeben.

Etwas wie das:

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

Hinweis: Ich habe das oben Genannte nicht wirklich ausprobiert, daher kann es zu Analysefehlern kommen, die Ihnen hoffentlich einen Hinweis geben, wie Sie es zum Laufen bringen können.

Mekwall
quelle
5
Ja, einige Änderungen waren notwendig, aber das gefällt mir besser, als eine zusätzliche neue "Proxy" -Modulabhängigkeit einzuführen. Ein bisschen ausführlich, aber zumindest weiß ich genau, was los ist. Prost.
user124114
Es scheint, als müssten Sie res.writeHead ausführen, bevor Daten-Chink geschrieben wird. Andernfalls wird eine Fehlermeldung angezeigt (Header können nicht nach body geschrieben werden).
Setec
3
@ user124114 - Bitte geben Sie die vollständige Lösung, die Sie verwendet haben
Michal Tsadok
1
Anscheinend haben Sie Probleme beim Festlegen von Headern auf diese Weise. Cannot render headers after they are sent to the client
Shnd
1
Ich habe die Antwort auf es6-Syntax aktualisiert und das Problem
writeHead
219

Die Anfrage wurde ab Februar 2020 abgelehnt. Ich werde die Antwort aus historischen Gründen unten belassen. Bitte ziehen Sie jedoch in Betracht, zu einer in dieser Ausgabe aufgeführten Alternative zu wechseln .

Archiv

Ich habe etwas Ähnliches gemacht, aber stattdessen die Anfrage verwendet:

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

Ich hoffe das hilft, es hat eine Weile gedauert, bis mir klar wurde, dass ich das schaffen kann :)

Trigoman
quelle
5
Vielen Dank, viel einfacher als die HTTP-Anfrage von Node.js zu verwenden
Alex Turpin
17
Noch einfacher, wenn Sie auch die Anfrage weiterleiten
Stephan Hoyer
1
Schöne und saubere Lösung. Ich habe eine Antwort gepostet, damit es auch mit POST-Anfragen funktioniert (andernfalls wird Ihr Post-Body nicht an die API weitergeleitet). Wenn Sie Ihre Antwort bearbeiten, würde ich gerne meine entfernen.
Henrik Peinar
Siehe auch diese Antwort für eine verbesserte Fehlerbehandlung.
Tamlyn
Wenn ich versuche, ein ähnliches Routing (oder genau das gleiche) durchzuführen, erhalte ich Folgendes: stream.js: 94 throw er; // Nicht behandelter Stream-Fehler in der Pipe. ^ Fehler: getaddrinfo ENOTFOUND google.com bei errnoException (dns.js: 44: 10) bei GetAddrInfoReqWrap.onlookup [als oncomplete] (dns.js: 94: 26) irgendwelche Ideen?
keinabel
82

Ich fand eine kürzere und sehr einfache Lösung , die auch nahtlos und mit Authentifizierung arbeitet, mit 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
});

Und dann einfach:

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

Hinweis: Wie von @MaxPRafferty erwähnt, verwenden Sie req.originalUrlanstelle von baseUrl, um den Querystring zu erhalten:

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

Update: Wie von Andrew erwähnt (danke!), Gibt es eine fertige Lösung nach dem gleichen Prinzip:

npm i --save http-proxy-middleware

Und dann:

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

Dokumentation: http-proxy-middleware auf Github

Ich weiß, dass ich zu spät bin, um an dieser Party teilzunehmen, aber ich hoffe, das hilft jemandem.

Egoistisch
quelle
3
Die req.url hat nicht die vollständige URL, daher wurde die Antwort aktualisiert, um req.baseUrl anstelle von req.url zu verwenden
Vinoth Kumar
1
Ich verwende auch gerne req.originalUrl anstelle von baseUrl, um Querystringe beizubehalten, aber dies ist möglicherweise nicht immer das gewünschte Verhalten.
MaxPRafferty
@ MaxPRafferty - vaid Kommentar. Nichts wert. Vielen Dank.
Egoistisch
4
Dies ist die beste Lösung. Ich verwende http-Proxy-Middleware, aber es ist das gleiche Konzept. Drehen Sie keine eigene Proxy-Lösung, wenn es bereits großartige gibt.
Andrew
1
@ Tannerburton danke! Ich habe die Antwort aktualisiert.
Egoistisch
46

Um die Antwort von Trigoman (volle Credits an ihn) auf die Arbeit mit POST auszudehnen (könnte auch die Arbeit mit PUT usw. machen):

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
quelle
1
Mit PUT konnte es nicht funktionieren. Funktioniert aber hervorragend für GET und POST. Danke dir!!
Mariano Desanze
5
@Protron für PUT-Anfragen verwenden Sie einfach so etwas wieif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil
Wenn Sie Header als Teil einer PUT- oder POST-Anforderung durchlaufen müssen, müssen Sie den Header mit Inhaltslänge löschen, damit die Anforderung ihn berechnen kann. Andernfalls könnte der empfangende Server die Daten abschneiden, was zu einem Fehler führt.
Carlos Rymer
@ Henrik Peinar, wird dies helfen, wenn ich eine Login-Post-Anfrage mache und erwarte, von web.com/api/login zu web.com/ umzuleiten
valik
22

Ich habe das folgende Setup verwendet, um alles an /restmeinen Backend-Server (an Port 8080) und alle anderen Anforderungen an den Frontend-Server (einen Webpack-Server an Port 3001) weiterzuleiten. Es unterstützt alle HTTP-Methoden, verliert keine Anforderungs-Metainformationen und unterstützt Websockets (die ich zum Hot-Reloading benötige).

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
quelle
1
Dies ist die einzige, die sich auch mit Web-Sockets befasst.
sec0ndHand
11

Installieren Sie zuerst Express und http-Proxy-Middleware

npm install express http-proxy-middleware --save

Dann in Ihrer 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 diesem Beispiel wird die Site auf Port 3000 bereitgestellt. Wenn eine Anforderung jedoch mit / api endet, leiten wir sie an localhost um: 8080.

http: // localhost: 3000 / api / login Weiterleiten an http: // localhost: 8080 / api / login

C. Dupetit
quelle
6

Ok, hier ist eine Antwort zum Kopieren und Einfügen mit dem erforderlichen npm-Modul ('request') und einer Umgebungsvariablen * anstelle eines fest codierten Proxys:

Kaffeeskript

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
quelle
2
Anstelle einer case-Anweisung, die alle dasselbe tun, außer eine andere Anforderungsfunktion verwenden, können Sie zuerst bereinigen (z. B. eine if-Anweisung, die Ihre Standardeinstellung aufruft, wenn die Methode nicht in der Liste der genehmigten Methoden enthalten ist), und dann einfach tun r = Anfrage [Methode] (/ * der Rest * /);
Paul
2

Ich habe eine kürzere Lösung gefunden, die genau das tut, was ich will: https://github.com/http-party/node-http-proxy

Nach der Installation http-proxy

npm install http-proxy --save

Verwenden Sie es wie unten in Ihrem 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: ['/']
}));

Ich habe wirklich tagelang überall gesucht, um dieses Problem zu vermeiden, habe viele Lösungen ausprobiert und keine davon hat funktioniert, außer dieser.

Hoffe, es wird auch jemand anderem helfen :)

hzitoun
quelle
0

Ich habe kein Expressmuster, aber eines mit einfachem http-proxyPaket. Eine sehr abgespeckte Version des Proxys, den ich für mein Blog verwendet habe.

Kurz gesagt, alle http-Proxy-Pakete von nodejs arbeiten auf der http-Protokollebene und nicht auf der TCP-Ebene (Socket). Dies gilt auch für Express- und alle Express-Middleware. Keiner von ihnen kann einen transparenten Proxy oder NAT ausführen, was bedeutet, dass die IP-Adresse des eingehenden Datenverkehrs in dem an den Backend-Webserver gesendeten Paket erhalten bleibt.

Der Webserver kann jedoch die ursprüngliche IP-Adresse von HTTP-X-Forwarded-Headern abrufen und dem Protokoll hinzufügen.

Die Funktion xfwd: truezum proxyOptionAktivieren des X-Forward-Headers für 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();

Referenz für X-Forwarded Header: https://en.wikipedia.org/wiki/X-Forwarded-For

Vollversion meines Proxys: https://github.com/J-Siu/ghost-https-nodejs-proxy

John Siu
quelle