BigPipe เป็นเทคโนโลยีที่พัฒนาโดย Facebook เพื่อเพิ่มประสิทธิภาพความเร็วในการโหลดหน้าเว็บ แทบจะไม่มีบทความที่ใช้กับ Node.js บนอินเทอร์เน็ต ในความเป็นจริงมันไม่ได้เป็นเพียง Node.js การใช้งาน BigPipe ในภาษาอื่น ๆ นั้นหายากบนอินเทอร์เน็ต ไม่นานหลังจากที่เทคโนโลยีนี้ปรากฏขึ้นฉันคิดว่าหลังจากส่งเฟรมเวิร์กหน้าเว็บทั้งหมดก่อนฉันใช้คำขอ AJAX อื่นหรือหลายครั้งเพื่อขอโมดูลในหน้า เมื่อไม่นานมานี้ฉันได้เรียนรู้ว่าแนวคิดหลักของ BigPipe คือการใช้คำขอ HTTP เพียงครั้งเดียวเท่านั้น แต่องค์ประกอบของหน้าจะถูกส่งตามลำดับ
มันจะง่ายที่จะเข้าใจแนวคิดหลักนี้ ขอบคุณคุณสมบัติอะซิงโครนัสของ node.js มันเป็นเรื่องง่ายที่จะใช้ bigpipe กับ node.js บทความนี้จะใช้ตัวอย่างทีละขั้นตอนเพื่อแสดงให้เห็นถึงสาเหตุของเทคโนโลยี BigPipe และการใช้งานอย่างง่ายตาม Node.js.
ฉันจะใช้ Express เพื่อสาธิต เพื่อความเรียบง่ายเราเลือกหยกเป็นเอ็นจิ้นเทมเพลตและเราไม่ได้ใช้คุณสมบัติย่อยของเครื่องยนต์ (บางส่วน) แต่ให้ใช้เทมเพลตเด็กเพื่อแสดงผล HTML เป็นข้อมูลเทมเพลตพาเรนต์
ก่อนอื่นสร้างโฟลเดอร์ nodejs-bigpipe และเขียนไฟล์ package.json ดังนี้:
การคัดลอกรหัสมีดังนี้:
-
"ชื่อ": "BigPipe-Experiment"
, "เวอร์ชัน": "0.1.0"
, "ส่วนตัว": จริง
, "การพึ่งพา": {
"Express": "3.xx"
, "รวม": "ล่าสุด"
, "Jade": "ล่าสุด"
-
-
เรียกใช้ NPM ติดตั้งเพื่อติดตั้งไลบรารีทั้งสามนี้ การรวมจะใช้เพื่ออำนวยความสะดวกในการเรียกหยก
มาพยายามอย่างง่ายที่สุดก่อนสองไฟล์:
app.js:
การคัดลอกรหัสมีดังนี้:
var express = ต้องการ ('ด่วน')
, des = require ('รวม')
, Jade = ต้องการ ('Jade')
, 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 ('เลย์เอาต์', {
S1: "สวัสดีฉันเป็นส่วนแรก"
, S2: "สวัสดีฉันเป็นส่วนที่สอง"
-
-
app.listen (3000)
views/layout.jade
การคัดลอกรหัสมีดังนี้:
doctype html
ศีรษะ
ชื่อสวัสดีโลก!
สไตล์
ส่วน {
มาร์จิ้น: 20px auto;
ชายแดน: สีเทาประ 1px;
ความกว้าง: 80%;
ความสูง: 150px;
-
ส่วน#S1! = S1
ส่วน#S2! = S2
ผลกระทบมีดังนี้:
ต่อไปเราใส่เทมเพลตสองส่วนลงในไฟล์เทมเพลตที่แตกต่างกันสองไฟล์:
Views/S1.jade:
การคัดลอกรหัสมีดังนี้:
H1 บางส่วน 1
.content! = เนื้อหา
Views/S2.jade:
การคัดลอกรหัสมีดังนี้:
H1 บางส่วน 2
.content! = เนื้อหา
เพิ่มสไตล์บางอย่างในเค้าโครงสไตล์ jade
การคัดลอกรหัสมีดังนี้:
ส่วน H1 {
ขนาดตัวอักษร: 1.5;
Padding: 10px 20px;
มาร์จิ้น: 0;
ขอบด้านล่าง: 1px Dotted Grey;
-
ส่วน Div {
มาร์จิ้น: 10px;
-
เปลี่ยน App.use () ส่วนหนึ่งของ app.js เป็น:
การคัดลอกรหัสมีดังนี้:
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 ('เลย์เอาต์', {
S1: Temp.S1 ({เนื้อหา: "สวัสดีฉันเป็นส่วนแรก"})
, S2: Temp.S2 ({เนื้อหา: "สวัสดีฉันเป็นส่วนที่สอง"})
-
-
เราได้กล่าวไว้ก่อนหน้านี้ว่า "HTML หลังจากการเรนเดอร์เสร็จสมบูรณ์ด้วยเทมเพลตเด็กเป็นข้อมูลของเทมเพลตแม่" ซึ่งหมายความว่าทั้งสองวิธี Temp.S1 และ Temp.S2 จะสร้างรหัส HTML สำหรับสองไฟล์ S1.JADE และ S2.JADE
ตอนนี้หน้าดูเหมือนว่า:
โดยทั่วไปข้อมูลของทั้งสองส่วนจะได้รับแยกต่างหากไม่ว่าจะเป็นการสอบถามฐานข้อมูลหรือคำขอพักเราใช้สองฟังก์ชั่นเพื่อจำลองการดำเนินการแบบอะซิงโครนัส
การคัดลอกรหัสมีดังนี้:
var getData = {
D1: ฟังก์ชั่น (fn) {
settimeout (fn, 3000, null, {เนื้อหา: "สวัสดีฉันเป็นส่วนแรก"})
-
, d2: ฟังก์ชั่น (fn) {
settimeout (fn, 5000, null, {เนื้อหา: "สวัสดีฉันเป็นส่วนที่สอง"})
-
-
ด้วยวิธีนี้ตรรกะใน app.use () จะซับซ้อนกว่าและวิธีที่ง่ายที่สุดในการจัดการกับมันคือ:
การคัดลอกรหัสมีดังนี้:
app.use (function (req, res) {
getData.d1 (ฟังก์ชั่น (err, s1data) {
getData.d2 (ฟังก์ชั่น (err, s2data) {
res.render ('เลย์เอาต์', {
S1: Temp.S1 (S1Data)
, S2: Temp.S2 (S2Data)
-
-
-
-
สิ่งนี้จะได้รับผลลัพธ์ที่เราต้องการ แต่ในกรณีนี้จะต้องใช้เวลาเต็ม 8 วินาทีในการกลับมา
ในความเป็นจริงตรรกะการใช้งานแสดงให้เห็นว่า getData.d2 เริ่มโทรหลังจากผลลัพธ์ของ getData.d1 จะถูกส่งคืนและพวกเขาไม่มีการพึ่งพาเช่นนี้ เราสามารถใช้ห้องสมุดเช่น Async ที่จัดการการโทรแบบอะซิงโครนัส JavaScript เพื่อแก้ปัญหานี้ แต่ลองเขียนที่นี่:
การคัดลอกรหัสมีดังนี้:
app.use (function (req, res) {
var n = 2
, result = {}
getData.d1 (ฟังก์ชั่น (err, s1data) {
result.s1data = s1data
-n || นักเขียน ()
-
getData.d2 (ฟังก์ชั่น (err, s2data) {
result.s2data = s2data
-n || นักเขียน ()
-
Function Writeresult () {
res.render ('เลย์เอาต์', {
S1: Temp.S1 (result.s1Data)
, S2: Temp.S2 (result.s2data)
-
-
-
ใช้เวลาเพียง 5 วินาที
ก่อนการเพิ่มประสิทธิภาพครั้งต่อไปเราเพิ่มไลบรารี jQuery และใส่สไตล์ CSS ลงในไฟล์ภายนอก โดยวิธีการที่เราจะเพิ่มไฟล์ runtime.js ที่จำเป็นสำหรับการใช้เทมเพลต Jade ที่เราจะใช้ในภายหลังและเรียกใช้ในไดเรกทอรีที่มี app.js:
การคัดลอกรหัสมีดังนี้:
mkdir คง
ซีดีคง
Curl http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js
และนำรหัสออกในแท็กสไตล์ใน layout.jade และวางไว้ใน Static/style.css และเปลี่ยนแท็กหัวเป็น:
การคัดลอกรหัสมีดังนี้:
ศีรษะ
ชื่อสวัสดีโลก!
link (href = "/static/style.css", rel = "stylesheet")
สคริปต์ (src = "/static/jQuery.js")
สคริปต์ (src = "/static/jade.js")
ใน app.js เราจำลองความเร็วในการดาวน์โหลดทั้งสองเป็นสองวินาทีและเพิ่มก่อน app.use (ฟังก์ชั่น (req, res) {:
การคัดลอกรหัสมีดังนี้:
var static = express.static (path.join (__ dirname, 'Static')))
app.use ('/static', function (req, res, ถัดไป) {
Settimeout (Static, 2000, req, res, ถัดไป)
-
เนื่องจากไฟล์คงที่ภายนอกหน้าของเราจึงมีเวลาโหลดประมาณ 7 วินาที
หากเราส่งคืนส่วนหัวทันทีที่เราได้รับคำขอ HTTP จากนั้นสองส่วนจะรอจนกว่าการดำเนินการแบบอะซิงโครนัสจะเสร็จสมบูรณ์ก่อนที่จะกลับมาสิ่งนี้จะใช้กลไกการเข้ารหัสการส่งสัญญาณการส่งผ่านของ HTTP ใน node.js ตราบใดที่คุณใช้วิธี res.write (), การเข้ารหัสการถ่ายโอน: ส่วนหัว chunked จะถูกเพิ่มโดยอัตโนมัติ ด้วยวิธีนี้ในขณะที่เบราว์เซอร์โหลดไฟล์แบบคงที่เซิร์ฟเวอร์โหนดกำลังรอผลการโทรแบบอะซิงโครนัส ก่อนอื่นให้ลบสองบรรทัดของส่วนใน layout.jade:
การคัดลอกรหัสมีดังนี้:
ส่วน#S1! = S1
ส่วน#S2! = S2
ดังนั้นเราไม่จำเป็นต้องให้วัตถุนี้ใน res.render () {s1: …, s2: …} และเนื่องจาก res.render () จะเรียก res.end () โดยค่าเริ่มต้นเราต้องตั้งค่าฟังก์ชันการโทรกลับด้วยตนเอง เนื้อหาของ layout.jade ไม่จำเป็นต้องอยู่ในฟังก์ชั่นการโทรกลับของ Writeresult () เราสามารถส่งคืนได้เมื่อเราได้รับคำขอนี้ โปรดทราบว่าเราเพิ่มส่วนหัวประเภทเนื้อหาด้วยตนเอง:
การคัดลอกรหัสมีดังนี้:
app.use (function (req, res) {
res.render ('layout', ฟังก์ชั่น (err, str) {
if (err) return res.req.next (err)
Res.SetheAder ('เนื้อหาประเภท', 'text/html; charset = utf-8')
res.write (str)
-
var n = 2
getData.d1 (ฟังก์ชั่น (err, s1data) {
res.write ('<section id = "s1">' + temp.s1 (s1data) + '</section>')
-n || Res.end ()
-
getData.d2 (ฟังก์ชั่น (err, s2data) {
res.write ('<section id = "s2">' + temp.s2 (s2data) + '</section>')
-n || Res.end ()
-
-
ตอนนี้ความเร็วในการโหลดสุดท้ายกลับมาประมาณ 5 วินาที ในการดำเนินการจริงเบราว์เซอร์จะได้รับรหัสส่วนหัวก่อนและโหลดไฟล์คงที่สามไฟล์ ใช้เวลาสองวินาที จากนั้นในวินาทีที่สามส่วนที่ 1 จะปรากฏขึ้นบางส่วน 2 จะปรากฏขึ้นในวินาทีที่ห้าและการโหลดหน้าเว็บจะสิ้นสุดลง ฉันจะไม่ให้ภาพหน้าจอเอฟเฟกต์ภาพหน้าจอเหมือนกับภาพหน้าจอใน 5 วินาทีก่อนหน้า
อย่างไรก็ตามเป็นสิ่งสำคัญที่จะต้องทราบว่าเอฟเฟกต์นี้สามารถทำได้เนื่องจาก getData.d1 เร็วกว่า getData.d2 กล่าวคือบล็อกใดในหน้าเว็บจะถูกส่งคืนก่อนขึ้นอยู่กับว่าใครส่งคืนผลลัพธ์ของการเรียกแบบอะซิงโครนัสของอินเทอร์เฟซที่อยู่ด้านหลัง หากเราเปลี่ยน getData.d1 เพื่อกลับใน 8 วินาทีเราจะส่งคืนบางส่วน 2 ลำดับของ S1 และ S2 กลับด้านและผลลัพธ์สุดท้ายของหน้าเว็บไม่สอดคล้องกับความคาดหวังของเรา
ในที่สุดปัญหานี้นำเราไปสู่ BigPipe ซึ่งเป็นเทคโนโลยีที่สามารถแยกลำดับการแสดงผลของแต่ละส่วนของหน้าเว็บจากลำดับการส่งข้อมูล
แนวคิดพื้นฐานคือการส่งเฟรมเวิร์กทั่วไปของเว็บเพจทั้งหมดก่อนและชิ้นส่วนที่ต้องส่งในภายหลังจะถูกแทนด้วย divs ว่างเปล่า (หรือแท็กอื่น ๆ ):
การคัดลอกรหัสมีดังนี้:
res.render ('layout', ฟังก์ชั่น (err, str) {
if (err) return res.req.next (err)
Res.SetheAder ('เนื้อหาประเภท', 'text/html; charset = utf-8')
res.write (str)
res.write ('<section id = "s1"> </section> <section id = "s2"> </section>')
-
จากนั้นเขียนข้อมูลที่ส่งคืนลงใน JavaScript
การคัดลอกรหัสมีดังนี้:
getData.d1 (ฟังก์ชั่น (err, s1data) {
res.write ('<script> $ ("#s1"). html ("' + temp.s1 (s1data). แทนที่ (/"/g, '// "') + '") </script>')
-n || Res.end ()
-
การประมวลผลของ S2 นั้นคล้ายคลึงกัน ในเวลานี้คุณจะเห็นว่าในวินาทีที่สองของการร้องขอหน้าเว็บกล่องประเปล่าสองกล่องจะปรากฏขึ้นในวินาทีที่ห้าบางส่วน 2 ปรากฏขึ้นและในวินาทีที่แปดบางส่วนปรากฏขึ้นและคำขอเว็บเพจเสร็จสมบูรณ์
ณ จุดนี้เราได้เสร็จสิ้นหน้าเว็บที่ง่ายที่สุดที่ใช้โดย BigPipe Technology
ควรสังเกตว่าหากหน้าเว็บส่วนที่จะเขียนมีแท็กสคริปต์เช่นการเปลี่ยน s1.jade เป็น:
การคัดลอกรหัสมีดังนี้:
H1 บางส่วน 1
.content! = เนื้อหา
สคริปต์
การแจ้งเตือน ("แจ้งเตือนจาก S1.jade")
จากนั้นรีเฟรชหน้าเว็บและคุณจะพบว่าประโยคการแจ้งเตือนไม่ได้ดำเนินการและหน้าเว็บจะมีข้อผิดพลาด ตรวจสอบซอร์สโค้ดและรู้ว่าเป็นข้อผิดพลาดที่เกิดจากสตริงใน <Script> เพียงแทนที่ด้วย <// script>
การคัดลอกรหัสมีดังนี้:
res.write ('<script> $ ("#s1"). html ("' + temp.s1 (s1data). แทนที่ (/"/g, '//"')
ข้างต้นเราอธิบายหลักการของ BigPipe และวิธีการพื้นฐานในการใช้ BigPipe ด้วย Node.JS. และควรใช้ในความเป็นจริงอย่างไร? นี่คือวิธีง่ายๆสำหรับการขว้างก้อนอิฐและหยกรหัสมีดังนี้:
การคัดลอกรหัสมีดังนี้:
var resproto = ต้องการ ('express/lib/response')
resproto.pipe = function (ตัวเลือก, html, แทนที่) {
this.write ('<script>' + '$ ("' + ตัวเลือก + '")' +
(แทนที่ === True? 'Replacewith': 'HTML') +
'("' + html.replace (/"/g, '//"').replace(/<//script>/g,' <// script> ')
'") </script>')
-
ฟังก์ชั่น pipename (res, ชื่อ) {
res.pipecount = res.pipecount || 0
res.pipemap = res.pipemap || -
if (res.pipemap [name]) return
res.pipecount ++
res.pipemap [ชื่อ] = this.id = ['pipe', math.random (). toString (). substring (2), (วันที่ใหม่ ()). valueof ()]. เข้าร่วม ('_')
this.res = res
this.name = ชื่อ
-
resproto.pipename = function (ชื่อ) {
ส่งคืน PIPENAME ใหม่ (ชื่อนี้)
-
resproto.pipelayout = ฟังก์ชั่น (ดูตัวเลือก) {
var res = สิ่งนี้
Object.keys (ตัวเลือก). foreach (ฟังก์ชั่น (คีย์) {
if (ตัวเลือก [key] อินสแตนซ์ของ Pipename) ตัวเลือก [key] = '<span id = "' + ตัวเลือก [คีย์] .id + '"> </span>'
-
res.render (ดู, ตัวเลือก, ฟังก์ชั่น (err, str) {
if (err) return res.req.next (err)
Res.SetheAder ('เนื้อหาประเภท', 'text/html; charset = utf-8')
res.write (str)
if (! res.pipecount) res.end ()
-
-
resproto.pipepartial = function (ชื่อ, มุมมอง, ตัวเลือก) {
var res = สิ่งนี้
res.render (ดู, ตัวเลือก, ฟังก์ชั่น (err, str) {
if (err) return res.req.next (err)
res.pipe ('#'+res.pipemap [ชื่อ], str, true)
-res.pipecount || Res.end ()
-
-
app.get ('/', function (req, res) {
res.pipelayout ('เลย์เอาต์', {
S1: Res.Pipename ('S1Name')
, S2: res.pipename ('S2Name')
-
getData.d1 (ฟังก์ชั่น (err, s1data) {
Res.pipepartial ('S1Name', 'S1', S1Data)
-
getData.d2 (ฟังก์ชั่น (err, s2data) {
Res.pipepartial ('S2Name', 'S2', S2Data)
-
-
เพิ่มสองส่วนใน layout.jade:
การคัดลอกรหัสมีดังนี้:
ส่วน#S1! = S1
ส่วน#S2! = S2
ความคิดที่นี่คือเนื้อหาของท่อจะต้องวางด้วยแท็ก Span ก่อนรับข้อมูลแบบอะซิงโครนัสและแสดงรหัส HTML ที่สอดคล้องกันก่อนที่จะส่งออกไปยังเบราว์เซอร์และแทนที่องค์ประกอบ Span ของตัวยึดด้วยวิธีการแทนที่ของ JQuery
รหัสของบทความนี้อยู่ที่ https://github.com/undozen/bigpipe-on-node ฉันได้ทำแต่ละขั้นตอนในการกระทำ ฉันหวังว่าคุณจะสามารถเรียกใช้งานได้ในพื้นที่และแฮ็คมัน เนื่องจากขั้นตอนต่อไปที่เกี่ยวข้องกับลำดับการโหลดคุณต้องเปิดเบราว์เซอร์ด้วยตัวเองเพื่อสัมผัสกับมันและไม่สามารถมองเห็นได้จากภาพหน้าจอ (ที่จริงแล้วมันควรจะนำไปใช้กับแอนิเมชั่น GIF แต่ฉันขี้เกียจเกินไปที่จะทำ)
ยังมีพื้นที่มากมายสำหรับการเพิ่มประสิทธิภาพเกี่ยวกับการฝึกฝน BigPipe ตัวอย่างเช่นเป็นการดีที่สุดที่จะตั้งค่าเวลาเวลาทริกเกอร์สำหรับเนื้อหาของท่อ หากข้อมูลที่เรียกว่าจะส่งคืนแบบอะซิงโครนัสอย่างรวดเร็วคุณไม่จำเป็นต้องใช้ BigPipe คุณสามารถสร้างหน้าเว็บโดยตรงและส่งออกมา คุณสามารถรอจนกว่าคำขอข้อมูลจะเกินระยะเวลาหนึ่งก่อนที่จะใช้ BigPipe เมื่อเทียบกับ AJAX การใช้ BIGPIPE ไม่เพียง แต่บันทึกจำนวนคำขอจากเบราว์เซอร์ไปยังเซิร์ฟเวอร์ Node.js แต่ยังบันทึกจำนวนคำขอจากเซิร์ฟเวอร์ Node.js ไปยังแหล่งข้อมูล อย่างไรก็ตามให้แบ่งปันวิธีการเพิ่มประสิทธิภาพและวิธีการฝึกซ้อมที่เฉพาะเจาะจงหลังจากเครือข่ายสโนว์บอลใช้ BigPipe