Heim  >  Artikel  >  Web-Frontend  >  Node.js implementiert BigPipe im Detail_node.js

Node.js implementiert BigPipe im Detail_node.js

WBOY
WBOYOriginal
2016-05-16 16:29:031561Durchsuche

BigPipe ist eine von Facebook entwickelte Technologie zur Optimierung der Ladegeschwindigkeit von Webseiten. Es gibt fast keine Artikel, die mit node.js im Internet implementiert wurden. Tatsächlich sind Implementierungen von BigPipe in anderen Sprachen, nicht nur mit node.js, im Internet selten. So lange nach dem Erscheinen dieser Technologie dachte ich, dass, nachdem zuerst der gesamte Webseiten-Frame gesendet wurde, eine oder mehrere Ajax-Anfragen verwendet wurden, um die Module auf der Seite anzufordern. Bis vor nicht allzu langer Zeit habe ich erfahren, dass das Kernkonzept von BigPipe darin besteht, nur eine HTTP-Anfrage zu verwenden, die Seitenelemente werden jedoch in der falschen Reihenfolge gesendet.

Es wird einfacher, wenn Sie dieses Kernkonzept verstanden haben. Dank der asynchronen Funktionen von node.js ist es einfach, BigPipe mit node.js zu implementieren. In diesem Artikel wird anhand von Beispielen Schritt für Schritt der Ursprung der BigPipe-Technologie und eine einfache Implementierung auf Basis von node.js erläutert.

Zur Veranschaulichung verwende ich Express. Wir wählen Jade als Vorlagen-Engine und verwenden nicht die (partielle) Untervorlagenfunktion der Engine, sondern den HTML-Code, nachdem die Untervorlage gerendert wurde die Daten der übergeordneten Vorlage.

Erstellen Sie zunächst einen Ordner „nodejs-bigpipe“ und schreiben Sie eine package.json-Datei wie folgt:

Code kopieren Der Code lautet wie folgt:

{
„name“: „bigpipe-experiment“
, „Version“: „0.1.0“
, „privat“: wahr
, „Abhängigkeiten“: {
„express“: „3.x.x“
, „consolidate“: „latest“
, „jade“: „neueste“
}
}

Führen Sie npm install aus, um diese drei Bibliotheken zu installieren. Consolidate wird verwendet, um den Aufruf von Jade zu erleichtern.

Lassen Sie uns zunächst das Einfachste ausprobieren, zwei Dateien:

app.js:

Code kopieren Der Code lautet wie folgt:

var express = require('express')
, Nachteile = require('consolidate')
, jade = require('jade')
, path = require('path')

var app = express()

app.engine('jade', cons.jade)
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')

app.use(function (req, res) {
res.render('layout', {
s1: „Hallo, ich bin der erste Abschnitt.“
, s2: „Hallo, ich bin der zweite Abschnitt.“
})
})

app.listen(3000)

views/layout.jade

Code kopieren Der Code lautet wie folgt:

doctype html

Kopf
Titel Hallo Welt!
Stil
Abschnitt {
        Rand: 20 Pixel automatisch;
Rand: 1 Pixel gepunktet grau;
       Breite: 80 %;
Höhe: 150px;
}

Abschnitt#s1!=s1
Abschnitt#s2!=s2

Der Effekt ist wie folgt:

Als nächstes legen wir die beiden Abschnittsvorlagen in zwei verschiedene Vorlagendateien ab:

views/s1.jade:

Code kopieren Der Code lautet wie folgt:

h1 Teilweise 1
.content!=content

views/s2.jade:

Code kopieren Der Code lautet wie folgt:

h1 Teil 2
.content!=content

Fügen Sie einige Stile zum Stil von layout.jade hinzu

Code kopieren Der Code lautet wie folgt:

Abschnitt h1 {
Schriftgröße: 1,5;
Polsterung: 10px 20px;
Rand: 0;
Rand unten: 1 Pixel gepunktet grau;
}
Abschnitt div {
Rand: 10px;
}

Ändern Sie den app.use()-Teil von app.js in:

