Proxy z express.js

168

Aby uniknąć problemów z AJAX tej samej domeny, chcę, aby mój serwer sieciowy node.js przekazywał na przykład wszystkie żądania z adresu URL /api/BLABLAdo innego serwera i w sposób przezroczysty zwracał other_domain.com:3000/BLABLAużytkownikowi to samo, co ten zdalny serwer.

Wszystkie inne adresy URL (obok /api/*) mają być obsługiwane bezpośrednio, bez proxy.

Jak to osiągnąć za pomocą node.js + express.js? Czy możesz podać prosty przykład kodu?

(zarówno serwer WWW, jak i serwer zdalny 3000są pod moją kontrolą, oba działają na node.js z express.js)


Do tej pory znalazłem to https://github.com/http-party/node-http-proxy , ale przeczytanie tamtej dokumentacji nie uczyniło mnie mądrzejszym. Skończyło się

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

ale nic nie jest zwracane na oryginalny serwer sieciowy (ani do użytkownika końcowego), więc nie ma szczęścia.

user124114
źródło
sposób, w jaki to robisz, działa dla mnie, bez żadnych modyfikacji
Saule
1
Chociaż trochę za późno, aby odpowiedzieć, napotkałem podobny problem i rozwiązałem go, usuwając parser treści, aby treść żądania nie była analizowana przed dalszym przekazaniem przez proxy.
VyvIT

Odpowiedzi:

52

Chcesz użyć, http.requestaby utworzyć podobne żądanie do zdalnego interfejsu API i zwrócić jego odpowiedź.

Coś takiego:

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

Uwaga: tak naprawdę nie wypróbowałem powyższego, więc może zawierać błędy analizy, mam nadzieję, że da ci to wskazówkę, jak sprawić, by działało.

mekwall
źródło
5
Tak, pewne modyfikacje były konieczne, ale podoba mi się to bardziej niż wprowadzenie dodatkowej zależności od modułu „Proxy”. Trochę gadatliwy, ale przynajmniej wiem dokładnie, co się dzieje. Twoje zdrowie.
user124114
Wygląda na to, że musisz wykonać res.writeHead przed zapisaniem luki danych, w przeciwnym razie pojawi się błąd (nagłówki nie mogą być zapisane po treści).
setec
3
@ user124114 - podaj pełne rozwiązanie, z którego korzystałeś
Michal Tsadok
1
wygląda na to, że będziesz mieć problem z ustawieniem nagłówków w ten sposób. Cannot render headers after they are sent to the client
Shnd,
1
Zaktualizowałem odpowiedź do składni es6 i naprawiłem problem z writeHead
mekwall,
219

Żądanie zostało wycofane w lutym 2020 r. Zostawię odpowiedź poniżej ze względów historycznych, ale rozważ przejście na alternatywę wymienioną w tym numerze .

Archiwum

Zrobiłem coś podobnego, ale zamiast tego użyłem 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);
});

Mam nadzieję, że to pomoże, chwilę zajęło mi uświadomienie sobie, że mogę to zrobić :)

trygoman
źródło
5
Dzięki, o wiele prostsze niż użycie żądania HTTP Node.js
Alex Turpin,
17
Jeszcze prościej, jeśli również wyślesz żądanie: stackoverflow.com/questions/7559862/…
Stephan Hoyer
1
Ładne i czyste rozwiązanie. Wysłałem odpowiedź, aby działała również z żądaniem POST (w przeciwnym razie nie przekazuje treści wiadomości do API). Jeśli edytujesz swoją odpowiedź, z przyjemnością usunę moją.
Henrik Peinar
Zobacz również tę odpowiedź, aby uzyskać lepszą obsługę błędów.
Tamlyn
ilekroć próbuję wykonać podobny routing (lub dokładnie to samo), otrzymuję następujący wynik: stream.js: 94 throw er; // Nieobsługiwany błąd strumienia w potoku. ^ Błąd: getaddrinfo ENOTFOUND google.com w errnoException (dns.js: 44: 10) w GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js: 94: 26) jakieś pomysły?
keinabel
82

Znalazłem krótsze i bardzo proste rozwiązanie, które działa bezproblemowo, a także z uwierzytelnianiem, używając 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
});

A potem po prostu:

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

Uwaga: jak wspomniano w @MaxPRafferty, użyj req.originalUrlzamiast, baseUrlaby zachować querystring:

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

Aktualizacja: Jak wspomniał Andrew (dziękuję!), Istnieje gotowe rozwiązanie działające na tej samej zasadzie:

npm i --save http-proxy-middleware

I wtedy:

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

Dokumentacja: http-proxy-middleware na Github

Wiem, że spóźniłem się na przyjęcie, ale mam nadzieję, że to komuś pomoże.

Samolubny
źródło
3
Req.url nie posiada pełnego adresu URL, więc zaktualizowane odpowiedź do korzystania req.baseUrl zamiast req.url
VINOTH Kumar
1
Lubię też używać req.originalUrl zamiast baseUrl, aby zachować querystring, ale nie zawsze jest to pożądane zachowanie.
MaxPRafferty,
@MaxPRafferty - komentarz vaid. Warto zwrócić uwagę. Dzięki.
Selfish
4
To najlepsze rozwiązanie. Używam oprogramowania pośredniczącego http-proxy, ale to ta sama koncepcja. Nie twórz własnego rozwiązania proxy, gdy są już świetne.
Andrew,
1
@tannerburton thanks! Zaktualizowałem odpowiedź.
Samolubny
46

Aby rozszerzyć odpowiedź trygomana (pełne kredyty dla niego) do pracy z POST (może również pracować z PUT itp.):

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
źródło
1
Nie mogłem sprawić, by działało z PUT. Ale działa świetnie w przypadku GET i POST. Dziękuję Ci!!
Mariano Desanze
5
@Protron dla żądań PUT po prostu użyj czegoś takiegoif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil
Jeśli musisz przejść przez nagłówki jako część żądania PUT lub POST, pamiętaj o usunięciu nagłówka content-length, aby żądanie mogło go obliczyć. W przeciwnym razie serwer odbierający może obciąć dane, co doprowadzi do błędu.
Carlos Rymer
@Henrik Peinar, czy to pomoże, gdy wyślę prośbę o wpis logowania i spodziewam się przekierowania z web.com/api/login na web.com/
valik
22

Użyłem następującej konfiguracji, aby skierować wszystko na /restmój serwer zaplecza (na porcie 8080), a wszystkie inne żądania do serwera frontendu (serwer webpacka na porcie 3001). Obsługuje wszystkie metody HTTP, nie traci żadnych meta-informacji żądania i obsługuje gniazda sieciowe (których potrzebuję do ponownego ładowania na gorąco)

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
źródło
1
Jest to jedyny, który zajmuje się również gniazdami internetowymi.
sec0ndHand
11

Najpierw zainstaluj oprogramowanie pośrednie express i http-proxy-middleware

npm install express http-proxy-middleware --save

Następnie w pliku 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');
});

W tym przykładzie obsługujemy witrynę na porcie 3000, ale gdy żądanie kończy się na / api, przekierowujemy je na localhost: 8080.

http: // localhost: 3000 / api / login przekierowanie do http: // localhost: 8080 / api / login

C. Dupetit
źródło
6

OK, oto odpowiedź gotowa do skopiowania i wklejenia przy użyciu modułu require ('request') npm i zmiennej środowiskowej * zamiast zakodowanego na stałe serwera proxy):

skrypt kawowy

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
źródło
2
Zamiast instrukcji case, z których wszystkie robią to samo, z wyjątkiem użycia innej funkcji żądania), możesz najpierw wyczyścić (np. Instrukcję if, która wywołuje twoją domyślną, jeśli metoda nie znajduje się na liście zatwierdzonych metod), a następnie po prostu wykonaj r = request [metoda] (/ * reszta * /);
Paul
2

Znalazłem krótsze rozwiązanie, które robi dokładnie to, czego chcę https://github.com/http-party/node-http-proxy

Po zainstalowaniu http-proxy

npm install http-proxy --save

Użyj go jak poniżej w swoim 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: ['/']
}));

Naprawdę spędziłem dni szukając wszędzie, aby uniknąć tego problemu, wypróbowałem wiele rozwiązań i żadne z nich nie zadziałało, ale to jedno.

Mam nadzieję, że to pomoże też komuś innemu :)

hzitoun
źródło
0

Nie mam próbki ekspresowej, ale taką ze zwykłym http-proxyopakowaniem. Bardzo uproszczona wersja proxy, którego użyłem na swoim blogu.

Krótko mówiąc, wszystkie pakiety proxy http nodejs działają na poziomie protokołu http, a nie tcp (gniazda). Dotyczy to również Express i wszystkich Express Middleware. Żaden z nich nie może wykonać przezroczystego proxy ani NAT, co oznacza przechowywanie adresu IP źródła ruchu przychodzącego w pakiecie wysyłanym do serwera WWW zaplecza.

Jednak serwer sieciowy może pobrać oryginalny adres IP z nagłówków http x-forwarded i dodać go do dziennika.

xfwd: trueW proxyOptionwłączyć funkcję nagłówka X-forward dla 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();

Odniesienie do nagłówka X-Forwarded: https://en.wikipedia.org/wiki/X-Forwarded-For

Pełna wersja mojego proxy: https://github.com/J-Siu/ghost-https-nodejs-proxy

John Siu
źródło