Прокси с помощью express.js

168

Чтобы избежать проблем с AJAX в одном домене, я хочу, чтобы мой веб-сервер node.js /api/BLABLA, например other_domain.com:3000/BLABLA, перенаправлял все запросы с URL-адреса на другой сервер и возвращал пользователю то же, что и этот удаленный сервер, прозрачно.

Все остальные 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
    });
});

но ничего не возвращается на исходный веб-сервер (или конечному пользователю), так что не повезло.

user124114
источник
то, как ты это делаешь, работает на меня, без каких-либо модификаций
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();
});

Обратите внимание: я на самом деле не пробовал вышеизложенное, поэтому оно может содержать ошибки разбора, надеюсь, это даст вам подсказку о том, как заставить его работать.

mekwall
источник
5
Да, некоторые модификации были необходимы, но мне это нравится больше, чем введение дополнительной зависимости модуля Proxy. Немного многословно, но, по крайней мере, я точно знаю, что происходит. Приветствия.
user124114
Похоже, вам нужно выполнить res.writeHead до записи данных, иначе вы получите ошибку (заголовки не могут быть записаны после тела).
Сетек
3
@ user124114 - пожалуйста, поставьте полное решение, которое вы использовали
Михал Цадок
1
кажется, у вас будут проблемы с установкой заголовков таким образом. Cannot render headers after they are sent to the client
Шнд
1
Я обновил ответ на синтаксис es6 и исправил проблему
writeHead
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);
});

Надеюсь, это поможет, мне понадобилось время, чтобы понять, что я могу сделать это :)

trigoman
источник
5
Спасибо, намного проще, чем использовать HTTP-запрос Node.js
Алекс Турпин
17
Еще проще, если вы также передадите запрос: stackoverflow.com/questions/7559862/…
Стефан Хойер
1
Хорошее и чистое решение. Я опубликовал ответ, чтобы он работал и с запросом POST (иначе он не перенаправляет ваше тело сообщения в API). Если вы отредактируете свой ответ, я буду рад удалить мой.
Хенрик Пейнар
Также смотрите этот ответ для улучшения обработки ошибок.
Тамлин
всякий раз, когда я пытаюсь сделать подобную маршрутизацию (или точно такую ​​же), я получаю следующее: stream.js: 94 throw er; // Необработанная ошибка потока в канале. ^ Ошибка: getaddrinfo ENOTFOUND google.com в errnoException (dns.js: 44: 10) в GetAddrInfoReqWrap.onlookup [как oncomplete] (dns.js: 94: 26) есть идеи?
Кейнабель
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
Винот Кумар
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. Спасибо!!
Мариано Дезанце
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) и все остальные запросы на внешний сервер (сервер веб-пакетов на порт 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-proxy-middleware

npm install express http-proxy-middleware --save

Тогда в вашем 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');
});

В этом примере мы обслуживаем сайт через порт 3000, но когда запрос заканчивается на / api, мы перенаправляем его на localhost: 8080.

http: // localhost: 3000 / api / login перенаправить на http: // localhost: 8080 / api / login

C. Дупетит
источник
6

Хорошо, вот готовый к копированию и вставке ответ, использующий модуль require ('request') 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);
});
coderofsalvation
источник
2
Вместо оператора case, который выполняет одно и то же, за исключением использования другой функции запроса), вы можете сначала выполнить санитарную обработку (например, оператор if, который вызывает ваше значение по умолчанию, если метод отсутствует в списке утвержденных методов), а затем просто выполнить r = request [метод] (/ * остальные * /);
Пол
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: ['/']
}));

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

Надеюсь, это поможет кому-то еще :)

hzitoun
источник
0

У меня нет экспресс-образца, но есть простой http-proxyпакет. Очень урезанная версия прокси, которую я использовал для своего блога.

Короче говоря, все http прокси-пакеты nodejs работают на уровне протокола http, а не на уровне tcp (сокета). Это также верно для экспресс и всего промежуточного программного обеспечения. Ни один из них не может использовать прозрачный прокси-сервер или NAT, что означает сохранение IP-адреса источника входящего трафика в пакете, отправляемом на внутренний веб-сервер.

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

Функция заголовка xfwd: truein- proxyOptionx-forward для 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 Header: https://en.wikipedia.org/wiki/X-Forwarded-For

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

Джон Сиу
источник