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.


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.


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ć :)


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.


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

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

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


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

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 :)


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.