Heim > Artikel > Web-Frontend > Implementieren Sie eine einfache FastCGI-Serverinstanz mit Node.js_node.js
Dieser Artikel ist eine Idee, die mir während meines letzten Studiums von Node.js gekommen ist, und ich möchte sie gerne mit allen diskutieren.
HTTP-Server für Node.js
Es ist sehr einfach, einen HTTP-Dienst mit Node.js zu implementieren. Das einfachste Beispiel ist auf der offiziellen Website zu sehen:
1. Aufgrund der Single-Threaded-Natur von Node.js müssen die Entwickler eine relativ hohe Garantie für seine Robustheit haben.
2. Möglicherweise gibt es andere http-Dienste auf dem Server, die Port 80 belegt haben, und andere Webdienste als Port 80 sind offensichtlich nicht benutzerfreundlich genug.
3.Node.js hat bei der Datei-E/A-Verarbeitung keine großen Vorteile. Als normale Website muss es beispielsweise möglicherweise gleichzeitig auf Bild- und andere Dateiressourcen reagieren.
4. Das verteilte Lastszenario ist ebenfalls eine Herausforderung.
Daher wird die Verwendung von Node.js als Webdienst eher als Spieleserverschnittstelle und in anderen ähnlichen Szenarien verwendet. Die meisten davon befassen sich mit Diensten, die keinen direkten Zugriff durch Benutzer erfordern und nur zum Datenaustausch verwendet werden .
Node.js-Webdienst basierend auf Nginx als Front-End-Maschine
Wenn es sich aus den oben genannten Gründen um ein Website-förmiges Produkt handelt, das mit Node.js erstellt wurde, besteht die herkömmliche Verwendung darin, einen anderen ausgereiften HTTP-Server am Front-End des Node.js-Webdienstes zu platzieren, z. B. Nginx wird am häufigsten verwendet.
Verwenden Sie dann Nginx als Reverse-Proxy, um auf den Node.js-basierten Webdienst zuzugreifen. Zum Beispiel:
Standort / {
Proxy_Pass http://127.0.0.1:1337;
}
Standort ~ .(gif|jpg|png|swf|ico|css|js)$ {
root /home/andy/wwwroot/yekai/static;
}
}
Dadurch werden die oben genannten Probleme besser gelöst.
Kommunikation über das FastCGI-Protokoll
Allerdings hat die oben genannte Proxy-Methode auch einige Nachteile.
Ein mögliches Szenario besteht darin, dass Sie den direkten HTTP-Zugriff auf den zugrunde liegenden Node.js-Webdienst steuern müssen. Wenn Sie das Problem jedoch lösen möchten, können Sie auch Ihren eigenen Dienst nutzen oder auf die Firewall-Blockierung setzen.
Der andere Grund ist, dass die Proxy-Methode schließlich eine Lösung auf der Netzwerkanwendungsebene ist und es nicht sehr praktisch ist, die Daten, die mit dem Client-HTTP interagieren, direkt abzurufen und zu verarbeiten, beispielsweise die Keep-Alive-Verarbeitung , Kofferraum und sogar Kekse. Dies hängt natürlich auch mit der Leistungsfähigkeit und Funktionsperfektion des Proxy-Servers selbst zusammen.
Also dachte ich darüber nach, eine andere Verarbeitungsmethode auszuprobieren. Das erste, was mir in den Sinn kam, war die FastCGI-Methode, die heute häufig in PHP-Webanwendungen verwendet wird.
Was ist FastCGI
Fast Common Gateway Interface/FastCGI ist ein Protokoll, das interaktiven Programmen die Kommunikation mit Webservern ermöglicht.
Der Hintergrund von FastCGI besteht darin, als Alternative zu CGI-Webanwendungen zu dienen. Eine der offensichtlichsten Funktionen besteht darin, dass ein FastCGI-Dienstprozess verwendet werden kann, um eine Reihe von Anforderungen zu verarbeiten. Der Webserver übergibt die Umgebungsvariablen Seitenanforderung über einen Socket, z. B. Der FastCGI-Prozess stellt über einen Unix-Domain-Socket oder eine TCP/IP-Verbindung eine Verbindung zum Webserver her. Weiteres Hintergrundwissen finden Sie im Wikipedia-Eintrag.
FastCGI-Implementierung für Node.js
Theoretisch müssen wir nur Node.js verwenden, um einen FastCGI-Prozess zu erstellen, und dann die Nginx-Überwachungsanforderung angeben, die an diesen Prozess gesendet werden soll. Da Nginx und Node.js beide auf ereignisgesteuerten Servicemodellen basieren, sollten sie „theoretisch“ eine natürliche Lösung sein. Lassen Sie es uns unten selbst umsetzen.
Das Netzmodul in Node.js kann zum Erstellen eines Socket-Dienstes verwendet werden. Der Einfachheit halber wählen wir die Unix-Socket-Methode.
Ändern Sie die Konfiguration auf der Nginx-Seite leicht:
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);
});
});
Dann ausführen (aus Berechtigungsgründen stellen Sie bitte sicher, dass die Nginx- und Node-Skripte von demselben Benutzer oder Konto mit gegenseitigen Berechtigungen ausgeführt werden, da sonst beim Lesen und Schreiben von Sock-Dateien Berechtigungsprobleme auftreten):
node node_fcgi.js
Beim Zugriff im Browser sehen wir, dass das Terminal, auf dem das Node-Skript ausgeführt wird, den Dateninhalt normal empfängt, wie zum Beispiel diesen:
Dies beweist, dass unsere theoretische Grundlage den ersten Schritt erreicht hat. Als nächstes müssen wir nur noch herausfinden, wie der Inhalt dieses Puffers analysiert wird.
Grundlagen des FastCGI-Protokolls
FastCGI-Datensätze bestehen aus einem Präfix fester Länge, gefolgt von einer variablen Menge an Inhalten und Füllbytes. Die Datensatzstruktur ist wie folgt:
Version: FastCGI-Protokollversion, jetzt ist der Standardwert 1. Im Fall von Multiplexing und Parallelität verwenden Sie hier einfach 1
contentLength: Inhaltslänge, die maximale Länge beträgt hier 65535
paddingLength: Auffülllänge, die Funktion besteht darin, lange Daten auf ein ganzzahliges Vielfaches von 8 Bytes zu füllen. Wird hauptsächlich zur effizienteren Verarbeitung von Daten verwendet, die ausgerichtet bleiben, hauptsächlich aus Leistungsgründen.
reserviert: reservierte Bytes für die spätere Erweiterung
contentData: echte Inhaltsdaten, die wird später ausführlich besprochen
paddingData: Fülldaten auf jeden Fall Sie sind alle 0, ignorieren Sie sie einfach.
Eine genaue Struktur und Beschreibung finden Sie im offiziellen Website-Dokument (http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.3).
Anfragebereich
Es scheint sehr einfach zu sein, analysieren Sie es einfach einmal und erhalten Sie die Daten. Hier gibt es jedoch eine Gefahr: Hier wird die Struktur der Dateneinheit (Datensatz) definiert, nicht die Struktur des gesamten Puffers. Der gesamte Puffer besteht aus einem Datensatz und einem Datensatz. Für diejenigen von uns, die an Front-End-Entwicklung gewöhnt sind, ist es zunächst vielleicht nicht leicht zu verstehen, aber dies ist die Grundlage für das Verständnis des FastCGI-Protokolls, und wir werden später weitere Beispiele sehen.
var body = contentLength ? data.slice(end, contentLength) : null;
rcds.push([type, body, requestId]);
return arguments.callee();
}
}
//使用
sock.on('data', function(data){
getRcds(data, function(rcds ){
})();
}
注意这里只是简单处理, 如果有上传文件等复杂情况这个函数不适应, 为了最简演示就先简便处理了.同时, 也忽略了requestId参数, 如果是多路复用的情况下不能忽略,并且处理会需要复杂得多.
接下来就可以根据type来对不同的记录进行处理了。type的定义如下:
接下来就可以根据记录的type来解析拿到真正的数据, 下面我只拿最常用的FCGI_PARAMS、FCGI_GET_VALUES、FCGI_GET_VALUES_ RESULT来说明,好在他们的解析方式是一致的.其他type记录的解析有自己不同的规则, 可以参考规范的定义实现, 我这里就不细说了.
FCGI_PARAMS、FCGI_GET_VALUES、FCGI_GET_VALUES_RESULT都是“编码名-值“类型数据,标准格式为:以名字长度,后跟值的长度, 后跟名字, 后跟值的形式传送, 其中127字节或更少的长度能在一字节中编码,而更长的长度总是在四字节中编码.长度的第一字节的高位指示长度的编码方式.高位为0意味着一个字节的编码方式, 1意味着四字节的编码方式.看个综合的例子,比如长名短值的情况:
对应的实现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] = Wert;
j = (nameLength valueLength);
}
Rückgabeparameter;
}
这样就实现了一个简单可获取各种参数和环境变量的方法.完善前面的代码,演示我们如何获取客户端ip:
Jetzt haben wir die Grundlagen des FastCGI-Anfrageteils verstanden. Als nächstes implementieren wir den Antwortteil und vervollständigen schließlich einen einfachen Echo-Antwortdienst.
Antwortabschnitt
Der Antwortteil ist relativ einfach. Im einfachsten Fall müssen Sie nur zwei Datensätze senden, nämlich FCGI_STDOUT und FCGI_END_REQUEST.
Ich werde nicht im Detail auf den spezifischen Inhalt der aufgezeichneten Entität eingehen, schauen Sie sich einfach den Code an:
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 - contendLength % 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(new Buffer("HTTP/1.1 200 OKrnContent-Type:text/html; charset=utf-8rnConnection: closernrn"));
}
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()]);
};
Im einfachsten Fall kann so eine vollständige Antwort versendet werden. Ändern Sie unseren endgültigen Code:
Kopieren Sie den Code
Vergleichstest
Abschließend müssen wir uns noch die Frage stellen, ob diese Lösung machbar ist. Einige Schüler haben das Problem möglicherweise bemerkt, daher werde ich zuerst die einfachen Stresstestergebnisse veröffentlichen:
500 Clients, 20 Sek. ausgeführt.
Geschwindigkeit = 22131 Seiten/Min., 63359 Bytes/Sek.
Anfragen: 6523 erfolgreich, 854 fehlgeschlagen.
//Proxy-Modus:
500 Clients, 10 Sek. ausgeführt.
Geschwindigkeit = 28752 Seiten/Min., 73191 Bytes/Sek.
Anfragen: 3724 erfolgreich, 1068 fehlgeschlagen.
500 Clients, 20 Sek. ausgeführt.
Geschwindigkeit = 26508 Seiten/Min., 66267 Bytes/Sek.
Anfragen: 6716 erfolgreich, 2120 fehlgeschlagen.
//Direkter Zugriff auf die Dienstmethode von Node.js:
500 Clients, 10 Sek. ausgeführt.
Geschwindigkeit = 101154 Seiten/Min., 264247 Bytes/Sek.
Anfragen: 15729 erfolgreich, 1130 fehlgeschlagen.
500 Clients, 20 Sek. ausgeführt.
Geschwindigkeit = 43791 Seiten/Min., 115962 Bytes/Sek.
Anfragen: 13898 erfolgreich, 699 fehlgeschlagen.
Postskriptum
Wenn Sie daran interessiert sind, weiterzuspielen, können Sie den Quellcode des Beispiels überprüfen, das ich in diesem Artikel implementiert habe. Ich habe die Protokollspezifikationen in den letzten zwei Tagen studiert, und es ist nicht schwierig.
Gleichzeitig werde ich zurückgehen und mich darauf vorbereiten, wieder mit uWSGI zu spielen, aber der Beamte sagte, dass v8 sich bereits darauf vorbereitet, es direkt zu unterstützen.
Das Spiel ist sehr einfach. Wenn es Fehler gibt, korrigieren Sie mich bitte und teilen Sie mir mit.