Code kopieren Der Code 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(function (req, res) {
res.render('layout', {
s1: temp.s1({ content: „Hallo, ich bin der erste Abschnitt.“ })
, s2: temp.s2({ content: „Hallo, ich bin der zweite Abschnitt.“ })
})
})

Bevor wir sagten: „Verwenden Sie den HTML-Code, nachdem die Untervorlage als Daten der übergeordneten Vorlage gerendert wurde“, bedeutet dies: Die beiden Methoden temp.s1 und temp.s2 generieren zwei Dateien, s1.jade und s2.jade. HTML-Code und verwenden Sie diese beiden Codeteile dann als Werte der beiden Variablen s1 und s2 in layout.jade.

Die Seite sieht jetzt so aus:

Im Allgemeinen werden die Daten der beiden Abschnitte separat abgerufen – sei es durch Abfrage der Datenbank oder durch RESTful-Anfrage, wir verwenden zwei Funktionen, um solche asynchronen Vorgänge zu simulieren.

Code kopieren Der Code lautet wie folgt:

var getData = {
d1: Funktion (fn) {
​ ​ ​ setTimeout(fn, 3000, null, { content: „Hallo, ich bin der erste Abschnitt.“ })
}
, d2: Funktion (fn) {
​ ​ ​ setTimeout(fn, 5000, null, { content: „Hallo, ich bin der zweite Abschnitt.“ })
}
}

Auf diese Weise wird die Logik in app.use() komplizierter. Der einfachste Weg, damit umzugehen, ist:

Code kopieren Der Code lautet wie folgt:

app.use(function (req, res) {
getData.d1(function (err, s1data) {
GetData.d2(function (err, s2data) {
res.render('layout', {
          s1: temp.s1(s1data)
, , s2: temp.s2(s2data)
})
})
})
})

Dies kann auch zu den gewünschten Ergebnissen führen, aber in diesem Fall dauert die Rückkehr volle 8 Sekunden.

Tatsächlich zeigt die Implementierungslogik, dass getData.d2 erst aufgerufen wird, nachdem das Ergebnis von getData.d1 zurückgegeben wurde, und zwischen den beiden besteht keine solche Abhängigkeit. Wir können Bibliotheken wie async verwenden, die asynchrone JavaScript-Aufrufe verarbeiten, um dieses Problem zu lösen, aber schreiben wir es einfach hier von Hand:

Code kopieren Der Code lautet wie folgt:

app.use(function (req, res) {
var n = 2
, Ergebnis = {}
getData.d1(function (err, s1data) {
​ result.s1data = s1data
--n ||. writeResult()
})
getData.d2(function (err, s2data) {
​ result.s2data = s2data
--n ||. writeResult()
})
Funktion writeResult() {
res.render('layout', {
        s1: temp.s1(result.s1data)
, s2: temp.s2(result.s2data)
})
}
})

Dies dauert nur 5 Sekunden.

Vor der nächsten Optimierung haben wir die JQuery-Bibliothek hinzugefügt und den CSS-Stil in eine externe Datei eingefügt. Übrigens haben wir auch die Datei runtime.js hinzugefügt, die für die Verwendung der Jade-Vorlage auf der Browserseite erforderlich ist, die wir später verwenden werden . Führen Sie im Verzeichnis aus, das app.js enthält:

Code kopieren Der Code lautet wie folgt:

mkdir static
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

Und nehmen Sie den Code im Style-Tag in layout.jade heraus, fügen Sie ihn in static/style.css ein und ändern Sie dann das Head-Tag in:

Code kopieren Der Code lautet wie folgt:

Kopf
Titel Hallo Welt!
link(href="/static/style.css", rel="stylesheet")
script(src="/static/jquery.js")
script(src="/static/jade.js")

