Эта статья - недавняя идея, которую я разработал во время учебного процесса Node.js, и я расскажу об этом с вами.
Node.js http -сервер
Использование node.js может быть использовано для реализации службы HTTP очень легко. Самый простой пример - это пример официального веб -сайта:
Кода -копия выглядит следующим образом:
var http = require ('http');
http.createserver (function (req, res) {
res.writehead (200, {'' content-type ':' text/plain '});
res.end ('Hello World/N');
}). Слушайте (1337, '127.0.0.1');
Это быстро создает веб -сервис, который слушает все HTTP -запросы на порту 1337.
Тем не менее, в реальной производственной среде мы обычно редко используем Node.js непосредственно в качестве интерфейского веб-сервера для пользователей. Основные причины заключаются в следующем:
1. Основываясь на однопользованной особенности Node.js, его гарантия надежности относительно высока для разработчиков.
2. Другие HTTP -сервисы на сервере уже могут занимать порт 80, а веб -сервисы, которые не являются портом 80, очевидно, недостаточно удобны для пользователей для пользователей.
3.Node.js не имеет большого преимущества в обработке ввода -вывода файлов. Например, как обычный веб -сайт, может потребовать от вас одновременно реагировать на файловые ресурсы, такие как изображения.
4. Сценарии распределенной нагрузки также являются проблемой.
Следовательно, использование node.js в качестве веб -службы может быть с большей вероятностью быть интерфейсом игрового сервера и другими аналогичными сценариями, в основном для работы с службами, которые не требуют прямых пользовательских доступа, и только выполнять обмен данными.
Веб-сервис Node.js на основе Nginx как на передней части машины
Основываясь на приведенных выше причинах, если это продукт в форме веб-сайта, созданный с Node.js, обычный способ его использования состоит в том, чтобы разместить еще один зрелый HTTP-сервер на переднем конце веб-службы Node.js, такой как Nginx, наиболее часто используется.
Затем используйте Nginx в качестве обратного прокси для доступа к веб-службе на основе Node.js. нравиться:
Кода -копия выглядит следующим образом:
сервер {
Слушайте 80;
server_name yekai.me;
root/home/andy/wwwroot/yekai;
расположение / {
proxy_pass http://127.0.0.1:1337;
}
местоположение ~ /.(gif|jpg|png|swf|ico|css|js)$ {
root/home/andy/wwwroot/yekai/static;
}
}
Это будет лучше решить несколько проблем, поднятых выше.
Связь с использованием протокола FastCGI
Тем не менее, есть некоторые вещи, которые не очень хороши в приведенном выше методе прокси.
Одним из возможных сценариев, которые требуют прямого доступа HTTP к веб -службе Node.js, которые необходимо контролировать позже. Однако, если вы хотите решить проблему, вы также можете использовать свои собственные услуги или полагаться на брандмауэр, чтобы заблокировать ее.
Другая причина заключается в том, что прокси-метод является решением на уровне приложений сети, и не очень удобно напрямую получать и обрабатывать данные, взаимодействующие с HTTP клиента, такими как обработка Keep-Alive, Trunk и даже куки. Конечно, это также связано с возможностями и функциональным совершенством самого прокси -сервера.
Итак, я думал о том, чтобы попробовать другой способ справиться с этим. Первое, о чем я подумал, это метод FastCGI, который сейчас обычно используется в веб -приложениях PHP.
Что такое FastCgi
Fast Common Gateway Interface (FASTCGI) - это протокол, который позволяет интерактивным программам общаться с веб -серверами.
Фон, созданный FastCGI, используется в качестве альтернативы веб -приложениям CGI. Одна из наиболее очевидных функций заключается в том, что для обработки ряда запросов можно использовать процесс обслуживания FastCGI. Веб -сервер подключит переменные среды и запрос этой страницы на веб -сервер через розетку, такой как процесс FastCGI. Соединение может быть подключено к веб -серверу с помощью Domain Socket Unix или подключения TCP/IP. Для получения дополнительных знаний, пожалуйста, обратитесь к записи Википедии.
Реализация FASTCGI node.js
Таким образом, теоретически нам нужно использовать только node.js для создания процесса FastCGI, а затем указывать, что запрос на прослушивание Nginx отправляется на этот процесс. Поскольку nginx и node.js являются моделями обслуживания, управляемых событиями, они должны быть «теоретическими» решениями в соответствии с миром. Давайте сделаем это самостоятельно.
В node.js чистый модуль может использоваться для создания службы сокета. Для удобства мы выбираем метод сокета Unix.
С небольшой модификацией конфигурации Nginx:
Кода -копия выглядит следующим образом:
...
расположение / {
fastcgi_pass unix: /tmp/node_fcgi.sock;
}
...
Создайте новый файл node_fcgi.js, со следующим контентом:
Кода -копия выглядит следующим образом:
var net = require ('net');
var server = net.createserver ();
server.listen ('/tmp/node_fcgi.sock');
server.on ('connection', function (sock) {
console.log ('connection');
sock.on ('data', function (data) {
console.log (data);
});
});
Затем запустите (из -за разрешений, пожалуйста, убедитесь, что сценарии Nginx и Node работают с одним и тем же пользователем или учетной записью с взаимными разрешениями, в противном случае вы столкнетесь с проблемами разрешения при чтении и написании файлов носков):
node node_fcgi.js
При доступе к браузеру мы видим, что терминал, запускающий скрипт узла, обычно получает содержание данных, например:
Кода -копия выглядит следующим образом:
связь
<Буфер 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01 ...>
Это доказывает, что наша теоретическая фонд достиг первого шага. Затем нам нужно только выяснить, как проанализировать содержание этого буфера.
Фонд протокола FASTCGI
Запись FastCGI состоит из префикса фиксированной длины, за которым следует переменное количество контента и мягких байтов. Структура записи заключается в следующем:
Кода -копия выглядит следующим образом:
typedef struct {
не подписываемая версия Char;
тип без знака типа;
unsigned char requestIdb1;
unsigned char requestIdb0;
unsigned char contentlengthb1;
unsigned char contentlengthb0;
без знака Чар паддинг -длина;
без подписи Чар зарезервировал;
unsigned char contentdata [contentLength];
unsigned char paddingdata [paddinglength];
} Fcgi_record;
Версия: версия протокола FASTCGI, теперь используйте 1 по умолчанию
Тип: Тип записи может быть фактически рассматриваться как другое состояние, и он будет подробно обсуждаться позже
requestId: идентификатор запроса, он должен соответствовать при возвращении. Если это не случай мультиплексирующего параллелизма, просто используйте 1 здесь
ContentLength: длина контента, максимальная длина здесь - 65535
Длина PaddingLenge: длина прокладки используется для заполнения длинных данных в целочисленное множество полных 8 байтов. В основном он используется для обработки данных, которые более эффективно выровняются, главным образом для соображений производительности
Зарезервирован: зарезервированные байты для последующего расширения
ContentData: реальные данные контента, давайте подробно поговорим об этом позже
PaddingData: Заполните данные, в любом случае 0, просто игнорируйте их напрямую.
Для конкретной структуры и описания, пожалуйста, обратитесь к официальному документу веб-сайта (http://www.fastcgi.com/devkit/doc/fcgi-pec.html#s3.3).
Запросить часть
Это кажется очень простым, просто разрабатывайте и получите данные за один раз. Тем не менее, здесь есть яма, то есть то, что здесь определено, является структурой блока данных (записи), а не всей структуры буфера. Весь буфер состоит из одной записи и одной записи. Вначале студентам, которые привыкли к фронтальному развитию, могут быть нелегко, но это основа для понимания протокола FastCGI, и мы увидим больше примеров позже.
Поэтому нам необходимо проанализировать запись и отличить записи на основе типа, который мы получали ранее. Вот простая функция, чтобы получить все записи:
Кода -копия выглядит следующим образом:
function getRcds (data, cb) {
var rcds = [],
Start = 0,
длина = data.length;
return function () {
if (start> = длина) {
CB && CB (RCDS);
RCD = NULL;
возвращаться;
}
var end = start + 8,
header = data.slice (start, end),
версия = заголовок [0],
Тип = заголовок [1],
requestId = (заголовок [2] << 8) + заголовок [3],
ContentLength = (заголовок [4] << 8) + заголовок [5],
PaddingLength = заголовок [6];
start = end + contentLength + paddingLength;
var body = contentLength? data.slice (end, contentlength): null;
rcds.push ([type, body, requestId]);
вернуть Arguments.callee ();
}
}
//использовать
sock.on ('data', function (data) {
getRcds (data, function (rcds) {
}) ();
}
Обратите внимание, что это просто простой процесс. Если существуют сложные ситуации, такие как загрузка файлов, эта функция не подходит для самой простой демонстрации. В то же время параметр requestId также игнорируется. Если это мультиплексирование, его нельзя игнорировать, и обработка должна быть гораздо сложнее.
Затем различные записи могут быть обработаны в соответствии с типом. Определение типа заключается в следующем:
Кода -копия выглядит следующим образом:
#define fcgi_begin_request 1
#define fcgi_abort_request 2
#define fcgi_end_request 3
#define fcgi_params 4
#define fcgi_stdin 5
#define fcgi_stdout 6
#define fcgi_stderr 7
#define fcgi_data 8
#define fcgi_get_values 9
#define fcgi_get_values_result 10
#define fcgi_unknown_type 11
#define fcgi_maxtype (fcgi_unknown_type)
Затем вы можете анализировать реальные данные в соответствии с записанным типом. Я буду использовать только наиболее часто используемые fcgi_params, fcgi_get_values и fcgi_get_values_result, чтобы проиллюстрировать. К счастью, их методы анализа последовательны. Проанализирование других записей о других типах имеет свои собственные правила, и вы можете ссылаться на определение спецификации для его реализации. Я не буду вдаваться в подробности здесь.
Fcgi_params, fcgi_get_values, fcgi_get_values_result-все данные типа «кодированного имени». Стандартный формат: передается в форме длины имени, за которой следует длина значения, за которым следует имя, за которым следует значение, где 127 байтов или меньше могут быть закодированы в одном байте, в то время как более длинные длины всегда кодируются в четырех байтах. Высокий бит первого байта длины указывает на то, как длина кодируется. Высокий бит 0 означает метод кодирования байта, а 1 означает метод кодирования с четырьмя байтами. Давайте посмотрим на всеобъемлющий пример, например, случай с длинными именами и короткими значениями:
Кода -копия выглядит следующим образом:
typedef struct {
unsigned char namelengthb3; / * namelengthb3 >> 7 == 1 */
unsigned char namelengthb2;
unsigned char namelengthb1;
unsigned char namelengthb0;
Unsigned char valuelengthb0; / * valuelengthb0 >> 7 == 0 */
Неподписал Чар именитата [Намель
((B3 & 0x7f) << 24) + (b2 << 16) + (b1 << 8) + b0];
без подписи Чар Валюдата [Valuelghing];
} Fcgi_namevaluepair41;
Соответствующая реализация JS Пример метода:
Кода -копия выглядит следующим образом:
Функция parsparams (body) {
var j = 0,
params = {},
длина = body.length;
while (j <длина) {
var name,
ценить,
Науказ,
валентная длина;
if (body [j] >> 7 == 1) {
inamelength = ((body [j ++] & 0x7f) << 24)+(body [j ++] << 16)+(body [j ++] << 8)+body [j ++];
} еще {
Намела = тело [j ++];
}
if (body [j] >> 7 == 1) {
valueldength = ((body [j ++] & 0x7f) << 24)+(body [j ++] << 16)+(body [j ++] << 8)+body [j ++];
} еще {
valueldength = body [j ++];
}
var ret = body.asciislice (j, j + namelength + valuelgene);
name = ret.substring (0, namelength);
value = ret.subString (Namelength);
params [name] = value;
J + = (Namelength + Valueldength);
}
вернуть параметры;
}
Это реализует простой метод получения различных параметров и переменных среды. Улучшить предыдущий код и продемонстрировать, как мы можем получить IP клиента:
Кода -копия выглядит следующим образом:
sock.on ('data', function (data) {
getRcds (data, function (rcds) {
for (var i = 0, l = rcds.length; i <l; i ++) {
var BodyData = rcds [i],
type = bodydata [0],
тело = BodyData [1];
if (body && (type === types.fcgi_params || type === types.fcgi_get_values || type === types.fcgi_get_values_result)) {
var params = parseparams (body);
console.log (params.remote_addr);
}
}
}) ();
}
До сих пор мы поняли основы запроса FastCGI, а затем мы будем реализовать часть ответа и, наконец, заработаем простой сервис ответа Echo.
Часть ответа
Часть ответа относительно проста. В простейшем случае вам нужно только отправить две записи, то есть FCGI_STDOUT и FCGI_END_REQUEST.
Я не буду описывать конкретный содержание объекта, просто посмотрите на код:
Кода -копия выглядит следующим образом:
var res = (function () {
var maxlength = math.pow (2, 16);
функция Buffer0 (len) {
вернуть новый буфер ((новый массив (Len + 1)). Join ('/u0000'));
};
функция writestdout (data) {
var rcdstdouthd = новый буфер (8),
contentLength = data.length,
PaddingLength = 8 - ContentLength % 8;
rcdstdouthd [0] = 1;
rcdstdouthd [1] = types.fcgi_stdout;
rcdstdouthd [2] = 0;
rcdstdouthd [3] = 1;
rcdstdouthd [4] = contendlength >> 8;
rcdstdouthd [5] = contendlength;
rcdstdouthd [6] = paddinglength;
rcdstdouthd [7] = 0;
return buffer.concat ([rcdstdouthd, data, buffer0 (paddinglength)]);
};
function writehttphead () {
return writestdout (новый буфер ("http/1.1 200 ok/r/ncontent-type: text/html; charset = utf-8/r/nconnection: close/r/n/r/n"));
}
функция writehttpbody (bodystr) {
var bodybuffer = [],
Body = новый буфер (Bodystr);
for (var i = 0, l = body.length; i <l; i + = maxlength + 1) {
bodybuffer.push (writestdout (body.slice (i, i + maxlength)));
}
return buffer.concat (bodybuffer);
}
function writeend () {
var rcdendhd = новый буфер (8);
rcdendhd [0] = 1;
rcdendhd [1] = types.fcgi_end_request;
rcdendhd [2] = 0;
rcdendhd [3] = 1;
rcdendhd [4] = 0;
rcdendhd [5] = 8;
rcdendhd [6] = 0;
rcdendhd [7] = 0;
return buffer.concat ([rcdendhd, buffer0 (8)]);
}
вернуть функцию (data) {
return buffer.concat ([writehttphead (), writehttpbody (data), writeend ()]);
};
}) ();
В простейшем случае это позволит вам отправить полный ответ. Измените наш окончательный код:
Кода -копия выглядит следующим образом:
VAR посетители = 0;
server.on ('connection', function (sock) {
Посетители ++;
sock.on ('data', function (data) {
...
var Querys = QueryString.parse (params.query_string);
var ret = res ('добро пожаловать,' + (Querys.name || 'дорогой друг') + '! Вы - номер' + посетители + 'Document ~');
sock.write (ret);
ret = null;
sock.end ();
...
});
Откройте браузер и посетите: http: // domain/? Name = yekai, и вы можете увидеть что -то вроде «Добро пожаловать, Yekai! Вы - 7 -й пользователь этого сайта ~».
На этом этапе мы успешно реализовали простейшую службу FastCGI с использованием node.js. Если это необходимо использовать в качестве реального сервиса, нам нужно только сравнить спецификации протокола, чтобы улучшить нашу логику.
Сравнительный тест
Наконец, вопрос, который мы должны рассмотреть, заключается в том, является ли это решение специально осуществимым? Некоторые студенты, возможно, увидели проблему, поэтому я сначала сделаю простые результаты испытания давления:
Кода -копия выглядит следующим образом:
// Метод FASTCGI:
500 клиентов, выполняя 10 сек.
Скорость = 27678 страниц/мин, 63277 байтов/сек.
Запросы: 3295 SUSCEED, 1318 не удалось.
500 клиентов, пробежав 20 сек.
Скорость = 22131 страницы/мин, 63359 байт/сек.
Запросы: 6523 SUSCEED, 854 не удалось.
// Прокси -метод:
500 клиентов, выполняя 10 сек.
Скорость = 28752 страницы/мин, 73191 байт/сек.
Запросы: 3724 SUSCEED, 1068 не удалось.
500 клиентов, пробежав 20 сек.
Скорость = 26508 страниц/мин, 66267 байт/сек.
Запросы: 6716 SUSCEED, 2120 потерпел неудачу.
// Метод обслуживания непосредственно доступа к Node.js:
500 клиентов, выполняя 10 сек.
Скорость = 101154 страницы/мин, 264247 байт/сек.
Запросы: 15729 SUSCEED, 1130 потерпел неудачу.
500 клиентов, пробежав 20 сек.
Скорость = 43791 страницы/мин, 115962 байт/сек.
Запросы: 13898 SUSCEED, 699 не удалось.
Почему метод прокси лучше, чем метод FastCGI? Это связано с тем, что в соответствии с схемой прокси, бэкэнд -сервис управляется непосредственно нативным модулем node.js, а схема FastCGI реализуется нами с использованием JavaScript. Тем не менее, также можно увидеть, что между этими двумя решениями нет большого разрыва в эффективности (конечно, сравнение здесь является простой ситуацией. Если разрыв больше в реальных сценариях бизнеса), и если Node.js национально поддерживает услуги FastCGI, эффективность должна быть лучше.
PostScript
Если вы заинтересованы в продолжении игры, вы можете проверить исходный код примеров, которые я реализовал в этой статье. Я изучал спецификации протокола за последние два дня, но это не сложно.
В то же время я вернусь и поиграю с UWSGI, но чиновник сказал, что V8 уже готов напрямую поддержать его.
У меня очень мелкая игра. Если есть ошибка, пожалуйста, поправьте меня и сообщите.