Dieser Artikel ist eine aktuelle Idee, die ich während des Lernprozesses von Node.js entwickelt habe, und ich werde ihn mit Ihnen diskutieren.
Node.js http Server
Die Verwendung von node.js kann verwendet werden, um einen HTTP -Dienst sehr einfach zu implementieren. Das einfachste Beispiel ist wie das Beispiel einer offiziellen Website:
Die Codekopie lautet wie folgt:
var http = required ('http');
http.createServer (Funktion (req, res) {{
Res.WriteHead (200, {'Inhalts-Typ': 'Text/Plain'});
res.end ('Hello World/n');
}). Hören (1337, '127.0.0.1');
Dies erstellt schnell einen Webdienst, der alle HTTP -Anfragen auf Port 1337 hört.
In einer realen Produktionsumgebung verwenden wir jedoch im Allgemeinen selten Node.js direkt als Front-End-Webserver für Benutzer. Die Hauptgründe sind wie folgt:
1. Basierend auf dem Einzel-Thread-Merkmal von Node.js ist seine Robustheit Garantie für Entwickler relativ hoch.
2. Andere HTTP -Dienste auf dem Server belegen möglicherweise bereits Port 80, und Webdienste, die nicht Port 80 sind, sind offensichtlich nicht benutzerfreundlich genug für Benutzer.
3.Node.js hat keinen großen Vorteil in der Datei -IO -Verarbeitung. Als reguläre Website müssen Sie beispielsweise gleichzeitig auf Dateiressourcen wie Bilder antworten.
4.. Verteilte Lastszenarien sind ebenfalls eine Herausforderung.
Daher ist es möglicherweise wahrscheinlicher, dass die Verwendung von node.js als Webdienst eine Game -Server -Schnittstelle und andere ähnliche Szenarien ist, hauptsächlich für Dienste, für die kein direkter Benutzerzugriff erforderlich ist und nur den Datenaustausch ausführt.
Node.js Webdienst basierend auf Nginx als Front-End-Maschine
Basierend auf den oben genannten Gründen besteht die konventionelle Möglichkeit, es zu verwenden, wenn es sich um ein mit Node.js erstellter Website-förmiges Produkt handelt, darin, einen weiteren reifen HTTP-Server am vorderen Ende des Webdienstes von Node.js zu platzieren, wie z. B. NGINX am häufigsten verwendet.
Verwenden Sie dann Nginx als Reverse Proxy, um auf den Webdienst von Node.js zuzugreifen. wie:
Die Codekopie lautet wie folgt:
Server{
Hören Sie 80;
server_name yekai.me;
root/home/andy/wwwroot/yekai;
Standort / {
proxy_pass http://127.0.0.1:1337;
}
Ort ~ /.(gif|JPG|Png|Swf|ico|cs|Js)$ {
root/home/andy/wwwroot/yekai/static;
}
}
Dies wird besser die oben aufgeworfenen Probleme lösen.
Kommunikation mit FastCGI -Protokoll
Es gibt jedoch einige Dinge, die an der oben genannten Proxy -Methode nicht sehr gut sind.
Eine mögliche Szenarien, die einen direkten HTTP -Zugriff auf den Node.js -Webdienst erfordern, der später gesteuert werden muss. Wenn Sie das Problem jedoch lösen möchten, können Sie auch Ihre eigenen Dienste nutzen oder sich auf Firewall verlassen, um es zu blockieren.
Ein weiterer Grund ist, dass die Proxy-Methode schließlich eine Lösung in der Netzwerkanwendungsschicht ist und es nicht sehr bequem ist, Daten direkt zu erhalten und zu verarbeiten, die mit dem Client HTTP interagieren, z. Dies hängt natürlich auch mit den Funktionen und der funktionalen Perfektion des Proxy -Servers selbst zusammen.
Also dachte ich daran, einen anderen Weg zu versuchen, damit umzugehen. Das erste, woran ich dachte, ist die FastCGI -Methode, die jetzt häufig in PHP -Webanwendungen verwendet wird.
Was ist Fastcgi
Fast Common Gateway Interface (FASTCGI) ist ein Protokoll, mit dem interaktive Programme mit Webservern kommunizieren können.
Der von FASTCGI erzeugte Hintergrund wird als Alternative zu CGI -Webanwendungen verwendet. Eine der offensichtlichsten Funktionen ist, dass ein FastCGI -Serviceprozess verwendet werden kann, um eine Reihe von Anfragen zu bearbeiten. Der Webserver verbindet die Umgebungsvariablen und diese Seitenanforderung an den Webserver über einen Socket wie FastCGI -Prozess. Die Verbindung kann mit der UNIX -Domänen -Socket oder einer TCP/IP -Verbindung mit dem Webserver verbunden werden. Weitere Hintergrundkenntnisse finden Sie im Eintrag von Wikipedia.
FASTCGI -Implementierung von Node.js
Theoretisch müssen wir also nur node.js verwenden, um einen FastCGI -Prozess zu erstellen, und dann festlegen, dass die Höranforderung von NGINX an diesen Prozess gesendet wird. Da Nginx und Node.js beide ereignisgesteuerte Service-Modelle sind, sollten sie "theoretische" Lösungen für die Welt sein. Lass es uns selbst machen.
In Node.js kann das Nettomodul verwendet werden, um einen Socket -Service zu erstellen. Aus Gründen der Bequemlichkeit wählen wir die Unix Socket -Methode.
Mit einer leichten Änderung der Nginx -Konfiguration:
Die Codekopie lautet wie folgt:
...
Standort / {
fastcgi_pass unix: /tmp/node_fcgi.sock;
}
...
Erstellen Sie eine neue Datei node_fcgi.js mit dem folgenden Inhalt:
Die Codekopie lautet wie folgt:
var net = require ('net');
var server = net.createServer ();
server.Listen ('/tmp/node_fcgi.sock');
server.on ('Verbindung', Funktion (Sock) {
console.log ('Verbindung');
Sock.on ('Daten', Funktion (Daten) {
console.log (Daten);
});
});
Leiten Sie dann (aufgrund von Berechtigungen sicher, dass Nginx- und Knotenskripte mit demselben Benutzer oder Konto mit gegenseitigen Berechtigungen ausgeführt werden. Andernfalls werden Sie beim Lesen und Schreiben von Sockendateien auf Berechtigungsprobleme stoßen):
node node_fcgi.js
Bei dem Zugriff auf den Browser sehen wir, dass das Terminal, das das Knotenskript ausführt, normalerweise den Dateninhalt empfängt, wie folgt:
Die Codekopie lautet wie folgt:
Verbindung
<Puffer 01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01 ...>
Dies beweist, dass unsere theoretische Stiftung den ersten Schritt erreicht hat. Als nächstes müssen wir nur herausfinden, wie der Inhalt dieses Puffers analysiert werden kann.
Fastcgi Protocol Foundation
Ein FastCGI -Datensatz besteht aus einem Präfix mit fester Länge, gefolgt von einer variablen Anzahl von Inhalten und gepolsterten Bytes. Die Datensatzstruktur lautet wie folgt:
Die Codekopie lautet wie folgt:
typedef struct {
nicht signierte char -Version;
nicht signiertes Zeichen;
nicht signiertes char requestidb1;
nicht signiertes char requestidb0;
nicht signiertes char contentLengthb1;
nicht signiertes char contentLengthb0;
nicht signiertes Zeichenpaddinglängen;
nicht signiertes Char;
nicht signiert char contentData [contentLength];
nicht signiertes char paddingdata [paddingLength];
} Fcgi_record;
Version: FASTCGI -Protokollversion, nun standardmäßig 1 verwenden
Typ: Der Datensatztyp kann tatsächlich als anderer Zustand angesehen werden und wird später ausführlich besprochen
RequestID: Anforderungs -ID, sie muss bei der Rückgabe entsprechen. Wenn es sich nicht um Multiplex -Parallelität handelt, verwenden Sie einfach 1 hier 1
ContentLength: Inhaltslänge, die maximale Länge hier ist 65535
Polsterlänge: Die Polsterlänge wird verwendet, um die langen Daten in ein ganzzahliges Vielfachen der vollen 8 Bytes zu füllen. Es wird hauptsächlich verwendet, um Daten zu verarbeiten, die effektiver ausgerichtet sind, hauptsächlich für Leistungsüberlegungen
Reserviert: Reservierte Bytes für die anschließende Erweiterung
ContentData: Echte Inhaltsdaten, lassen Sie uns später ausführlich darüber sprechen
PaddingData: Füllen Sie die Daten aus, es ist trotzdem 0, ignorieren Sie sie einfach direkt.
Eine bestimmte Struktur und Beschreibung finden Sie im offiziellen Website-Dokument (http://www.fastcgi.com/devkit/doc/fcgispec.html#s3.3).
Teil anfordern
Es scheint sehr einfach zu sein, analysieren und die Daten auf einmal abrufen. Hier gibt es jedoch eine Grube, dh, was hier definiert ist, ist die Struktur der Dateneinheit (Aufzeichnung), nicht die gesamte Pufferstruktur. Der gesamte Puffer besteht aus einem Rekord und einem Rekord. Zu Beginn ist es für Schüler, die es gewohnt sind, die Entwicklung von Front-End-Entwicklung zu gewöhnen, möglicherweise nicht einfach, aber dies ist die Grundlage für das Verständnis des FastCGI-Protokolls, und wir werden später weitere Beispiele sehen.
Daher müssen wir einen Datensatz analysieren und die Datensätze basierend auf dem zuvor erhaltenen Typ unterscheiden. Hier ist eine einfache Funktion, um alle Datensätze zu erhalten:
Die Codekopie lautet wie folgt:
Funktion getrcds (Daten, cb) {
var rcds = [],
Start = 0,
Länge = Daten.Length;
return function () {
if (start> = länge) {
CB && CB (RCDS);
rcds = null;
zurückkehren;
}
var end = start + 8,
Header = Data.lice (Start, Ende),
Version = Header [0],
Typ = Header [1],
RequestID = (Header [2] << 8) + Header [3],
contentLength = (Header [4] << 8) + Header [5],
paddingLength = header [6];
start = end + contentLength + paddingLength;
var body = contentLength? Data.slice (Ende, ContentLength): NULL;
rcds.push ([Typ, Körper, RequestID]);
return argumente.callee ();
}
}
//verwenden
Sock.on ('Daten', Funktion (Daten) {
getrcds (Daten, Funktion (RCDS) {
}) ();
}
Beachten Sie, dass dies nur ein einfacher Prozess ist. Wenn es komplexe Situationen wie das Hochladen von Dateien gibt, ist diese Funktion für die einfachste Demonstration nicht geeignet. Gleichzeitig wird auch der RequestID -Parameter ignoriert. Wenn es sich um Multiplexe handelt, kann es nicht ignoriert werden und die Verarbeitung muss viel komplizierter sein.
Als nächstes können verschiedene Datensätze entsprechend dem Typ verarbeitet werden. Die Definition des Typs lautet wie folgt:
Die Codekopie lautet wie folgt:
#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)
Als nächstes können Sie die tatsächlichen Daten gemäß dem aufgezeichneten Typ analysieren. Ich werde nur die am häufigsten verwendeten FCGI_PARAMS, FCGI_get_values und fcgi_get_values_result verwenden, um zu veranschaulichen. Glücklicherweise sind ihre Analysemethoden konsistent. Die Analyse anderer Typ -Aufzeichnungen enthält ihre eigenen Regeln, und Sie können auf die Definition der Spezifikation zur Implementierung verweisen. Ich werde hier nicht auf Details eingehen.
Fcgi_params, fcgi_get_values, fcgi_get_values_result sind alle "codierter Name-Wert" -Typdaten. Das Standardformat ist: in Form einer Namenslänge übertragen, gefolgt von der Länge des Wertes, gefolgt vom Namen, gefolgt vom Wert, wobei 127 Bytes oder weniger in einem Byte codiert werden können, während längere Längen immer in vier Bytes codiert werden. Das hohe Bit des ersten Länge Byte zeigt an, wie die Länge codiert wird. Ein hohes Stück 0 bedeutet eine Byte-Codierungsmethode und 1 eine Vier-Byte-Codierungsmethode. Schauen wir uns ein umfassendes Beispiel an, z. B. den Fall langer Namen und kurzen Werte:
Die Codekopie lautet wie folgt:
typedef struct {
nicht signiertes char namelengthb3; / * namelengthb3 >> 7 == 1 */
nicht signiertes char namelengthb2;
nicht signiertes char namelengthb1;
nicht signiertes char namelengthb0;
nicht signiertes Char Valuelengthb0; / * Valuelengthb0 >> 7 == 0 */
vorzeichenloser Zeichen namensata [Neamelength
((B3 & 0x7f) << 24) + (B2 << 16) + (B1 << 8) + B0];
nicht signiertes char valuedata [Valuelength];
} Fcgi_namevaluepair41;
Beispiel für die entsprechende Implementierung JS -Methode Beispiel:
Die Codekopie lautet wie folgt:
Funktion parseparams (Körper) {
var j = 0,
params = {},
Länge = Körper.Length;
while (j <länge) {
var name,
Wert,
Neamelength,
Valolength;
if (Körper [j] >> 7 == 1) {
namelength = ((Körper [j ++] & 0x7f) << 24)+(Körper [J ++] << 16)+(Körper [J ++] << 8)+Körper [J ++];
} anders {
namelength = body [j ++];
}
if (Körper [j] >> 7 == 1) {
Valuelength = ((Körper [J ++] & 0x7f) << 24)+(Körper [J ++] << 16)+(Körper [J ++] << 8)+Körper [J ++];
} anders {
Valuelength = Body [J ++];
}
var ret = body.asciislice (j, j + namelength + valuelength);
name = ret.substring (0, gebetenlänge);
value = ret.substring (Namelength);
Parameter [Name] = Wert;
J + = (Namelength + Valuelength);
}
Rückgabeparameter;
}
Dies implementiert eine einfache Methode, um verschiedene Parameter und Umgebungsvariablen zu erhalten. Verbessern Sie den vorherigen Code und zeigen Sie, wie wir den Client -IP erhalten können:
Die Codekopie lautet wie folgt:
Sock.on ('Daten', Funktion (Daten) {
getrcds (Daten, Funktion (RCDS) {
für (var i = 0, l = rcds.length; i <l; i ++) {
var bodydata = rcds [i],
Typ = BodyData [0],
Body = BodyData [1];
if (body && (type === type.fcgi_params || type === Typen.fcgi_get_values || type === Typen.fcgi_get_values_result)) {
var params = parseparams (Körper);
console.log (params.remote_addr);
}
}
}) ();
}
Bisher haben wir die Grundlagen des Fastcgi -Anfrageteils verstanden und dann den Antwortteil implementieren und schließlich einen einfachen Echo -Antwortdienst abschließen.
Antwortteil
Der Antwortteil ist relativ einfach. Im einfachsten Fall müssen Sie nur zwei Datensätze senden, dh fcgi_stdout und fcgi_end_request.
Ich werde den spezifischen Inhalt der Entität nicht beschreiben, sondern schauen Sie sich den Code an:
Die Codekopie lautet wie folgt:
var res = (function () {
var maxLength = math.pow (2, 16);
Funktion Buffer0 (Len) {
Neuen Puffer zurückgeben ((Neuarray (Len + 1)). Join ('/u0000');
};
Funktion writestdout (Daten) {
var rcdstdouthd = neuer Puffer (8),
contentLength = data.length,
paddingLength = 8 - contentLength % 8;
rcdstdouthd [0] = 1;
rcdstdouthd [1] = Typen.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)]);
};
Funktion writehttphead () {
return writeStout (neuer Buffer ("http/1.1 200 OK/r/nContent-Typ: text/html; charset = utf-8/r/nconnection: close/r/r/r/n"));
}
Funktion writehttpbody (bodystr) {
var bodyBuffer = [],
Body = neuer Puffer (Bodystr);
für (var i = 0, l = body.length; i <l; i + = maxLength + 1) {
BodyBuffer.push (WriteStout (Body.Slice (i, i + maxLength)));
}
return buffer.concat (bodyBuffer);
}
Funktion writeend () {
var rcdendhd = neuer Puffer (8);
rcdendhd [0] = 1;
rcdendhd [1] = Typen.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)]);
}
Rückgabefunktion (Daten) {
return buffer.concat ([writeHttphead (), writehttpbody (data), writeend ()]);
};
}) ();
Im einfachsten Fall können Sie eine vollständige Antwort senden. Ändern Sie unseren endgültigen Code:
Die Codekopie lautet wie folgt:
var Besucher = 0;
server.on ('Verbindung', Funktion (Sock) {
Besucher ++;
Sock.on ('Daten', Funktion (Daten) {
...
var querys = queryString.parse (params.Query_string);
var ret = res ('Willkommen,' + (querys.name || 'Lieber Freund') + '! Du bist die Nummer' + Besucher + 'Dokument ~');
Sock.Write (ret);
ret = null;
Sock.end ();
...
});
Öffnen Sie den Browser und besuchen Sie: http: // domain/? Name = yekai, und Sie können so etwas wie "Willkommen, Yekai! Sie sind der 7. Benutzer dieser Website ~".
Zu diesem Zeitpunkt haben wir den einfachsten FastCGI -Dienst mit Node.js. erfolgreich implementiert. Wenn es als realer Service verwendet werden muss, müssen wir nur die Protokollspezifikationen vergleichen, um unsere Logik zu verbessern.
Vergleichstest
Schließlich ist die Frage, die wir berücksichtigen müssen, ob diese Lösung spezifisch machbar ist. Einige Schüler haben das Problem möglicherweise gesehen, daher werde ich zuerst die einfachen Druckprüfergebnisse einsetzen:
Die Codekopie lautet wie folgt:
// Fastcgi -Methode:
500 Kunden, 10 Sekunden lang.
Geschwindigkeit = 27678 Seiten/min, 63277 Bytes/Sek.
Anfragen: 3295 Sussece, 1318 fehlgeschlagen.
500 Kunden, 20 Sekunden lang.
Geschwindigkeit = 22131 Seiten/min, 63359 Bytes/Sek.
Anfragen: 6523 Sussece, 854 fehlgeschlagen.
// Proxy -Methode:
500 Kunden, 10 Sekunden lang.
Geschwindigkeit = 28752 Seiten/min, 73191 Bytes/Sek.
Anfragen: 3724 SuSprece, 1068 fehlgeschlagen.
500 Kunden, 20 Sekunden lang.
Geschwindigkeit = 26508 Seiten/min, 66267 Bytes/Sek.
Anfragen: 6716 SuSprece, 2120 fehlgeschlagen.
// Zugriff direkt auf node.js Service -Methode:
500 Kunden, 10 Sekunden lang.
Geschwindigkeit = 101154 Seiten/min, 264247 Bytes/Sek.
Anfragen: 15729 Suspeprece, 1130 fehlgeschlagen.
500 Kunden, 20 Sekunden lang.
Geschwindigkeit = 43791 Seiten/min, 115962 Bytes/Sek.
Anfragen: 13898 Suspeds, 699 fehlgeschlagen.
Warum ist die Proxy -Methode besser als die FastCGI -Methode? Das liegt daran, dass der Backend -Service im Rahmen des Proxy -Schemas direkt vom nativen Modul von Node.js ausgeführt wird und das FastCGI -Schema mit JavaScript von uns selbst implementiert wird. Es ist jedoch auch ersichtlich, dass zwischen den beiden Lösungen keine große Effizienzlücke besteht (natürlich ist der Vergleich hier nur eine einfache Situation. Wenn die Lücke in realen Geschäftsszenarien größer ist), wenn Node.js FastCGI -Dienste nativ unterstützt, sollte die Effizienz besser sein.
PostScript
Wenn Sie weiterhin spielen möchten, können Sie den Quellcode der in diesem Artikel implementierten Beispiele überprüfen. Ich habe die Protokollspezifikationen in den letzten zwei Tagen untersucht, aber es ist nicht schwierig.
Gleichzeitig werde ich zurückgehen und mit UWSGI spielen, aber der Beamte sagte, dass V8 bereits bereit ist, ihn direkt zu unterstützen.
Ich habe ein sehr flaches Spiel. Wenn es einen Fehler gibt, korrigieren Sie mich bitte und kommunizieren Sie.