BigPipe est une technologie développée par Facebook pour optimiser la vitesse de chargement des pages Web. Il n'y a presque aucun article implémenté avec Node.js sur Internet. En fait, ce n'est pas seulement Node.js. Les implémentations de BigPipe dans d'autres langues sont rares sur Internet. Si longtemps après l'apparition de cette technologie, j'ai pensé qu'après l'envoi de l'ensemble de la page Web, j'ai utilisé une autre ou plusieurs demandes AJAX pour demander les modules de la page. Ce n'est que peu il n'y a pas longtemps que j'ai appris que le concept principal de Bigpipe est d'utiliser une seule demande HTTP, mais les éléments de la page sont envoyés dans l'ordre.
Il sera facile de comprendre ce concept de base. Grâce à la fonctionnalité asynchrone de Node.js, il est facile d'implémenter BigPipe avec Node.js. Cet article utilisera des exemples étape par étape pour illustrer les causes de la technologie BigPipe et une simple implémentation basée sur Node.js.
J'utiliserai Express pour démontrer. Pour plus de simplicité, nous choisissons Jade comme moteur de modèle, et nous n'utilisons pas la fonctionnalité du sous-modèle du moteur (partielle), mais utilisons plutôt le modèle enfant pour rendre HTML comme données de modèle parent.
Créez d'abord un dossier Nodejs-BigPipe et écrivez un fichier package.json comme suit:
La copie de code est la suivante:
{
"Nom": "Bigpipe-Experiment"
, "Version": "0.1.0"
, "privé": vrai
, "dépendances": {
"express": "3.xx"
, "Consolider": "Dernier"
, "Jade": "Dernier"
}
}
Exécutez NPM Installer pour installer ces trois bibliothèques. La consolidation est utilisée pour faciliter l'appel Jade.
Faisons d'abord la tentative la plus simple, deux fichiers:
app.js:
La copie de code est la suivante:
var express = require ('express')
, contre = exiger («consolider»)
, jade = require ('jade')
, chemin = requis ('path')
var app = express ()
App.Engine ('Jade', contre.jade)
app.set ('vues', path.join (__ dirname, 'vues'))
app.set («voir le moteur», «jade»)
app.use (function (req, res) {
res.render ('Layout', {
S1: "Bonjour, je suis la première section."
, S2: "Bonjour, je suis la deuxième section."
})
})
app.Listen (3000)
vues / disposition.jade
La copie de code est la suivante:
doctype html
tête
Titre Bonjour, monde!
style
section {
marge: 20px automatique;
Border: 1px en pointillé gris;
Largeur: 80%;
hauteur: 150px;
}
Section # S1! = S1
Section # S2! = S2
Les effets sont les suivants:
Ensuite, nous avons mis deux modèles de section dans deux fichiers de modèles différents:
vues / s1.jade:
La copie de code est la suivante:
H1 partiel 1
.Content! = Contenu
Vues / S2.Jade:
La copie de code est la suivante:
H1 partiel 2
.Content! = Contenu
Ajoutez des styles à mettre en page.
La copie de code est la suivante:
Section H1 {
taille de police: 1,5;
rembourrage: 10px 20px;
marge: 0;
Border-Bottom: 1px gris en pointillé;
}
Section Div {
marge: 10px;
}
Modifiez la partie app.use () d'App.js en:
La copie de code est la suivante:
var temp = {
S1: jade.compile (fs.readfilesync (path.join (__ dirname, 'Views', 's1.jade'))))
.
}
app.use (function (req, res) {
res.render ('Layout', {
s1: temp.s1 ({contenu: "Bonjour, je suis la première section."})
, s2: temp.s2 ({contenu: "Bonjour, je suis la deuxième section."})
})
})
Nous avons déjà dit: "Le HTML après rendu est terminé avec le modèle enfant comme données du modèle parent", ce qui signifie que les deux méthodes temp.s1 et temp.s2 généreront le code HTML pour les deux fichiers S1.jade et S2.jade, puis utiliseront ces deux pièces de code comme valeurs des deux variables S1 et S2 dans la mise en page.Jade.
Maintenant, la page ressemble à ceci:
De manière générale, les données des deux sections sont obtenues séparément - que ce soit en interrogeant la base de données ou la demande RESTFul, nous utilisons deux fonctions pour simuler ces opérations asynchrones.
La copie de code est la suivante:
var getData = {
d1: fonction (fn) {
setTimeout (fn, 3000, null, {contenu: "Bonjour, je suis la première section."})
}
, d2: fonction (fn) {
setTimeout (fn, 5000, null, {contenu: "Bonjour, je suis la deuxième section."})
}
}
De cette façon, la logique dans app.use () sera plus compliquée, et le moyen le plus simple d'y faire face est:
La copie de code est la suivante:
app.use (function (req, res) {
getData.d1 (fonction (err, s1data) {
getData.d2 (fonction (err, s2data) {
res.render ('Layout', {
S1: Temp.S1 (S1DATA)
, S2: temp.s2 (s2data)
})
})
})
})
Cela obtiendra également les résultats que nous voulons, mais dans ce cas, il faudra 8 secondes pour revenir.
En fait, la logique d'implémentation montre que GetData.D2 commence à appeler après le résultat de GetData.D1 et qu'ils n'ont pas une telle dépendance. Nous pouvons utiliser des bibliothèques telles que Async qui gère les appels asynchrones JavaScript pour résoudre ce problème, mais écrivons simplement ici:
La copie de code est la suivante:
app.use (function (req, res) {
var n = 2
, résultat = {}
getData.d1 (fonction (err, s1data) {
résultat.s1data = s1data
--n || écrivain ()
})
getData.d2 (fonction (err, s2data) {
résultat.s2data = s2data
--n || écrivain ()
})
function writerSult () {
res.render ('Layout', {
S1: temp.s1 (result.s1data)
, S2: temp.s2 (result.s2data)
})
}
})
Cela ne prend que 5 secondes.
Avant la prochaine optimisation, nous ajoutons la bibliothèque jQuery et mettons le style CSS dans des fichiers externes. Au fait, nous ajouterons le fichier runtime.js nécessaire pour utiliser le modèle jade que nous utiliserons plus tard et l'exécuterons dans le répertoire contenant app.js:
La copie de code est la suivante:
mkdir statique
CD statique
Curl http://code.jquery.com/jquery-1.8.3.min.js -o jQuery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
Et éliminez le code dans la balise de style dans la mise en page.jade et mettez-le dans static / style.css, et modifiez la balise de tête en:
La copie de code est la suivante:
tête
Titre Bonjour, monde!
lien (href = "/ static / style.css", rel = "Stylesheet")
script (src = "/ static / jquery.js")
script (src = "/ static / jade.js")
Dans app.js, nous simulons les deux vitesses de téléchargement à deux secondes et ajoutées avant app.use (fonction (req, res) {:
La copie de code est la suivante:
var static = express.static (path.join (__ dirname, 'static')))
app.use ('/ static', fonction (req, res, suivant) {
setTimeout (Static, 2000, req, res, Next)
})
En raison de fichiers statiques externes, notre page a maintenant un temps de chargement d'environ 7 secondes.
Si nous retournons la partie de tête dès que nous recevons une demande HTTP, puis deux sections attendent que l'opération asynchrone soit terminée avant de revenir, cela utilise le mécanisme de codage de transmission bloqué de HTTP. Dans Node.js, tant que vous utilisez la méthode res.write (), l'en-tête de transfert: Chunked sera automatiquement ajouté. De cette façon, alors que le navigateur charge le fichier statique, le serveur de nœuds attend le résultat de l'appel asynchrone. Supprimons d'abord les deux lignes de la section dans la mise en page.jade:
La copie de code est la suivante:
Section # S1! = S1
Section # S2! = S2
Par conséquent, nous n'avons pas besoin de donner cet objet dans res.render () {s1:…, s2:…}, et parce que res.render () appellera res.end () par défaut, nous devons définir manuellement la fonction de rappel une fois le rendu terminé et utiliser la méthode res.write (). Le contenu de la mise en page.jade n'a pas besoin d'être dans la fonction de rappel des écrivains (). Nous pouvons le retourner lorsque nous recevons cette demande. Notez que nous avons ajouté manuellement l'en-tête de type contenu:
La copie de code est la suivante:
app.use (function (req, res) {
res.render ('Layout', fonction (err, str) {
if (err) return res.req.next (err)
res.sethEader ('Content-Type', 'text / html; charset = utf-8')
res.write (str)
})
var n = 2
getData.d1 (fonction (err, s1data) {
res.write ('<section id = "s1">' + temp.s1 (s1data) + '</pction>')
--n || res.end ()
})
getData.d2 (fonction (err, s2data) {
res.write ('<section id = "s2">' + temp.s2 (s2data) + '</ction>')
--n || res.end ()
})
})
Maintenant, la vitesse de chargement finale est de retour à environ 5 secondes. En fonctionnement réel, le navigateur reçoit d'abord le code de partie de tête et charge trois fichiers statiques. Cela prend deux secondes. Ensuite, dans la troisième seconde, le partiel 1 apparaît, le partiel 2 apparaît dans la cinquième seconde et le chargement de la page Web se termine. Je ne donnerai pas de capture d'écran, l'effet de capture d'écran est le même que les captures d'écran dans les 5 secondes précédentes.
Cependant, il est important de noter que cet effet peut être réalisé car getData.d1 est plus rapide que getData.d2. C'est-à-dire que le blocage dans la page Web est renvoyé en premier dépend de qui renvoie le résultat de l'appel asynchrone de l'interface derrière. Si nous modifions GetData.D1 pour revenir en 8 secondes, nous retournerons d'abord partiels 2. L'ordre de S1 et S2 est inversé, et le résultat final de la page Web est incompatible avec nos attentes.
Ce problème nous conduit finalement à Bigpipe, qui est une technologie qui peut découpler l'ordre d'affichage de chaque partie de la page Web à partir de l'ordre de transmission des données.
L'idée de base est de transmettre d'abord le cadre général de toute la page Web, et les pièces qui doivent être transmises plus tard sont représentées par des divs vides (ou d'autres balises):
La copie de code est la suivante:
res.render ('Layout', fonction (err, str) {
if (err) return res.req.next (err)
res.sethEader ('Content-Type', 'text / html; charset = utf-8')
res.write (str)
res.write ('<section id = "s1"> </ section> <section id = "s2"> </ Section>')
})
Ensuite, écrivez les données renvoyées dans JavaScript
La copie de code est la suivante:
getData.d1 (fonction (err, s1data) {
res.write ('<script> $ ("# s1"). html ("' + temp.s1 (s1data) .replace (/" / g, '// "') + '") </script>')
--n || res.end ()
})
Le traitement de S2 est similaire à cela. À l'heure actuelle, vous verrez que dans la deuxième seconde de la demande de la page Web, deux boîtes en pointillés vierges apparaissent, dans la cinquième seconde, partielle 2 apparaît, et dans la huitième seconde, partielle 1 apparaît, et la demande de page Web est terminée.
À ce stade, nous avons terminé la page Web la plus simple implémentée par BigPipe Technology.
Il convient de noter que si le fragment de page Web à écrire a des balises de script, telles que la modification de S1.jade en:
La copie de code est la suivante:
H1 partiel 1
.Content! = Contenu
scénario
alerte ("alerte de s1.jade")
Ensuite, actualisez la page Web et vous constaterez que la phrase d'alerte n'est pas exécutée et que la page Web aura des erreurs. Vérifiez le code source et sachez qu'il s'agit d'une erreur causée par la chaîne dans <Script>. Remplacez-le simplement par <// script>
La copie de code est la suivante:
res.write ('<script> $ ("# s1"). html ("' + temp.s1 (s1data) .replace (/" / g, '//"' ).replace(/</script>/g,' <// script> ') +' "") </ script> ')
Ci-dessus, nous expliquons les principes de Bigpipe et la méthode de base de la mise en œuvre de Bigpipe avec Node.js. Et comment devrait-il être utilisé dans la réalité? Voici une méthode simple pour lancer des briques et du jade, le code est le suivant:
La copie de code est la suivante:
var resproto = require ('express / lib / réponse')
resproto.pipe = fonction (sélecteur, html, remplacer) {
this.write ('<cript>' + '$ ("' + sélecteur + '").' +
(remplacer === true? 'Remplacewith': 'html') +
'("' + html.replace (/" / g, '//"' ).replace(/<//script>/g,' <// script> ') +
'") </script>')
}
fonction pipename (res, name) {
res.pipeCount = res.pipeCount || 0
res.pipemap = res.pipemap || {}
if (res.pipemap [name]) return
res.pipeCount ++
res.pipemap [name] = this.id = ['tuy', math.random (). toString (). substring (2), (new Date ()). Value of ()]. JOIN ('_')
this.res = res
this.name = nom
}
resproto.piPename = function (name) {
Renvoie un nouveau pipeName (ce, nom)
}
resproto.pipelayout = function (View, Options) {
var res = ceci
Object.keys (options) .ForEach (fonction (key) {
if (Options [key] instanceof PiPename) Options [key] = '<span id = "' + options [key] .id + '"> </span>'
})
res.render (vue, options, fonction (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 = fonction (nom, vue, options) {
var res = ceci
res.render (vue, options, fonction (err, str) {
if (err) return res.req.next (err)
res.pipe ('#' + res.pipemap [nom], str, true)
--res.pipeCount || res.end ()
})
}
app.get ('/', fonction (req, res) {
res.pipelayout ('Layout', {
S1: res.pipeName ('s1Name')
, s2: res.piPename ('s2Name')
})
getData.d1 (fonction (err, s1data) {
res.pipepartial ('s1name', 's1', s1data)
})
getData.d2 (fonction (err, s2data) {
res.pipepartial ('s2Name', 's2', s2data)
})
})
Ajoutez également deux sections dans la mise en page.jade:
La copie de code est la suivante:
Section # S1! = S1
Section # S2! = S2
L'idée ici est que le contenu du tuyau doit être placé avec une étiquette de span d'abord, obtenir les données de manière asynchrone et rendre le code HTML correspondant avant de le sortir au navigateur, et remplacer l'élément de portée d'espace réservé par la méthode Remplace avec JQuery.
Le code de cet article se trouve à https://github.com/undozen/bigpipe-on-odde. J'ai fait chaque pas dans un engagement. J'espère que vous pourrez réellement l'exécuter localement et le pirater. Parce que les prochaines étapes impliquent l'ordre de chargement, vous devez vraiment ouvrir le navigateur vous-même pour en faire l'expérience et je ne peux pas le voir à partir de la capture d'écran (en fait, il devrait être implémenté avec l'animation GIF, mais j'étais trop paresseux pour le faire).
Il y a encore beaucoup de place à l'optimisation sur la pratique de Bigpipe. Par exemple, il est préférable de définir une valeur de temps déclenchée pour le contenu du tuyau. Si les données appelées reviennent de manière asynchrone rapidement, vous n'avez pas besoin d'utiliser BigPipe. Vous pouvez générer directement une page Web et l'envoyer. Vous pouvez attendre que la demande de données ait dépassé une certaine période avant d'utiliser BigPipe. Par rapport à Ajax, l'utilisation de BigPipe enregistre non seulement le nombre de demandes du navigateur vers le serveur Node.js, mais enregistre également le nombre de demandes du serveur Node.js à la source de données. Cependant, partageons les méthodes d'optimisation et de pratique spécifiques après que Snowball Network a utilisé BigPipe.