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
Ветка с НПМ:
Удалить свой предыдущий
http-node-proxy
Мастер установки сnpm uninstall node-proxy
и / илиsudo npm -d uninstall node-proxy
Скачать
caronte
Разветвите.zip и распакуйте его.- Бежать
npm -g install /path/to/node-http-proxy-caronte
- В моем случае установочная связь была нарушена, поэтому мне пришлось запустить
sudo npm link http-proxy
Я запустил его на своем базовом примере прокси-сервера - решит ли это проблему с удаленными сеансами или нет, только вы будете знать.