Este artículo es una idea reciente que he desarrollado durante el proceso de aprendizaje Node.js, y lo discutiré con usted.
Servidor node.js http
El uso de Node.js se puede usar para implementar un servicio HTTP muy fácilmente. El ejemplo más simple es como el ejemplo de un sitio web oficial:
La copia del código es la siguiente:
var http = require ('http');
http.createServer (function (req, res) {
res.Writehead (200, {'Content-type': 'Text/Plain'});
res.end ('Hello World/n');
}). Escuchar (1337, '127.0.0.1');
Esto construye rápidamente un servicio web que escucha todas las solicitudes HTTP en el puerto 1337.
Sin embargo, en un entorno de producción real, generalmente rara vez usamos Node.js directamente como el servidor web front-end para los usuarios. Las razones principales son las siguientes:
1. Basado en la característica única de Node.js, su garantía de robustez es relativamente alta para los desarrolladores.
2. Otros servicios HTTP en el servidor ya pueden ocupar el puerto 80, y los servicios web que no son puerto 80 obviamente no son lo suficientemente fáciles de usar para los usuarios.
3.node.js no tiene mucha ventaja en el procesamiento del archivo IO. Por ejemplo, como sitio web regular, puede requerir que responda a los recursos de archivos como imágenes al mismo tiempo.
4. Los escenarios de carga distribuida también son un desafío.
Por lo tanto, usar Node.js como servicio web puede ser más probable que sea una interfaz de servidor de juegos y otros escenarios similares, principalmente para tratar los servicios que no requieren acceso directo al usuario y solo realizan intercambio de datos.
Servicio web node.js basado en Nginx como una máquina frontal
Según las razones anteriores, si se trata de un producto en forma de sitio web construido con Node.js, la forma convencional de usarlo es colocar otro servidor HTTP maduro en la parte delantera del servicio web de Node.js, como Nginx es el más utilizado.
Luego use NGINX como un proxy inverso para acceder al servicio web basado en Node.js. como:
La copia del código es la siguiente:
servidor{
Escucha 80;
server_name yekai.me;
root/home/andy/wwwroot/yekai;
ubicación / {
proxy_pass http://127.0.0.1:1337;
}
Ubicación ~ /.(gif|Jpg|Png|Swf|ICO|css|js)$ {
root/home/andy/wwwroot/yekai/static;
}
}
Esto resolverá mejor los diversos problemas planteados anteriormente.
Comunicación utilizando el protocolo FastCGI
Sin embargo, hay algunas cosas que no son muy buenas sobre el método proxy anterior.
Uno es posible escenarios que requieren acceso directo HTTP al servicio web node.js que debe controlarse más adelante. Sin embargo, si desea resolver el problema, también puede usar sus propios servicios o confiar en el firewall para bloquearlo.
Otra razón es que el método proxy es una solución en la capa de aplicación de red después de todo, y no es muy conveniente obtener y procesar directamente los datos que interactúan con el HTTP del cliente, como el procesamiento de mantenimiento de mantenimiento, troncal e incluso cookies. Por supuesto, esto también está relacionado con las capacidades y la perfección funcional del servidor proxy en sí.
Entonces, estaba pensando en intentar otra forma de lidiar con eso. Lo primero que pensé es el método FastCGI que se usa comúnmente en aplicaciones web de PHP ahora.
¿Qué es Fastcgi?
La interfaz Fast Common Gateway (FASTCGI) es un protocolo que permite que los programas interactivos se comuniquen con los servidores web.
El fondo generado por FastCGI se utiliza como alternativa a las aplicaciones web CGI. Una de las características más obvias es que se puede utilizar un proceso de servicio FASTCGI para manejar una serie de solicitudes. El servidor web conectará las variables de entorno y esta solicitud de página al servidor web a través de un socket como el proceso FastCGI. La conexión se puede conectar al servidor web mediante un sistema de dominio UNIX o una conexión TCP/IP. Para obtener más conocimiento previo, consulte la entrada de Wikipedia.
Implementación de FastCGI de Node.js
Entonces, en teoría, solo necesitamos usar Node.js para crear un proceso FASTCGI, y luego especificar que la solicitud de escucha de Nginx se envía a este proceso. Dado que Nginx y Node.js son modelos de servicio impulsados por eventos, deben ser soluciones "teóricas" para que coincidan con el mundo. Hagámoslo tú mismo.
En Node.js, el módulo neto se puede usar para establecer un servicio de socket. En aras de la conveniencia, elegimos el método de socket Unix.
Con una ligera modificación de la configuración de Nginx:
La copia del código es la siguiente:
...
ubicación / {
fastcgi_pass unix: /tmp/node_fcgi.sock;
}
...
Cree un nuevo archivo node_fcgi.js, con el siguiente contenido:
La copia del código es la siguiente:
var net = require ('net');
var servidor = net.createServer ();
server.listen ('/tmp/node_fcgi.sock');
servidor.on ('conexión', function (sock) {
console.log ('conexión');
sock.on ('data', function (data) {
console.log (datos);
});
});
Luego se ejecute (debido a los permisos, asegúrese de que los scripts Nginx y Node se ejecuten con el mismo usuario o cuenta con permisos mutuos, de lo contrario encontrará problemas de permiso al leer y escribir archivos de calcetín):
nodo node_fcgi.js
Al acceder al navegador, vemos que el terminal que ejecuta el script de nodo normalmente recibe el contenido de datos, como este:
La copia del código es la siguiente:
conexión
<Buffer 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01 ...>
Esto demuestra que nuestra base teórica ha logrado el primer paso. A continuación, solo necesitamos descubrir cómo analizar el contenido de este búfer.
Fundación de protocolo FastCGI
Un registro FastCGI consiste en un prefijo de longitud fija seguido de un número variable de contenido y bytes acolchados. La estructura de registro es la siguiente:
La copia del código es la siguiente:
typedef struct {
Versión de Char Unsigned;
Tipo de char sin firmar;
CHAR sin firmar requestidb1;
CHAR sin firmar requestidb0;
Unsigned Char ContentLengthb1;
Unsigned Char ContentLengthB0;
CHAR CARDING LENGUACIÓN DE CHAR;
Char sin firmar reservado;
Unsigned Char ContentData [ContentLength];
Unsigned Char PaddingData [PaddingLength];
} Fcgi_record;
Versión: Versión de protocolo FastCGI, ahora usa 1 de forma predeterminada
Tipo: El tipo de registro se puede considerar como un estado diferente, y se discutirá en detalle más adelante
RequestId: ID de solicitud, debe corresponder al regresar. Si no es un caso de concurrencia multiplexación, solo use 1 aquí
ContentLength: Longitud de contenido, la longitud máxima aquí es 65535
La longitud de relleno: la longitud del relleno se usa para llenar los datos largos en un múltiplo entero de los 8 bytes completos. Se utiliza principalmente para procesar datos que se alinean de manera más efectiva, principalmente para consideraciones de rendimiento.
Reservado: bytes reservados para la expansión posterior
ContentData: datos reales de contenido, hablemos de ello en detalle más tarde
PaddingData: Complete los datos, es 0 de todos modos, simplemente ignórelo directamente.
Para obtener una estructura y descripción específicas, consulte el documento oficial del sitio web (http://www.fastcgi.com/devkit/doc/fcgi-spec.html#s3.3).
Parte de solicitud
Parece muy simple, solo analice y obtenga los datos de una sola vez. Sin embargo, aquí hay un pozo, es decir, lo que se define aquí es la estructura de la unidad de datos (registro), no toda la estructura del búfer. Todo el búfer consta de un récord y un récord. Al principio, puede que no sea fácil para los estudiantes que están acostumbrados al desarrollo delantero, pero esta es la base para comprender el protocolo FastCGI, y veremos más ejemplos más adelante.
Por lo tanto, necesitamos analizar un registro y distinguir los registros en función del tipo que obtuvimos antes. Aquí hay una función simple para obtener todos los registros:
La copia del código es la siguiente:
function getRcds (Data, CB) {
var rcds = [],
inicio = 0,
longitud = data.length;
Función de retorno () {
if (inicio> = longitud) {
CB && CB (RCDS);
rcds = nulo;
devolver;
}
var end = inicio + 8,
Header = data.slice (inicio, finalización),
versión = encabezado [0],
tipo = encabezado [1],
requestiD = (encabezado [2] << 8) + encabezado [3],
contentLength = (encabezado [4] << 8) + encabezado [5],
PaddingLength = Header [6];
start = end + contentLength + paddingLength;
var cuerpo = contentLength? data.slice (end, contentLength): null;
rcds.push ([tipo, cuerpo, requestId]);
devolver argumentos.callee ();
}
}
//usar
sock.on ('data', function (data) {
getRCDS (datos, función (RCDS) {
}) ();
}
Tenga en cuenta que este es solo un proceso simple. Si hay situaciones complejas como cargar archivos, esta función no es adecuada para la demostración más simple. Al mismo tiempo, el parámetro SoldId también se ignora. Si es multiplexación, no se puede ignorar y el procesamiento deberá ser mucho más complicado.
A continuación, se pueden procesar diferentes registros de acuerdo con el tipo. La definición de tipo es la siguiente:
La copia del código es la siguiente:
#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)
A continuación, puede analizar los datos reales de acuerdo con el tipo registrado. Solo usaré los fcgi_params más utilizados, fcgi_get_values y fcgi_get_values_result para ilustrar. Afortunadamente, sus métodos de análisis son consistentes. El análisis de otros registros de tipos tiene sus propias reglas diferentes, y puede consultar la definición de la especificación para implementarla. No voy a entrar en detalles aquí.
Fcgi_params, fcgi_get_values, fcgi_get_values_result son todos los datos de tipo "valor-valor codificado". El formato estándar es: transmitido en forma de una longitud de nombre, seguido de la longitud del valor, seguido del nombre, seguido del valor, donde se pueden codificar 127 bytes o menos en un byte, mientras que las longitudes más largas siempre están codificadas en cuatro bytes. El alto bit del primer byte de longitud indica cómo se codifica la longitud. Un alto bit de 0 significa un método de codificación de bytes, y 1 significa un método de codificación de cuatro bytes. Veamos un ejemplo completo, como el caso de nombres largos y valores cortos:
La copia del código es la siguiente:
typedef struct {
Unsigned char namelengthb3; / * namelengthb3 >> 7 == 1 */
un sin signo char namelengthb2;
Unsigned char namelengthb1;
Unsigned Char Namelengthb0;
Unsigned Char ValuelgentingB0; / * ValuelgentingB0 >> 7 == 0 */
Char unsigned nameata [namelength
((B3 y 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
sin firmar char valiedata [Valuelgth];
} Fcgi_nameValuePair41;
Ejemplo de método JS de implementación correspondiente:
La copia del código es la siguiente:
función parseparams (cuerpo) {
var j = 0,
params = {},
longitud = cuerpo.length;
while (j <longitud) {
nombre var,
valor,
namelength,
Valuelggth;
if (cuerpo [j] >> 7 == 1) {
namelength = ((cuerpo [j ++] y 0x7f) << 24)+(cuerpo [j ++] << 16)+(cuerpo [j ++] << 8)+cuerpo [j ++];
} demás {
namelength = cuerpo [j ++];
}
if (cuerpo [j] >> 7 == 1) {
Valuelgth = ((cuerpo [j ++] y 0x7f) << 24)+(cuerpo [j ++] << 16)+(cuerpo [j ++] << 8)+cuerpo [j ++];
} demás {
Valuelgth = Body [j ++];
}
var Ret = Body.asciislice (J, J + Namelength + Valuelggth);
name = ret.substring (0, namelength);
valor = ret.substring (namelength);
params [nombre] = valor;
j + = (namelength + Valuelggth);
}
devolver los parámetros;
}
Esto implementa un método simple para obtener varios parámetros y variables de entorno. Mejore el código anterior y demuestre cómo podemos obtener la IP del cliente:
La copia del código es la siguiente:
sock.on ('data', function (data) {
getRCDS (datos, función (RCDS) {
para (var i = 0, l = rcds.length; i <l; i ++) {
var cuerpodata = rcds [i],
type = BodyData [0],
Body = BodyData [1];
if (body && (type === tipos.fcgi_params || type === tipos.fcgi_get_values || type === tipos.fcgi_get_values_result)) {
var params = parseparams (cuerpo);
console.log (params.remote_addr);
}
}
}) ();
}
Hasta ahora hemos entendido los conceptos básicos de la parte de solicitud FastCGI, y luego implementaremos la parte de respuesta y finalmente completaremos un simple servicio de respuesta de eco.
Parte de respuesta
La parte de respuesta es relativamente simple. En el caso más simple, solo necesita enviar dos registros, es decir, fcgi_stdout y fcgi_end_request.
No describiré el contenido específico de la entidad, solo mire el código:
La copia del código es la siguiente:
var res = (function () {
var maxLength = Math.Pow (2, 16);
function buffer0 (len) {
devolver nuevo búfer ((nueva matriz (len + 1)). unir ('/u0000'));
};
function writeStDout (data) {
var rcdstdouthd = nuevo búfer (8),
contentLength = data.length,
PaddingLength = 8 - ContentLength % 8;
rcdstdouthd [0] = 1;
rcdstdouthd [1] = tipos.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 (nuevo búfer ("http/1.1 200 ok/r/ncontent-type: text/html; charset = utf-8/r/nconnection: search/r/n/r/n"));
}
function writeHttpBody (BodyStr) {
var Bodybuffer = [],
cuerpo = nuevo búfer (bodystr);
para (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 = nuevo búfer (8);
rcdendhd [0] = 1;
rcdendhd [1] = tipos.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)]);
}
Función de retorno (datos) {
return buffer.concat ([writeHttphead (), writeHttpbody (data), writeend ()]);
};
}) ();
En el caso más simple, esto le permitirá enviar una respuesta completa. Cambiar nuestro código final:
La copia del código es la siguiente:
visitantes var = 0;
servidor.on ('conexión', function (sock) {
visitantes ++;
sock.on ('data', function (data) {
...
var Querys = QueryString.Parse (params.query_string);
var ret = res ('bienvenido', + (querys.name || 'querido amigo') + '! Eres el número' + visitantes + 'documento ~');
sock.write (ret);
ret = nulo;
sock.end ();
...
});
Abra el navegador y visite: http: // dominio/? Name = yekai, y puede ver algo como "¡Bienvenido, yekai! Eres el séptimo usuario de este sitio ~".
En este punto, implementamos con éxito el servicio FastCGI más simple usando Node.js. Si necesita ser utilizado como un servicio real, solo necesitamos comparar las especificaciones de protocolo para mejorar nuestra lógica.
Prueba comparativa
Finalmente, la pregunta que debemos considerar es si esta solución es específicamente factible. Es posible que algunos estudiantes hayan visto el problema, por lo que primero pondré los resultados simples de las pruebas de presión:
La copia del código es la siguiente:
// Método FastCGI:
500 clientes, con 10 segundos.
Velocidad = 27678 páginas/min, 63277 bytes/seg.
Solicitudes: 3295 Sospechoso, 1318 fracasaron.
500 clientes, con 20 segundos.
Velocidad = 22131 páginas/min, 63359 bytes/seg.
Solicitudes: 6523 Sospechoso, 854 fracasaron.
// método proxy:
500 clientes, con 10 segundos.
Velocidad = 28752 páginas/min, 73191 bytes/seg.
Solicitudes: 3724 Sospechoso, 1068 fracasaron.
500 clientes, con 20 segundos.
Velocidad = 26508 páginas/min, 66267 bytes/seg.
Solicitudes: 6716 Sospechoso, 2120 falló.
// Acceda directamente al método de servicio Node.js:
500 clientes, con 10 segundos.
Velocidad = 101154 páginas/min, 264247 bytes/seg.
Solicitudes: 15729 Sospechoso, 1130 falló.
500 clientes, con 20 segundos.
Velocidad = 43791 páginas/min, 115962 bytes/seg.
Solicitudes: 13898 Sospechoso, 699 fracasaron.
¿Por qué el método proxy es mejor que el método FastCGI? Esto se debe a que bajo el esquema proxy, el servicio de backend se ejecuta directamente mediante el módulo nativo Node.js, y el esquema FastCGI es implementado por nosotros mismos utilizando JavaScript. Sin embargo, también se puede ver que no hay una gran brecha en la eficiencia entre las dos soluciones (por supuesto, la comparación aquí es solo una situación simple. Si la brecha es mayor en escenarios comerciales reales), y si Node.js admite nativamente los servicios FastCGI, la eficiencia debería ser mejor.
posdata
Si está interesado en continuar jugando, puede verificar el código fuente de los ejemplos que implementé en este artículo. He estudiado las especificaciones de protocolo en los últimos dos días, pero no es difícil.
Al mismo tiempo, volveré y jugaré con UWSGI, pero el funcionario dijo que V8 ya está listo para apoyarlo directamente.
Tengo un juego muy superficial. Si hay algún error, corregirme y comuníquese.