Http-прокси Node.js отбрасывает запросы websocket

Хорошо, я потратил больше недели, пытаясь понять это безрезультатно, поэтому, если кто-то знает, вы герой. На этот вопрос будет нелегко ответить, если только я не чокнутый.

Я использую node-http-proxy для прокси липких сессий 16 работникам node.js, работающим на разных портах.

Я использую Socket.IO Web Sockets для обработки множества различных типов запросов, а также использую традиционные запросы.

Когда я переключил свой сервер на прокси через node-http-proxy, возникла новая проблема: иногда мой сеанс Socket.IO не может установить соединение.

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

Если я перезагружаю браузер пользователя, он может иногда подключаться, а иногда нет.

Липкие Сессии

Я должен прокси-сеанс липких сессий, так как мое приложение проходит проверку подлинности для каждого сотрудника, и поэтому он направляет запрос на основе своего файла cookie Connect.SID (я использую Connect / Express).

Хорошо, немного кода

Это мой файл proxy.js, который запускается в узле и маршрутизирует каждого из рабочих:

var http = require('http');
var httpProxy = require('http-proxy');

// What ports the proxy is routing to.
var data = {
  proxyPort: 8888,
  currentPort: 8850,
  portStart: 8850,
  portEnd: 8865,
};

// Just gives the next port number.
nextPort = function() {
  var next = data.currentPort++;
  next = (next > data.portEnd) ? data.portStart : next;
  data.currentPort = next;
  return data.currentPort;
};

// A hash of Connect.SIDs for sticky sessions.
data.routes = {}

var svr = httpProxy.createServer(function (req, res, proxy) {

  var port = false;

  // parseCookies is just a little function
  // that... parses cookies.
  var cookies = parseCookies(req);  

  // If there is an SID passed from the browser.
  if (cookies['connect.sid'] !== undefined) {

    var ip = req.connection.remoteAddress;

    if (data.routes[cookies['connect.sid']] !== undefined) {

      // If there is already a route assigned to this SID,
      // make that route's port the assigned port.
      port = data.routes[cookies['connect.sid']].port;
    } else {

      // If there isn't a route for this SID,
      // create the route object and log its
      // assigned port.
      port = data.currentPort;
      data.routes[cookies['connect.sid']] = {
        port: port,
      }

      nextPort();
    }

  } else {

    // Otherwise assign a random port, it will/
    // pick up a connect SID on the next go.
    // This doesn't really happen.
    port = nextPort();
  }

  // Now that we have the chosen port, 
  // proxy the request.
  proxy.proxyRequest(req, res, {
    host: '127.0.0.1',
    port: port
  });
}).listen(data.proxyPort);

// Now we handle WebSocket requests.
// Basically, I feed off of the above route
// logic and try to route my WebSocket to the
// same server regular requests are going to.
svr.on('upgrade', function (req, socket, head) {

  var cookies = parseCookies(req);  
  var port = false;

  // Make sure there is a Connect.SID,
  if (cookies['connect.sid'] != undefined) {

    // Make sure there is a route...
    if (data.routes[cookies['connect.sid']] !== undefined) {

      // Assign the appropriate port.
      port = data.routes[cookies['connect.sid']].port;
    } else {

      // this has never, ever happened, i've been logging it.
    }
  } else {

    // this has never, ever happened, i've been logging it.
  };

  if (port === false) {

    // this has never happened...
  };

  // So now route the WebSocket to the same port
  // as the regular requests are getting.
  svr.proxy.proxyWebSocketRequest(req, socket, head, {
    host: 'localhost',
    port: port
  });

});

Клиентская сторона / Феномены

Разъем соединяется так:

var socket = io.connect('http://whatever:8888');

Примерно через 10 секунд после входа в систему я снова получаю эту ошибку на этом приемнике, что не сильно помогает.

socket.on('error', function (data) {
  // this is what gets triggered. ->
  // Firefox can't establish a connection to the server at ws://whatever:8888/socket.io/1/websocket/Nnx08nYaZkLY2N479KX0.
});

GET-запрос Socket.IO, который отправляет браузер, никогда не возвращается - он просто висит в ожидании, даже после того, как ошибка возвращается, поэтому выглядит как ошибка тайм-аута. Сервер никогда не отвечает.

Серверная сторона - работник

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

app.sio.socketio.sockets.on('connection', function (socket) {
  // works... some of the time! all of my workers run this
  // exact same process.
});

