Este artigo é uma ideia recente que desenvolvi durante o processo de aprendizado do Node.js, e discutirei com você.
Node.js HTTP Server
O uso do Node.js pode ser usado para implementar um serviço HTTP com muita facilidade. O exemplo mais simples é o exemplo de um site oficial:
A cópia do código é a seguinte:
var http = requer ('http');
http.createServer (function (req, res) {
res.writehead (200, {'content-type': 'text/plana'});
Res.End ('Hello World/N');
}). Ouça (1337, '127.0.0.1');
Isso constrói rapidamente um serviço da Web que ouve todas as solicitações HTTP na porta 1337.
No entanto, em um ambiente de produção real, geralmente usamos o Node.js diretamente como o servidor da Web front-end para os usuários. Os principais motivos são os seguintes:
1. Com base no recurso de thread único do Node.js, sua garantia de robustez é relativamente alta para os desenvolvedores.
2. Outros serviços HTTP no servidor já podem ocupar a porta 80 e os serviços da Web que não são a porta 80 obviamente não são amigáveis o suficiente para os usuários.
3.Node.js não tem muita vantagem no processamento de IO de arquivo. Por exemplo, como site regular, pode exigir que você responda aos recursos como imagens ao mesmo tempo.
4. Os cenários de carga distribuídos também são um desafio.
Portanto, o uso do Node.js como serviço da Web pode ter maior probabilidade de ser uma interface de servidor de jogo e outros cenários semelhantes, principalmente para lidar com serviços que não exigem acesso direto ao usuário e executam apenas a troca de dados.
Node.js Web Service com base no NGINX como uma máquina front-end
Com base nos motivos acima, se for um produto em forma de site criado com o Node.js, a maneira convencional de usá-lo é colocar outro servidor HTTP maduro na extremidade frontal do serviço da web do Node.js, como o Nginx é o mais comumente usado.
Em seguida, use o NGINX como proxy reverso para acessar o serviço da web baseado em Node.js. como:
A cópia do código é a seguinte:
servidor {
Ouça 80;
server_name yekai.me;
raiz/home/andy/wwwroot/yekai;
Localização / {
proxy_pass http://127.0.0.1:1337;
}
Localização ~ /.(gif|jpg|png|swf|ico|css|js)$ {
raiz/home/andy/wwwroot/yekai/static;
}
}
Isso resolverá melhor os vários problemas levantados acima.
Comunicação usando o protocolo fastcgi
No entanto, existem algumas coisas que não são muito boas no método de proxy acima.
Um são cenários possíveis que exigem acesso direto ao HTTP ao serviço da Web Node.js que precisam ser controlados posteriormente. No entanto, se você deseja resolver o problema, também pode usar seus próprios serviços ou confiar no firewall para bloqueá -lo.
Outro motivo é que o método proxy é uma solução na camada de aplicativo de rede, afinal, e não é muito conveniente obter diretamente e processar dados interagindo com o HTTP do cliente, como processamento de biscoitos de manutenção, tronco e até. Obviamente, isso também está relacionado aos recursos e à perfeição funcional do próprio servidor proxy.
Então, eu estava pensando em tentar outra maneira de lidar com isso. A primeira coisa que pensei é o método fastcgi que é comumente usado nos aplicativos da Web PHP agora.
O que é fastcgi
A Interface de Gateway Fast Common (FastCGI) é um protocolo que permite que os programas interativos se comuniquem com os servidores da Web.
O plano de fundo gerado pelo FastCGI é usado como uma alternativa aos aplicativos da Web CGI. Um dos recursos mais óbvios é que um processo de serviço FastCGI pode ser usado para lidar com uma série de solicitações. O servidor da Web conectará as variáveis de ambiente e esta solicitação de página ao servidor da Web através de um soquete como o processo FastCGI. A conexão pode ser conectada ao servidor da Web pelo soquete do UNIX Domain ou uma conexão TCP/IP. Para obter mais conhecimento de fundo, consulte a entrada da Wikipedia.
Implementação do Fastcgi do Node.js
Portanto, em teoria, precisamos apenas usar o Node.js para criar um processo FastCGI e, em seguida, especificar que a solicitação de escuta do NGINX é enviada para esse processo. Como o nginx e o node.js são modelos de serviço orientados a eventos, eles devem ser soluções "teóricas" para corresponder ao mundo. Vamos fazer você mesmo.
No Node.js, o módulo líquido pode ser usado para estabelecer um serviço de soquete. Por uma questão de conveniência, escolhemos o método do soquete Unix.
Com uma ligeira modificação da configuração Nginx:
A cópia do código é a seguinte:
...
Localização / {
fastcgi_pass unix: /tmp/node_fcgi.sock;
}
...
Crie um novo arquivo node_fcgi.js, com o seguinte conteúdo:
A cópia do código é a seguinte:
var net = requer ('net');
var server = net.createServer ();
server.listen ('/tmp/node_fcgi.sock');
Server.on ('Connection', function (Sock) {
console.log ('conexão');
Sock.on ('dados', função (dados) {
console.log (dados);
});
});
Em seguida, execute (por causa de permissões, verifique se os scripts nginx e nó são executados com o mesmo usuário ou conta com permissões mútuas; caso contrário, você encontrará problemas de permissão ao ler e escrever arquivos de meia):
node node_fcgi.js
Ao acessar o navegador, vemos que o terminal executando o script do nó normalmente recebe o conteúdo dos dados, como este:
A cópia do código é a seguinte:
conexão
<Buffer 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01 ...>
Isso prova que nossa base teórica alcançou o primeiro passo. Em seguida, precisamos descobrir como analisar o conteúdo desse buffer.
Fundação do Protocolo Fastcgi
Um registro fastcgi consiste em um prefixo de comprimento fixo, seguido de um número variável de conteúdo e bytes acolchoados. A estrutura de registros é a seguinte:
A cópia do código é a seguinte:
typedef struct {
Versão de char não assinada;
tipo de char não assinado;
char não assinado requestIdb1;
char não assinado requestIdb0;
CHAR não assinado contentLengthB1;
CHAR não assinado contentLengthB0;
Char PaddingLeng de Char Paddinglen não assinado;
char não assinado reservado;
Char ContentData não assinado [contentLength];
Char PaddingData não assinado [PaddingLength];
} Fcgi_record;
Versão: versão do protocolo fastcgi, agora use 1 por padrão
Tipo: o tipo de registro pode realmente ser considerado um estado diferente e será discutido em detalhes posteriormente
RequestId: ID de solicitação, ele deve corresponder ao retornar. Se não for um caso de simultaneidade multiplexadora, basta usar 1 aqui
ContentLength: Comprimento do conteúdo, o comprimento máximo aqui é 65535
PaddingLength: O comprimento do preenchimento é usado para preencher os dados longos em um múltiplo inteiro dos 8 bytes completos. É usado principalmente para processar dados alinhados com mais eficácia, principalmente para considerações de desempenho
Reservado: bytes reservados para expansão subsequente
ContentData: dados reais de conteúdo, vamos falar sobre isso em detalhes mais tarde
PaddingData: preencha os dados, é 0 de qualquer maneira, basta ignorá -lo diretamente.
Para uma estrutura e descrição específicas, consulte o documento oficial do site (http://www.fastcgi.com/devkit/doc/fcgi-spec.html#s3.3).
Peça de solicitação
Parece muito simples, apenas analise e obtenha os dados de uma só vez. No entanto, há um poço aqui, ou seja, o que é definido aqui é a estrutura da unidade de dados (registro), não toda a estrutura do buffer. Todo o buffer consiste em um registro e um recorde. No começo, pode não ser fácil para os alunos que estão acostumados ao desenvolvimento do front-end, mas essa é a base para a compreensão do protocolo FastCGI, e veremos mais exemplos posteriormente.
Portanto, precisamos analisar um registro e distinguir os registros com base no tipo que obtivemos antes. Aqui está uma função simples para obter todos os registros:
A cópia do código é a seguinte:
função getrcds (dados, cb) {
var rcds = [],
start = 0,
comprimento = data.length;
Return function () {
if (start> = length) {
CB && CB (RCDS);
rcds = nulo;
retornar;
}
var end = start + 8,
cabeçalho = data.slice (start, fim),
versão = cabeçalho [0],
tipo = cabeçalho [1],
requestId = (cabeçalho [2] << 8) + cabeçalho [3],
contentLength = (cabeçalho [4] << 8) + cabeçalho [5],
PaddingLength = cabeçalho [6];
start = end + contentLength + PaddingLength;
var corpo = contentLength? data.slice (final, contentLength): null;
rcds.push ([tipo, corpo, requestId]);
retorno argumentos.callee ();
}
}
//usar
Sock.on ('dados', função (dados) {
getrcds (dados, função (rcds) {
}) ();
}
Observe que este é apenas um processo simples. Se houver situações complexas, como o upload de arquivos, essa função não será adequada para a demonstração mais simples. Ao mesmo tempo, o parâmetro RequestId também é ignorado. Se for multiplexista, não pode ser ignorado e o processamento precisará ser muito mais complicado.
Em seguida, diferentes registros podem ser processados de acordo com o tipo. A definição do tipo é a seguinte:
A cópia do código é a seguinte:
#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)
Em seguida, você pode analisar os dados reais de acordo com o tipo gravado. Usarei apenas os fcgi_params mais usados, fcgi_get_values e fcgi_get_values_result para ilustrar. Felizmente, seus métodos de análise são consistentes. A análise de outros tipos registros tem suas próprias regras diferentes e você pode consultar a definição da especificação para implementá -la. Não vou entrar em detalhes aqui.
Fcgi_params, fcgi_get_values, fcgi_get_values_result são todos os dados do tipo "codificado de nome codificado". O formato padrão é: transmitido na forma de um comprimento de nome, seguido pelo comprimento do valor, seguido pelo nome, seguido pelo valor, onde 127 bytes ou menos podem ser codificados em um byte, enquanto comprimentos mais longos são sempre codificados em quatro bytes. O bit alto do primeiro byte de comprimento indica como o comprimento é codificado. Um bit alto de 0 significa um método de codificação de bytes e 1 significa um método de codificação de quatro bytes. Vejamos um exemplo abrangente, como o caso de nomes longos e valores curtos:
A cópia do código é a seguinte:
typedef struct {
CHAR NAMELENDO NÃO SIGNADO; / * nameLengthb3 >> 7 == 1 */
Char NameLength2 não assinado;
CHAR NAMELENDO NÃO SIGNADO;
Char NameLengthb0 não assinado;
CHAR VALUELELTEN LIMPENDIMENTO NUMAGENDO; / * valuelngthngthb0 >> 7 == 0 */
char não assinado chamadoata [comprimento
((B3 e 0x7f) << 24) + (b2 << 16) + (b1 << 8) + b0];
Char não assinado Valuedata [Valuel comprimento];
} Fcgi_nameValuepair41;
Exemplo de método JS de implementação correspondente:
A cópia do código é a seguinte:
função parseparams (corpo) {
var j = 0,
params = {},
comprimento = corpo.length;
while (j <comprimento) {
Var Nome,
valor,
comprimento de nome,
Valuel comprimento;
if (corpo [j] >> 7 == 1) {
nameLength = ((corpo [j ++] e 0x7f) << 24)+(corpo [j ++] << 16)+(corpo [j ++] << 8)+corpo [j ++];
} outro {
nameLength = corpo [j ++];
}
if (corpo [j] >> 7 == 1) {
Valuel comprimento = ((corpo [j ++] e 0x7f) << 24)+(corpo [j ++] << 16)+(corpo [j ++] << 8)+corpo [j ++];
} outro {
valuel comprimento = corpo [j ++];
}
var ret = body.asciislice (j, j + nameLenth + valuel comprimento);
nome = ret.substring (0, comprimento);
value = ret.substring (comprimento de namel);
params [nome] = value;
J + = (NameLenthnth + Valuel comprimento);
}
retornar params;
}
Isso implementa um método simples para obter vários parâmetros e variáveis de ambiente. Melhore o código anterior e demonstre como podemos obter o IP do cliente:
A cópia do código é a seguinte:
Sock.on ('dados', função (dados) {
getrcds (dados, função (rcds) {
for (var i = 0, l = rcds.length; i <l; i ++) {
var BodyData = RCDs [i],
tipo = bodydata [0],
corpo = bodydata [1];
if (body && (type === types.fcgi_params || type === types.fcgi_get_values || type === types.fcgi_get_values_result)) {
var params = parseparams (corpo);
console.log (params.remote_addr);
}
}
}) ();
}
Até agora, entendemos o básico da peça de solicitação FastCGI e, em seguida, implementaremos a parte da resposta e finalmente concluiremos um serviço de resposta de eco simples.
Parte de resposta
A parte da resposta é relativamente simples. No caso mais simples, você só precisa enviar dois registros, ou seja, fcgi_stdout e fcgi_end_request.
Não descreverei o conteúdo específico da entidade, basta olhar para o código:
A cópia do código é a seguinte:
var res = (function () {
var maxlength = math.pow (2, 16);
função buffer0 (len) {
Retorne o novo buffer ((nova matriz (LEN + 1)). JONE ('/U0000'));
};
function writestdout (dados) {
var rcdstdouthd = novo buffer (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;
retornar buffer.concat ([rcdstdouthd, dados, buffer0 (paddingLength)]);
};
função writehttphead () {
Return writestdout (novo buffer ("http/1.1 200 ok/r/content-type: text/html; charset = utf-8/r/nconnection: close/r/n/r/n");
}
função writehttpbody (bodysttr) {
var bodbuffer = [],
corpo = novo tampão (bodysttr);
for (var i = 0, l = body.length; i <l; i + = maxLength + 1) {
bodybuffer.push (writestdout (body.slice (i, i + maxlength)));
}
retornar buffer.Concat (Bodybuffer);
}
function wreatchEnd () {
var rcdendHd = novo buffer (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;
retornar buffer.concat ([rcdendhd, buffer0 (8)]);
}
Função de retorno (dados) {
return buffer.concat ([writehttphead (), writehttpbody (dados), writend ()]);
};
}) ();
No caso mais simples, isso permitirá que você envie uma resposta completa. Altere nosso código final:
A cópia do código é a seguinte:
var visitantes = 0;
Server.on ('Connection', function (Sock) {
Visitantes ++;
Sock.on ('dados', função (dados) {
...
Var consulta = querystring.parse (params.query_string);
var ret = res ('Bem -vindo,' + (querys.name || 'querido amigo') + '! Você é o número' + visitantes + 'documento ~');
Sock.Write (RET);
ret = null;
Sock.end ();
...
});
Abra o navegador e visite: http: // domain/? Name = yekai, e você pode ver algo como "Bem -vindo, yekai! Você é o 7º usuário deste site ~".
Neste ponto, implementamos com sucesso o serviço FastCGI mais simples usando o Node.JS. Se ele precisar ser usado como um serviço real, precisamos apenas comparar as especificações do protocolo para melhorar nossa lógica.
Teste comparativo
Finalmente, a pergunta que precisamos considerar é se essa solução é especificamente viável? Alguns alunos podem ter visto o problema, então colocarei os resultados simples dos testes de pressão em primeiro lugar:
A cópia do código é a seguinte:
// Método fastcgi:
500 clientes, executando 10 segundos.
Velocidade = 27678 páginas/min, 63277 bytes/s.
Solicitações: 3295 SUSECED, 1318 falhou.
500 clientes, executando 20 segundos.
Velocidade = 22131 páginas/min, 63359 bytes/s.
Solicitações: 6523 SUSECED, 854 falhou.
// Método proxy:
500 clientes, executando 10 segundos.
Velocidade = 28752 páginas/min, 73191 bytes/s.
Solicitações: 3724 SUSECED, 1068 falhou.
500 clientes, executando 20 segundos.
Velocidade = 26508 páginas/min, 66267 bytes/s.
Solicitações: 6716 SUSECED, 2120 falhou.
// Acesse diretamente o método de serviço node.js:
500 clientes, executando 10 segundos.
Velocidade = 101154 páginas/min, 264247 bytes/s.
Solicitações: 15729 SUSECED, 1130 falhou.
500 clientes, executando 20 segundos.
Velocidade = 43791 páginas/min, 115962 bytes/s.
Solicitações: 13898 SUSECED, 699 falhou.
Por que o método proxy é melhor do que o método fastcgi? Isso ocorre porque, no esquema de proxy, o serviço de back -end é executado diretamente pelo módulo nativo Node.js, e o esquema fastcgi é implementado por nós mesmos usando JavaScript. No entanto, também se pode observar que não há uma grande lacuna na eficiência entre as duas soluções (é claro, a comparação aqui é apenas uma situação simples. Se a lacuna for maior em cenários de negócios reais) e se o Node.js suportar nativamente os serviços FastCGI, a eficiência deve ser melhor.
PostScript
Se você estiver interessado em continuar a reproduzir, pode verificar o código -fonte dos exemplos que implementei neste artigo. Estudei as especificações do protocolo nos últimos dois dias, mas não é difícil.
Ao mesmo tempo, voltarei e brincarei com o UWSGI, mas o funcionário disse que o V8 já está pronto para apoiá -lo diretamente.
Eu tenho um jogo muito superficial. Se houver algum erro, por favor me corrija e comunique -me.