In app.js simulieren wir die Download-Geschwindigkeit beider auf zwei Sekunden und fügen Folgendes hinzu:
vor app.use(function (req, res) {

Code kopieren Der Code lautet wie folgt:

var static = express.static(path.join(__dirname, 'static'))
app.use('/static', function (req, res, next) {
setTimeout(static, 2000, req, res, next)
})

Unsere Seite wird aufgrund externer statischer Dateien jetzt in etwa 7 Sekunden geladen.

Wenn wir den Kopfteil zurückgeben, sobald wir die HTTP-Anfrage erhalten, und die beiden Abschnitte dann warten, bis der asynchrone Vorgang abgeschlossen ist, bevor sie zurückkehren, verwendet dies den HTTP-Chunked-Transfer-Codierungsmechanismus. Solange Sie in node.js die Methode res.write() verwenden, wird der Header „Transfer-Encoding: chunked“ automatisch hinzugefügt. Während der Browser die statische Datei lädt, wartet der Knotenserver auf diese Weise auf das Ergebnis des asynchronen Aufrufs. Löschen wir zunächst diese beiden Zeilen im Abschnitt in layout.jade:

Code kopieren Der Code lautet wie folgt:

Abschnitt#s1!=s1
Abschnitt#s2!=s2

Wir müssen das Objekt { s1: …, s2: … } also nicht in res.render() angeben, und da res.render() standardmäßig res.end() aufruft, müssen wir dies manuell tun Nach Abschluss rendern Die Rückruffunktion verwendet die darin enthaltene Methode res.write(). Der Inhalt von „layout.jade“ muss nicht in der Callback-Funktion „writeResult()“ enthalten sein. Wir können beim Empfang dieser Anfrage zurückkehren. Beachten Sie, dass wir den Content-Type-Header manuell hinzugefügt haben

Code kopieren Der Code lautet wie folgt:
app.use(function (req, res) {
res.render('layout', function (err, str) {
Wenn (err) res.req.next(err)
zurückgibt res.setHeader('content-type', 'text/html; charset=utf-8')
res.write(str)
})
var n = 2
getData.d1(function (err, s1data) {
res.write('
' temp.s1(s1data) '
')
--n ||. res.end()
})
getData.d2(function (err, s2data) {
res.write('
' temp.s2(s2data) '
')
--n ||. res.end()
})
})

Die endgültige Ladegeschwindigkeit beträgt jetzt wieder etwa 5 Sekunden. Im tatsächlichen Betrieb empfängt der Browser zunächst den Kopfteil des Codes und lädt dann drei statische Dateien, was zwei Sekunden dauert. In der dritten Sekunde erscheint dann der Teil 1 und in der fünften Sekunde der Teil 2. und die Webseite wird geladen. Ich werde keinen Screenshot machen, der Screenshot-Effekt ist der gleiche wie beim vorherigen 5-Sekunden-Screenshot.

Bitte beachten Sie jedoch, dass dieser Effekt erzielt werden kann, da getData.d1 schneller ist als getData.d2. Mit anderen Worten: Welcher Block in der Webseite zuerst zurückgegeben wird, hängt vom asynchronen Aufrufergebnis der Schnittstelle ab, die zuerst zurückgegeben wird. Wenn wir getData auf 8 Sekunden setzen, wird Teil 2 zuerst zurückgegeben und die Reihenfolge von s1 und s2 wird umgekehrt. Das Endergebnis der Webseite entspricht nicht unseren Erwartungen.

Diese Frage führt uns schließlich zu BigPipe. BigPipe ist eine Technologie, die die Anzeigereihenfolge jedes Teils der Webseite von der Datenübertragungsreihenfolge entkoppeln kann.

Die Grundidee besteht darin, zunächst den allgemeinen Rahmen der gesamten Webseite zu übertragen und die Teile, die später übertragen werden müssen, durch leere Divs (oder andere Tags) darzustellen:

Code kopieren Der Code lautet wie folgt:

res.render('layout', function (err, str) {
if (err) return res.req.next(err)
res.setHeader('content-type', 'text/html; charset=utf-8')
res.write(str)
res.write('
')
})

Dann schreiben Sie die zurückgegebenen Daten mit JavaScript

Code kopieren Der Code lautet wie folgt:

getData.d1(function (err, s1data) {
res.write('<script>$("#s1").html("' temp.s1(s1data).replace(/"/g, '\"') '")</script>')
--n ||. res.end()
})

s2 wird ähnlich gehandhabt. Zu diesem Zeitpunkt werden Sie sehen, dass in der zweiten Sekunde der Anforderung der Webseite zwei leere gepunktete Kästchen erscheinen, in der fünften Sekunde der Teil 2 erscheint, in der achten Sekunde der Teil 1 erscheint und die Webseite erscheint Die Anfrage ist abgeschlossen.

Zu diesem Zeitpunkt haben wir eine Webseite fertiggestellt, die mit der einfachsten BigPipe-Technologie implementiert wurde.

Es ist zu beachten, dass das zu schreibende Webseitenfragment ein Skript-Tag hat. Ändern Sie beispielsweise s1.jade in:

Code kopieren Der Code lautet wie folgt:

h1 Teilweise 1
.content!=content
Skript
Alert("Alarm von s1.jade")

Aktualisieren Sie dann die Webseite und Sie werden feststellen, dass die Warnung nicht ausgeführt wird und es Fehler auf der Webseite gibt. Überprüfen Sie den Quellcode und stellen Sie fest, dass der Fehler durch das Erscheinen von in der Zeichenfolge in verursacht wird

Code kopieren Der Code lautet wie folgt:
res.write('<script>$("#s1").html("' temp.s1(s1data).replace(/"/g, '\"').replace(/</script>/ g, '<
\/script>') '")')
Oben haben wir das Prinzip von BigPipe und die grundlegende Methode zur Implementierung von BigPipe mithilfe von node.js erläutert. Und wie soll es in der Praxis eingesetzt werden? Nachfolgend finden Sie eine einfache Methode, nur als Referenz. Der Code lautet wie folgt:

Code kopieren Der Code lautet wie folgt:

var resProto = require('express/lib/response')
resProto.pipe = Funktion (Selektor, HTML, Ersetzen) {
  this.write('<script>' '$("' selector '").' <br>     (replace === true ? 'replaceWith' : 'html') <br>     '("' html.replace(/"/g, '\"').replace(/</script>/g, '<\/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), (new Date()).valueOf()].join('_')
  this.res = res
  this.name = name
}
resProto.pipeName = Funktion (Name) {
  gib den neuen PipeName(this, name)
zurück }
resProto.pipeLayout = Funktion (Ansicht, Optionen) {
  var res = this
  Object.keys(options).forEach(function (key) {
    if (options[key] Instanz von PipeName) options[key] = ''
  })
  res.render(view, options, function (err, str) {
    if (err) return res.req.next(err)
    res.setHeader('content-type', 'text/html; charset=utf-8')
    res.write(str)
    if (!res.pipeCount) res.end()
  })
}
resProto.pipePartial = Funktion (Name, Ansicht, Optionen) {
  var res = this
  res.render(view, options, function (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(function (err, s1data) {
    res.pipePartial('s1name', 's1', s1data)
  })
  getData.d2(function (err, s2data) {
    res.pipePartial('s2name', 's2', s2data)
  })
})

还要在 Layout.jade 把两个 Abschnitt 添加回来:

复制代码 代码如下:

Abschnitt#s1!=s1
Abschnitt#s2!=s2

这里的思路是,需要pipe 的内容先用一个 span 标签占位,异步获取数据并渲染完成相应的HTML. 代码后Wenn Sie eine neue jQuery-App verwenden, verwenden Sie replaceWith und verwenden Sie die span-Funktion 。

本文的代码在 https://github.com/undozen/bigpipe-on-node并 hack 一下看看(其实应该可以用 gif 动画实现,但是我懒得做了).

关于 BigPipe 的实践还有很大的优化空间, 比如说, 要 Pipe 的内容最好设置一个触发的时间值, 如果异步调用的数据很快返回, 就不需要用 BigPipe, 直接生成网页送出Sie haben die Möglichkeit, BigPipe zu verwenden, indem Sie BigPipe verwenden Verwenden Sie die Datei node.js优化和实践方法,等到雪球网用上 BigPipe 以后再分享吧.

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn