Проксі з express.js


168

Щоб уникнути проблем з AJAX з одним доменом, я хочу, щоб мій веб-сервер node.js пересилав усі запити з URL /api/BLABLAна інший сервер, наприклад other_domain.com:3000/BLABLA, і повертав користувачеві те саме, що цей віддалений сервер повертав прозоро.

Усі інші URL-адреси (поруч /api/*) надаватимуться безпосередньо, не надаючи проксі.

Як досягти цього за допомогою node.js + express.js? Чи можете ви навести простий приклад коду?

(і веб-сервер, і віддалений 3000сервер знаходяться під моїм контролем, обидва працюють node.js з express.js)


Поки я знайшов це https://github.com/http-party/node-http-proxy , але читання документації там не зробило мене мудрішим. Я закінчила с

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

але нічого не повертається на початковий веб-сервер (або кінцевому користувачеві), тому не пощастить.


так, як ви це робите, для мене працює без будь-яких модифікацій
Saule

1
Хоча трохи пізно, щоб відповісти, але зіткнувся з подібною проблемою і вирішив її, видаливши аналізатор тіла, щоб тіло запиту не було проаналізовано перед тим, як надалі звертатись до нього.
Вивіт

Відповіді:


52

Ви хочете http.requestстворити подібний запит на віддалений API та повернути його відповідь.

Щось на зразок цього:

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

Зауважте: я не дуже намагався сказати вище, тому він може містити помилки розбору, сподіваємось, це дасть вам підказку щодо того, як змусити його працювати.


5
Так, деякі модифікації були необхідні, але мені це подобається краще, ніж введення додаткової нової залежності "проксі" модуля. Трохи багатослівний, але принаймні я точно знаю, що відбувається. Ура.
користувач124114

Здається, що вам потрібно зробити res.writeHead перед тим, як записати посилання даних, інакше ви отримаєте помилку (заголовки не можуть бути записані після тіла).
setec

3
@ user124114 - будь ласка, поставте повне рішення, яке ви використовували
Michal Tsadok

1
Схоже, у вас виникнуть проблеми із встановленням заголовків таким чином. Cannot render headers after they are sent to the client
Шнд

1
Я оновив відповідь на синтаксис es6 і виправив питання writeHead
mekwall

219

запит застарілий станом на лютий 2020 року. Відповідь я залишу нижче з історичних причин, але, будь ласка, подумайте про перехід до альтернативи, зазначеної в цьому випуску .

Архів

Я щось подібне робив, але замість цього використовував запит :

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

Я сподіваюся, що це допомагає, знадобилося мені час, щоб зрозуміти, що я можу це зробити :)


5
Дякую, набагато простіше, ніж використання HTTP-запиту Node.js
Алекс Турпін

17
Ще простіше, якщо ви також подаєте запит: stackoverflow.com/questions/7559862/…
Стефан Хойер

1
Приємне і чисте рішення. Я опублікував відповідь, щоб він також працював із POST-запитом (інакше він не пересилає ваше повідомлення до API). Якщо ви відредагуєте свою відповідь, я би радий видалити мою.
Генрік Пейнар

Також див. Цю відповідь для покращеного поводження з помилками.
Тамлін

кожного разу, коли я намагаюся зробити подібну маршрутизацію (або точно таку ж), я закінчую наступним чином: stream.js: 94 кидаю er; // Неопрацьована помилка потоку в трубі. ^ Помилка: getaddrinfo ENOTFOUND google.com на errnoException (dns.js: 44: 10) на GetAddrInfoReqWrap.onlookup [як oncomplete] (dns.js: 94: 26) якісь ідеї?
keinabel

82

Я знайшов коротше і просте рішення, яке працює безперебійно, а також з аутентифікацією, використовуючи 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
});

А потім просто:

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

Примітка. Як зазначає @MaxPRafferty, використовуйте req.originalUrlзамість baseUrlзбереження рядка запитів:

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

Оновлення: Як згадував Ендрю (спасибі!), Є готове рішення за тим самим принципом:

npm i --save http-proxy-middleware

І потім:

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

Документація: http-proxy-middleware на Github

Я знаю, що я спізнююсь приєднатися до цієї партії, але сподіваюся, що це комусь допоможе.


3
У req.url немає повної URL-адреси, тому оновлено відповідь на використання req.baseUrl замість req.url
Vinoth Kumar

1
Мені також подобається використовувати req.originalUrl замість baseUrl для збереження рядків запитів, але це не завжди може бути бажана поведінка.
MaxPRafferty

@MaxPRafferty - вагомий коментар. Варто зазначити. Дякую.
Егоїст

4
Це найкраще рішення. Я використовую http-proxy-middleware, але це та сама концепція. Не прядіть власне проксі-рішення, коли там вже є чудові.
Андрій

1
@tannerburton дякую! Я оновив відповідь.
Егоїстичний

46

Щоб розширити відповідь тригомана (повна кількість кредитів для нього) для роботи з POST (також можна зробити роботу з PUT тощо):

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
Не вдалося це працювати з PUT. Але чудово працює для GET та POST. Дякую!!
Mariano Desanze

5
@Protron для запитів PUT просто використовуйте щось на кшталтif(req.method === 'PUT'){ r = request.put({uri: url, json: req.body}); }
davnicwil

Якщо вам потрібно пройти через заголовки як частина запиту PUT або POST, видаліть заголовок довжини вмісту, щоб запит міг його обчислити. В іншому випадку сервер прийому може усікати дані, що призведе до помилки.
Карлос Раймер

@Henrik Peinar, чи допоможе це, коли я виконаю запит на вхід у систему та очікую переадресацію з web.com/api/login на web.com/
valik

22

Я використовував наступні настройки, щоб спрямувати все на /restмій сервер заходу (на порт 8080), а також всі інші запити на сервер frontend (сервер вебпакету на порт 3001). Він підтримує всі HTTP-методи, не втрачає жодної метаінформації запиту та підтримує веб-розетки (що мені потрібно для гарячого перезавантаження)

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
Це єдиний, який також займається веб-розетками.
sec0ndHand

11

Спочатку встановіть експрес та http-проксі-посередництво

npm install express http-proxy-middleware --save

Потім у вашому сервері.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');
});

У цьому прикладі ми обслуговуємо сайт на порту 3000, але коли запит закінчується з / api, ми перенаправляємо його на localhost: 8080.

http: // localhost: 3000 / api / login перенаправлення на http: // localhost: 8080 / api / login


6

Гаразд, ось відповідь, готовий до копіювання та вставки, використовуючи npm-модуль вимагати ("запит") та змінну середовища * замість жорсткого коду проксі):

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
Замість того, щоб констатувати випадок, який виконує те ж саме, за винятком використання іншої функції запиту), ви можете спочатку оздоровити (наприклад, оператор if, який викликає за замовчуванням, якщо метод відсутній у списку затверджених методів), а потім просто зробити r = запит [метод] (/ * решта * /);
Пол

2

Я знайшов коротше рішення, яке робить саме те, що я хочу https://github.com/http-party/node-http-proxy

Після установки http-proxy

npm install http-proxy --save

Використовуйте його як нижче у вашому сервері / index / app.js

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

Я дійсно проводив дні, шукаючи всюди, щоб уникнути цього питання, спробував багато рішень, і жодне з них не працювало, окрім цього.

Сподіваюся, що це теж допоможе комусь іншому :)


0

У мене немає експрес-зразка, але один із звичайним http-proxyпакетом. Дуже скорочена версія проксі, яку я використовував у своєму блозі.

Коротше кажучи, всі пакети проксі-серверів nodejs працюють на рівні протоколу http, а не на рівні tcp (socket). Це справедливо і для експрес-програмного забезпечення, і для всіх експрес-програмного забезпечення. Жоден з них не може робити прозорий проксі, ані NAT, що означає збереження IP джерела трафіку в пакеті, що надсилається на сервер веб-сервера.

Однак веб-сервер може забрати оригінальний IP-адресу з заголовків, переданих http x, і додати його до журналу.

The xfwd: trueВ proxyOptionвключити й вперед функцію заголовка для 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();

Довідка для X-Forwarded заголовка: https://en.wikipedia.org/wiki/X-Forwarded-For

Повна версія мого проксі: https://github.com/J-Siu/ghost-https-nodejs-proxy

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.