BigPipe ist eine von Facebook entwickelte Technologie, um die Ladegeschwindigkeit der Webseite zu optimieren. Mit Node.js im Internet sind fast keine Artikel implementiert. Tatsächlich ist es nicht nur Node.js. Bigpipe -Implementierungen in anderen Sprachen sind im Internet selten. So lange nach dem Auftreten dieser Technologie dachte ich, dass ich nach dem ersten Senden des gesamten Webseiten -Frameworks eine andere oder mehrere AJAX -Anfragen verwendet habe, um die Module auf der Seite anzufordern. Erst vor nicht allzu langer Zeit erfuhr ich, dass das Kernkonzept von BigPipe darin besteht, nur eine HTTP -Anfrage zu verwenden, aber die Seitenelemente werden in der Reihenfolge gesendet.
Es wird leicht dieses Kernkonzept verstehen. Dank der asynchronen Funktion von node.js ist es einfach, BigPipe mit node.js. In diesem Artikel werden Beispiele Schritt für Schritt verwendet, um die Ursachen der BigPipe -Technologie und eine einfache Implementierung basierend auf node.js. zu veranschaulichen.
Ich werde Express verwenden, um zu demonstrieren. Der Einfachheit halber wählen wir JADE als Vorlagenmotor und verwenden nicht die Sub-Template-Funktion (teilweise) der Engine, sondern verwenden die untergeordnete Vorlage, um HTML als übergeordnete Vorlagendaten zu rendern.
Erstellen Sie zuerst einen NodeJS-Bigpipe-Ordner und schreiben Sie wie folgt eine Paket.json-Datei:
Die Codekopie lautet wie folgt:
{
"Name": "BigPipe-Experiment"
, "Version": "0.1.0"
, "privat": wahr
, "Abhängigkeiten": {
"Express": "3.xx"
, "Konsolidierung": "Neueste"
"Jade": "Neueste"
}
}
Führen Sie die NPM -Installation aus, um diese drei Bibliotheken zu installieren. Konsolidierung wird verwendet, um die Anrufe zu erleichtern.
Lassen Sie uns zuerst den einfachsten Versuch machen, zwei Dateien:
app.js:
Die Codekopie lautet wie folgt:
var express = require ('express')
, Cons = fordert ('Consolidate')
, jade = fordert ('jade')
, Path = fordert ('Pfad')
var app = express ()
App.Engine ('Jade', con.jade)
app.set ('Ansichten', Path.Join (__ DirName, 'Ansichten'))
app.set ('Ansicht Engine', 'Jade')
app.use (Funktion (req, res) {
res.render ('layout', {
S1: "Hallo, ich bin der erste Abschnitt."
, S2: "Hallo, ich bin der zweite Abschnitt."
})
})
App.Listen (3000)
Ansichten/Layout.jade
Die Codekopie lautet wie folgt:
docType html
Kopf
Titel Hallo, Welt!
Stil
Abschnitt {
Rand: 20px Auto;
Grenze: 1PX gepunktet grau;
Breite: 80%;
Höhe: 150px;
}
Abschnitt#S1! = S1
Abschnitt#S2! = S2
Die Effekte sind wie folgt:
Als nächstes setzen wir zwei Abschnittsvorlagen in zwei verschiedene Vorlagendateien ein:
Ansichten/s1.jade:
Die Codekopie lautet wie folgt:
H1 Teil 1
.Content! = Inhalt
Ansichten/S2.jade:
Die Codekopie lautet wie folgt:
H1 Partial 2
.Content! = Inhalt
Fügen Sie zum Layout.jade -Stil einige Stile hinzu
Die Codekopie lautet wie folgt:
Abschnitt H1 {
Schriftgröße: 1,5;
Polsterung: 10px 20px;
Rand: 0;
Grenzboden: 1PX gepunktet grau;
}
Abschnitt div {
Rand: 10px;
}
Ändern Sie die App.Use () -Teil von App.js in:
Die Codekopie lautet wie folgt:
var temp = {
S1: jade.comPile (fs.readFilesync (Path.Join (__ Dirname, 'Views', 'S1.jade')))
, s2: jade.compile (fs.readFilesync (Path.Join (__ Dirname, 'Views', 'S2.jade')))
}
app.use (Funktion (req, res) {
res.render ('layout', {
S1: temp.s1 ({Inhalt: "Hallo, ich bin der erste Abschnitt."})
, S2: temp.s2 ({Inhalt: "Hallo, ich bin der zweite Abschnitt."})
})
})
Wir sagten vorher: "Die HTML nach dem Rendering wird mit der untergeordneten Vorlage als Daten der übergeordneten Vorlage abgeschlossen", was bedeutet, dass die beiden Methoden temp.s1 und temp.s2 HTML -Code für die beiden Dateien s1.jade und s2.jade generieren und dann diese beiden Code -Werte der beiden Variablen S1 und S2 in Layout verwenden.
Jetzt sieht die Seite so aus:
Im Allgemeinen werden die Daten der beiden Abschnitte separat erhalten - sei es durch Abfragen der Datenbank oder durch erholsame Anforderung, dass wir zwei Funktionen wie asynchrone Operationen verwenden.
Die Codekopie lautet wie folgt:
var getData = {
D1: Funktion (fn) {
setTimeout (fn, 3000, null, {Inhalt: "Hallo, ich bin der erste Abschnitt."})
}
, D2: Funktion (fn) {
setTimeout (fn, 5000, null, {Inhalt: "Hallo, ich bin der zweite Abschnitt."})
}
}
Auf diese Weise ist die Logik in App.Use () komplizierter und der einfachste Weg, um damit umzugehen, ist:
Die Codekopie lautet wie folgt:
app.use (Funktion (req, res) {
getData.d1 (Funktion (Err, S1Data) {
getData.d2 (Funktion (Err, S2Data) {
res.render ('layout', {
S1: temp.s1 (S1Data)
, S2: temp.s2 (S2Data)
})
})
})
})
Dies wird auch die gewünschten Ergebnisse erhalten, aber in diesem Fall dauert es volle 8 Sekunden, die Rückkehr.
Tatsächlich zeigt die Implementierungslogik, dass getData.d2 anfängt, nach dem Ergebnis von getData.d1 zurückgegeben zu werden, und sie haben keine solche Abhängigkeit. Wir können Bibliotheken wie Async verwenden, die mit JavaScript asynchronen Aufrufen umgehen, um dieses Problem zu lösen, aber lassen Sie es einfach hierher schreiben:
Die Codekopie lautet wie folgt:
app.use (Funktion (req, res) {
var n = 2
, result = {}
getData.d1 (Funktion (Err, S1Data) {
result.s1data = s1data
--N || writersult ()
})
getData.d2 (Funktion (Err, S2Data) {
result.s2data = s2data
--N || writersult ()
})
Funktion writersult () {
res.render ('layout', {
S1: temp.s1 (result.s1data)
, s2: temp.s2 (result.s2data)
})
}
})
Dies dauert nur 5 Sekunden.
Vor der nächsten Optimierung fügen wir die JQuery -Bibliothek hinzu und geben den CSS -Stil in externe Dateien ein. Übrigens werden wir die Datei runTime.js -Datei hinzufügen, die für die Verwendung der JADE -Vorlage, die wir später verwenden, benötigt und sie im Verzeichnis mit App.js ausführen:
Die Codekopie lautet wie folgt:
Mkdir statisch
CD statisch
curl http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
Nehmen Sie den Code im Style -Tag in layout.jade heraus und setzen Sie ihn in static/style.css und ändern Sie das Kopf -Tag in:
Die Codekopie lautet wie folgt:
Kopf
Titel Hallo, Welt!
link (href = "/static/style.css", rel = "stylesheet"))
Skript (src = "/static/jQuery.js"))
Skript (src = "/static/jade.js"))
In App.js simulieren wir beide Download -Geschwindigkeiten auf zwei Sekunden und fügten vor App.use (Funktion (Req, Res) {: hinzugefügt.
Die Codekopie lautet wie folgt:
var static = express.static (Path.Join (__ DirName, 'static'))
app.use ('/static', function (req, res, next) {
setTimeout (static, 2000, Req, Res, Weiter)
})
Aufgrund externer statischer Dateien hat unsere Seite nun eine Ladezeit von etwa 7 Sekunden.
Wenn wir den Kopfteil zurückgeben, sobald wir eine HTTP -Anfrage erhalten und dann zwei Abschnitte warten, bis der asynchrone Betrieb vor der Rückkehr abgeschlossen ist, verwendet dies die blockierte Getriebecodierungsmechanismus von HTTP. In Node.js wird die Methode res.Write (), solange Sie die RES.Write () -Methode verwenden, automatisch der Übertragungskodierende: Chunked Header hinzugefügt. Während der Browser die statische Datei lädt, wartet der Knotenserver auf das Ergebnis des asynchronen Aufrufs. Lassen Sie uns zunächst die beiden Zeilen des Abschnitts in Layout.jade löschen:
Die Codekopie lautet wie folgt:
Abschnitt#S1! = S1
Abschnitt#S2! = S2
Daher müssen wir dieses Objekt in res.render () {s1:…, s2:…} nicht angeben und da res.render () standardmäßig res.end () aufgerufen wird, müssen wir die Rückruffunktion manuell festlegen, nachdem das Rendern abgeschlossen ist, und die RES.Write () -Methode in dieser Verwendung verwenden. Der Inhalt von layout.jade muss nicht in der Callback -Funktion writersult () sein. Wir können es zurückgeben, wenn wir diese Anfrage erhalten. Beachten Sie, dass wir den Header vom Typ Inhalt manuell hinzugefügt haben:
Die Codekopie lautet wie folgt:
app.use (Funktion (req, res) {
res.render ('layout', function (err, str) {
if (err) return res.req.next (err)
res.setheader ('Inhaltstyp', 'text/html; charset = utf-8')
res.write (str)
})
var n = 2
getData.d1 (Funktion (Err, S1Data) {
Res.Write ('<Abschnitt ID = "S1">' + temp.s1 (s1data) + '</section>')
--N || res.end ()
})
getData.d2 (Funktion (Err, S2Data) {
Res.Write ('<Abschnitt ID = "S2">' + temp.s2 (S2Data) + '</section>')
--N || res.end ()
})
})
Jetzt liegt die endgültige Ladegeschwindigkeit auf etwa 5 Sekunden zurück. Im tatsächlichen Betrieb empfängt der Browser zunächst den Kopfteilcode und lädt drei statische Dateien. Dies dauert zwei Sekunden. In der dritten Sekunde erscheint teilweise 1, partiell 2 erscheint in der fünften Sekunde und das Laden von Webseite endet. Ich werde keinen Screenshot geben, der Screenshot -Effekt entspricht den Screenshots in den letzten 5 Sekunden.
Es ist jedoch wichtig zu beachten, dass dieser Effekt erreicht werden kann, da getData.d1 schneller ist als getData.d2. Das heißt, welcher Block auf der Webseite zuerst zurückgegeben wird, hängt davon ab, wer das Ergebnis des asynchronen Rufs der Schnittstelle dahinter zurückgibt. Wenn wir GetData.d1 in 8 Sekunden zurückkehren, werden wir zunächst Teil 2. Die Reihenfolge von S1 und S2 wird umgekehrt, und das Endergebnis der Webseite steht nicht mit unseren Erwartungen wider.
Dieses Problem führt uns letztendlich zu BigPipe, einer Technologie, mit der die Anzeigereihenfolge jedes Teils der Webseite aus der Übertragungsreihenfolge der Daten entkoppeln kann.
Die Grundidee besteht darin, zuerst den allgemeinen Rahmen der gesamten Webseite zu übertragen, und die Teile, die später übertragen werden müssen, werden durch leere Divs (oder andere Tags) dargestellt:
Die Codekopie lautet wie folgt:
res.render ('layout', function (err, str) {
if (err) return res.req.next (err)
res.setheader ('Inhaltstyp', 'text/html; charset = utf-8')
res.write (str)
res.write ('<Abschnitt ID = "S1"> </Abschnitt> <Abschnitt ID = "S2"> </§>')
})
Schreiben Sie dann die zurückgegebenen Daten in JavaScript
Die Codekopie lautet wie folgt:
getData.d1 (Funktion (Err, S1Data) {
res.write ('<script> $ ("#s1").
--N || res.end ()
})
Die Verarbeitung von S2 ähnelt diesem. Zu diesem Zeitpunkt sehen Sie, dass in der zweiten Sekunde, in der die Webseite angefordert wird, zwei leere gepunktete Boxen in der fünften Sekunde angezeigt werden, und in der achten Sekunde wird in der fünften Sekunde angezeigt, und in der achten Sekunde wird teilweise 1 angezeigt, und die Webseitenanforderung wird abgeschlossen.
Zu diesem Zeitpunkt haben wir die einfachste Webseite abgeschlossen, die von der BigPipe -Technologie implementiert wurde.
Es ist zu beachten, dass, wenn das zu schriftliche Webseitenfragment Skript -Tags enthält, z. B. das Ändern von s1.jade in:
Die Codekopie lautet wie folgt:
H1 Teil 1
.Content! = Inhalt
Skript
ALERT ("Alarm von S1.Jade")
Aktualisieren Sie dann die Webseite und Sie werden feststellen, dass der Warnungssatz nicht ausgeführt wird und die Webseite Fehler hat. Überprüfen Sie den Quellcode und wissen Sie, dass es sich um einen Fehler handelt, der durch die Zeichenfolge in <skript> verursacht wird. Ersetzen Sie es einfach durch <// script>
Die Codekopie lautet wie folgt:
res.write ('<script> $ ("#s1").
Oben erläutern wir die Prinzipien von BigPipe und die grundlegende Methode zur Implementierung von BigPipe mit Node.js. Und wie sollte es in der Realität verwendet werden? Hier ist eine einfache Methode zum Werfen von Ziegeln und Jade. Der Code lautet wie folgt:
Die Codekopie lautet wie folgt:
var ressproto = require ('express/lib/response')
ressproto.pipe = function (selektor, html, ersetzen) {
this.write ('<script>' + '$ ("' + selector + '").' +
(Ersetzen === true? 'ErsatzWith': 'HTML') + +
'("' + html.replace (/"/g, '//"').replace(/<//script>/g,' <// script> ') + +
'") </script>')
}
Funktion pipename (res, name) {
res.pipeCount = res.pipeCount || 0
res.pipemap = res.pipemap || {}
if (res.pipemap [name]) return
res.pipeCount ++
res.pipemap [name] = this.id = ['pipe', math.random (). toString (). Substring (2), (neuer Datum ()). ValueOf ()]. Join ('_')
this.res = res
this.name = name
}
ressproto.pipename = function (name) {
Kehren Sie neuer Pipename zurück (this, Name)
}
Ressproto.Pipelayout = Funktion (Ansicht, Optionen) {
var res = this
Object.Keys (Optionen) .foreach (Funktion (Schlüssel) {
if (Optionen [Schlüssel] Instanz von PipEname) Optionen [Schlüssel] = '<span id = "' + option [Schlüssel] .ID + '"> </span>'
})
res.render (Ansicht, Optionen, Funktion (err, str) {
if (err) return res.req.next (err)
res.setheader ('Inhaltstyp', 'text/html; charset = utf-8')
res.write (str)
if (! res.pipeCount) res.end ()
})
}
ressproto.pipePartial = function (Name, Ansicht, Optionen) {
var res = this
res.render (Ansicht, Optionen, Funktion (err, str) {
if (err) return res.req.next (err)
res.pipe ('#'+res.pipemap [name], str, true)
--res.pipeCount || res.end ()
})
}
app.get ('/', Funktion (req, res) {
res.pipelayout ('layout', {
S1: Res.Pipename ('S1Name')
, S2: Res.Pipename ('S2Name')
})
getData.d1 (Funktion (Err, S1Data) {
Res.PipePartial ('S1Name', 'S1', S1Data)
})
getData.d2 (Funktion (Err, S2Data) {
Res.PipePartial ('S2Name', 'S2', S2Data)
})
})
Fügen Sie auch zwei Abschnitte in layout.jade hinzu:
Die Codekopie lautet wie folgt:
Abschnitt#S1! = S1
Abschnitt#S2! = S2
Die Idee hier ist, dass der Inhalt des Rohrs zuerst mit einem Spann -Tag platziert werden muss, die Daten asynchron erhalten und den entsprechenden HTML -Code rendern, bevor er ihn in den Browser ausgibt, und das Platzhalter -Span -Element durch die Ersatzmethode von JQuery ersetzen.
Der Code dieses Artikels finden Sie unter https://github.com/undozen/bigpipe-on-node. Ich habe jeden Schritt in ein Commit gemacht. Ich hoffe, Sie können es tatsächlich lokal ausführen und hacken. Da die nächsten paar Schritte die Ladereihenfolge beinhalten, müssen Sie den Browser wirklich selbst öffnen, um ihn zu erleben, und können ihn nicht vom Screenshot aus sehen (tatsächlich sollte es mit GIF -Animation implementiert werden, aber ich war zu faul, um es zu tun).
Es gibt immer noch viel Raum für die Optimierung über die BigPipe -Praxis. Beispielsweise ist es am besten, einen ausgelösten Zeitwert für den Inhalt des Rohrs festzulegen. Wenn die Daten asynchron schnell zurückgegeben werden, müssen Sie BigPipe nicht verwenden. Sie können direkt eine Webseite generieren und aussenden. Sie können warten, bis die Datenanforderung vor der Verwendung von BigPipe einen bestimmten Zeitraum überschritten hat. Im Vergleich zu AJAX speichert die Verwendung von BigPipe nicht nur die Anzahl der Anforderungen vom Browser auf dem Node.js -Server, sondern auch die Anzahl der Anforderungen vom Node.js -Server an der Datenquelle. Teilen wir jedoch die spezifischen Optimierungs- und Übungsmethoden, nachdem das Snowball -Netzwerk BigPipe verwendet hat.