Cet article est une idée qui m'est venue lors de ma récente étude de Node.js, et j'aimerais en discuter avec tout le monde.
Serveur HTTP pour Node.js
Il est très simple d'implémenter un service http en utilisant Node.js. L'exemple le plus simple est celui présenté sur le site officiel :
2. Il peut y avoir d'autres services http sur le serveur qui ont occupé le port 80, et les services Web autres que le port 80 ne sont évidemment pas assez conviviaux.
3.Node.js n'a pas beaucoup d'avantages dans le traitement des E/S de fichiers. Par exemple, en tant que site Web classique, il peut avoir besoin de répondre aux images et à d'autres ressources de fichiers en même temps.
4. Le scénario de charge distribuée est également un défi.
Service Web Node.js basé sur Nginx comme machine frontale
Pour les raisons ci-dessus, s'il s'agit d'un produit en forme de site Web construit à l'aide de Node.js, l'utilisation conventionnelle consiste à placer un autre serveur http mature sur le front-end du service Web Node.js, tel que Nginx, qui est le plus couramment utilisé.Utilisez ensuite Nginx comme proxy inverse pour accéder au service Web basé sur Node.js. Tel que :
proxy_pass http://127.0.0.1:1337;
}
root /home/andy/wwwroot/yekai/static;
}
}
Communication via le protocole FastCGI
Cependant, la méthode proxy mentionnée ci-dessus présente également certains inconvénients.Un scénario possible est que vous deviez contrôler l'accès http direct au service Web Node.js sous-jacent. Cependant, si vous souhaitez le résoudre, vous pouvez également utiliser votre propre service ou compter sur le blocage du pare-feu.
L'autre raison est que la méthode proxy est après tout une solution sur la couche application réseau, et il n'est pas très pratique d'obtenir et de traiter directement les données qui interagissent avec le client http, comme le traitement du keep-alive , le coffre et même les cookies. Bien entendu, cela est également lié aux capacités et à la perfection fonctionnelle du serveur proxy lui-même.
Je pensais donc essayer une autre méthode de traitement. La première chose qui m'est venue à l'esprit était la méthode FastCGI qui est maintenant couramment utilisée dans les applications Web php.
Qu'est-ce que FastCGI
Fast Common Gateway Interface/FastCGI est un protocole qui permet aux programmes interactifs de communiquer avec des serveurs Web.L'arrière-plan de FastCGI est de servir d'alternative aux applications Web cgi. L'une des fonctionnalités les plus évidentes est qu'un processus de service FastCGI peut être utilisé pour gérer une série de requêtes. Le serveur Web transmettra les variables d'environnement et les données. demande de page via un socket, tel que Le processus FastCGI se connecte au serveur Web via un socket de domaine Unix ou une connexion TCP/IP. Pour plus de connaissances générales, veuillez vous référer à l'entrée Wikipédia.
Implémentation FastCGI pour Node.js
Théoriquement, il suffit d'utiliser Node.js pour créer un processus FastCGI, puis de spécifier la requête de surveillance Nginx à envoyer à ce processus. Étant donné que Nginx et Node.js sont tous deux basés sur des modèles de services événementiels, « en théorie », ils devraient constituer une solution naturelle. Implémentons-le nous-mêmes ci-dessous.Le module net dans Node.js peut être utilisé pour créer un service socket Pour plus de commodité, nous choisissons la méthode socket unix.
Modifiez légèrement la configuration côté Nginx :
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);
});
});
Ensuite, exécutez (pour des raisons d'autorisation, veuillez vous assurer que les scripts Nginx et node sont exécutés par le même utilisateur ou par des comptes avec des autorisations mutuelles, sinon vous rencontrerez des problèmes d'autorisation lors de la lecture et de l'écriture de fichiers sock) :
node node_fcgi.js
Lors de l'accès dans le navigateur, nous voyons que le terminal exécutant le script de nœud reçoit normalement le contenu des données, comme ceci :
Cela prouve que notre fondement théorique a franchi la première étape. Il ne nous reste plus qu'à comprendre comment analyser le contenu de ce tampon.
Bases du protocole FastCGI
Les enregistrements FastCGI se composent d'un préfixe de longueur fixe suivi d'une quantité variable de contenu et d'octets de remplissage. La structure de l'enregistrement est la suivante :
version : version du protocole FastCGI, maintenant la valeur par défaut est 1. Dans le cas du multiplexage et de la concurrence, utilisez simplement 1 ici
contentLength : longueur du contenu, la longueur maximale ici est 65535
paddingLength : longueur de remplissage, la fonction consiste à remplir des données longues jusqu'à un multiple entier de 8 octets, principalement utilisé pour traiter les données qui restent alignées plus efficacement, principalement en raison de considérations de performances
réservé : octets réservés, pour une expansion ultérieure
contentData : données de contenu réel, qui sera discuté en détail plus tard
paddingData : remplissage des données, de toute façon. Ils sont tous à 0, ignorez-les simplement.
Pour une structure et une description spécifiques, veuillez vous référer au document du site officiel (http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.3).
Section Demande
Cela semble très simple, il suffit de l'analyser une fois et d'obtenir les données. Cependant, il y a un piège ici, c'est-à-dire que ce qui est défini ici est la structure de l'unité de données (enregistrement), et non la structure de l'ensemble du tampon. L'ensemble du tampon est composé d'un enregistrement et d'un enregistrement. Cela n'est peut-être pas facile à comprendre au début pour ceux d'entre nous qui sont habitués au développement front-end, mais c'est la base pour comprendre le protocole FastCGI, et nous verrons plus d'exemples plus tard.
var body = contentLength ? data.slice(end, contentLength) : null;
rcds.push([type, body, requestId]);
return arguments.callee();
antecedents ){
})();
}
注意这里只是简单处理,如果有上传文件等复杂情况这个函数不适应,为了最简演示就先简Il s'agit d'une demande d'identification de demande. ,并且处理会需要复杂得多。
接下来就可以根据type来对不同的记录进行处理了。type的定义如下:
复制代码
对应的实现js方法示例:
if(body[j] >> 7 == 1){
valueLength = ((body[j ] & 0x7f) << 24) (body[j ] << 16) (body[j ] << 8) body[j ];
} else {
valueLength = body[j ];
}
var ret = body.asciiSlice(j, j nameLength valueLength);
name = ret.substring(0, nameLength);
value = ret.substring(nameLength);
params[name] = valeur ;
j = (nameLength valueLength);
}
return params;
}
这样就实现了一个简单可获取各种参数和环境变量的方法。完善前面的代码,演示我们如何获取客户端ip:
Maintenant, nous avons compris les bases de la partie requête FastCGI. Ensuite, nous allons implémenter la partie réponse et enfin compléter un service de réponse d'écho simple.
Section Réponse
La partie réponse est relativement simple. Dans le cas le plus simple, vous n'avez besoin d'envoyer que deux enregistrements, qui sont FCGI_STDOUT et FCGI_END_REQUEST.
Je n'entrerai pas dans les détails sur le contenu spécifique de l'entité enregistrée, il suffit de regarder le code :
function buffer0(len){
return new Buffer((new Array(len 1)).join('u0000'));
};
function writeStdout(data){
var rcdStdoutHd = new Buffer(8),
contendLength = data.length,
paddingLength = 8 - contestLength % 8;
rcdStdoutHd[0] = 1;
rcdStdoutHd[1] = TYPES.FCGI_STDOUT;
rcdStdoutHd[2] = 0;
rcdStdoutHd[3] = 1;
rcdStdoutHd[4] = contestLength >> 8;
rcdStdoutHd[5] = contestLength;
rcdStdoutHd[6] = paddingLength;
rcdStdoutHd[7] = 0;
return Buffer.concat([rcdStdoutHd, data, buffer0(paddingLength)]);
};
function writeHttpHead(){
return writeStdout(new Buffer("HTTP/1.1 200 OKrnContent-Type:text/html; charset=utf-8rnConnection: closenrn"));
}
function writeHttpBody(bodyStr){
var bodyBuffer = [],
body = new Buffer(bodyStr);
for(var i = 0, l = body.length; i < l; i = MaxLength 1){
function writeEnd(){
var rcdEndHd = new Buffer(8);
rcdEndHd[0] = 1;
rc dEndHd[2] = 0;
rcdEndHd[3] = 1;
rcdEndHd[4] = 0;
rcdEndHd[5] = 8;
rcdEndHd[6] = 0;
rcd EndHd[7 ] = 0;
return Buffer.concat([rcdEndHd, buffer0(8)]);
}
return function(data){
return Buffer.concat([writeHttpHead(), writeHttpBody(data), writeEnd()]);
};
Dans le cas le plus simple, cela permet d'envoyer une réponse complète. Modifiez notre code final :
Copiez le code
Test de comparaison
Enfin, la question que nous devons nous poser est de savoir si cette solution est réalisable ? Certains étudiants ont peut-être remarqué le problème, je publierai donc d'abord les résultats simples du test d'effort :
500 clients, exécutant 20 secondes.
Vitesse=22 131 pages/min, 63 359 octets/s.
Requêtes : 6 523 réussites, 854 échecs.
//Mode proxy :
500 clients, exécutant 10 secondes.
Vitesse=28 752 pages/min, 73 191 octets/sec.
Requêtes : 3 724 réussites, 1 068 échecs.
500 clients, exécutant 20 secondes.
Vitesse=26 508 pages/min, 66 267 octets/s.
Requêtes : 6 716 ont réussi, 2 120 ont échoué.
//Accès direct à la méthode de service Node.js :
500 clients, exécutant 10 secondes.
Vitesse=101154 pages/min, 264247 octets/sec.
Requêtes : 15729 réussies, 1130 échouées.
500 clients, exécutant 20 secondes.
Vitesse=43791 pages/min, 115962 octets/sec.
Requêtes : 13898 ont réussi, 699 ont échoué.
Post-scriptum
Si vous souhaitez continuer à jouer, vous pouvez consulter le code source de l'exemple que j'ai implémenté dans cet article. J'ai étudié les spécifications du protocole au cours des deux derniers jours, et ce n'est pas difficile.
En même temps, je vais y retourner et me préparer à jouer à nouveau avec uWSGI, mais le responsable a dit que la v8 se prépare déjà à la prendre en charge directement.
Le jeu est très simple. S'il y a des erreurs, veuillez me corriger et communiquer.