Резюме

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

ОБНОВИТЬ

Хорошо, я вполне уверен, что проблема в этом утверждении на домашней странице github-http-proxy:

node-http-proxy <= 0.8.x совместим, если вы ищете>= 0.10 совместимую версию, пожалуйста, проверьте caronte

Я использую Node.js v0.10.13, и это явление точно такое же, как некоторые прокомментировали в github-проблемах на эту тему: он просто случайным образом прерывает соединения с веб-сокетами.

Я пытался реализовать caronte, "более новый" форк, но он совсем не документирован, и я изо всех сил старался собрать их документы в работоспособное решение, но я не могу заставить его переадресовывать веб-сокеты, мой Socket. IO понижается до опроса.

Есть ли другие идеи о том, как это реализовать и работать? node-http-proxy имеет 8200 загрузок вчера! Конечно, кто-то использует сборку Node с этого года и использует прокси-серверы....

Что я ищу именно

Я хочу создать прокси-сервер (предпочтительно Node), который проксирует нескольким работникам node.js и который направляет запросы через липкие сессии на основе куки-файла браузера. Этот прокси должен стабильно поддерживать как традиционные запросы, так и веб-сокеты.

Или же...

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

Если есть лучший способ выполнить вышесказанное, чем то, что я пытаюсь, я за это.

3 ответа

Решение

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

Это легкий интерфейсный сервер с json-подобной конфигурацией, надежный и очень хорошо протестированный.

nginx также намного быстрее, если вы хотите обслуживать статические страницы, css. Он идеально подходит для настройки заголовков кэширования, перенаправления трафика на несколько серверов в зависимости от домена, липких сессий, сжатия CSS и JavaScript и т. Д.

Вы также можете рассмотреть решение с открытым исходным кодом с чистой балансировкой нагрузки, такое как HAProxy. В любом случае я не верю, что нод - лучший инструмент для этого, лучше использовать его только для реализации вашего бэкэнда и поставить перед ним что-то вроде nginx для обработки обычных задач сервера внешнего интерфейса.

Я согласен с гексацианидом. Для меня было бы наиболее разумно ставить работников в очередь через службу вроде redis или какую-либо систему запросов сообщений. Рабочие будут поставлены в очередь через функциональность Redis Pub/Sub веб-узлами (которые проксируются). Рабочие будут вызывать в случае ошибки, завершать или передавать данные в реальном времени с событием "data". Может быть, проверить библиотеку куе. Вы также можете свернуть свою собственную подобную библиотеку. RabbitMQ - еще одна система для аналогичных целей.

Я использую socket.io, если вы уже используете эту технологию, но вам нужно использовать инструменты по назначению. Redis или система MQ были бы наиболее разумными и отлично сочетались с веб-сокетами (socket.io) для создания проницательных приложений в реальном времени.

Session Affinity (липкие сессии) поддерживается через Elastic LoadBalancer для aws, это поддерживает webSockets. Поставщик PaaS ( модуль) делает это точно. Theres также satalite, который предоставляет липкие сессии для узла-http-прокси, однако я понятия не имею, поддерживает ли он webSockets.

Я сам искал что-то очень похожее на это с целью создания (и уничтожения) узлов кластера Node.js на лету.

Отказ от ответственности: я все еще не рекомендовал бы делать это с Node; nginx более стабилен для той архитектуры проектирования, которую вы ищете, или, тем более, для HAProxy (очень зрелый и легко поддерживает прокси-сеанс липкой сессии). Как указывает @tsturzl, есть satellite, но учитывая небольшой объем загрузок, я бы осторожно пошёл (по крайней мере, в производственной среде).

Тем не менее, поскольку у вас, похоже, уже все настроено с помощью Node, перестройка и ре-архитектура могут быть более трудоемкими, чем они того стоят. Поэтому для установки caronte Ветка с НПМ:

  1. Удалить свой предыдущий http-node-proxy Мастер установки с npm uninstall node-proxy и / или sudo npm -d uninstall node-proxy

  2. Скачать caronte Разветвите.zip и распакуйте его.

  3. Бежать npm -g install /path/to/node-http-proxy-caronte
  4. В моем случае установочная связь была нарушена, поэтому мне пришлось запустить sudo npm link http-proxy

Я запустил его на своем базовом примере прокси-сервера - решит ли это проблему с удаленными сеансами или нет, только вы будете знать.

Другие вопросы по тегам