Rumah  >  Artikel  >  hujung hadapan web  >  Node.js melaksanakan BigPipe secara terperinci_node.js

Node.js melaksanakan BigPipe secara terperinci_node.js

WBOY
WBOYasal
2016-05-16 16:29:031561semak imbas

BigPipe ialah teknologi yang dibangunkan oleh Facebook untuk mengoptimumkan kelajuan memuatkan halaman web. Hampir tiada artikel yang dilaksanakan menggunakan node.js di Internet Malah, pelaksanaan BigPipe dalam bahasa lain, bukan sahaja node.js, jarang berlaku di Internet. Lama selepas teknologi ini muncul, saya fikir selepas keseluruhan bingkai halaman web dihantar terlebih dahulu, satu lagi atau beberapa permintaan ajax digunakan untuk meminta modul dalam halaman. Sehingga tidak lama dahulu, saya mengetahui bahawa konsep teras BigPipe adalah menggunakan hanya satu permintaan HTTP, tetapi elemen halaman dihantar tidak teratur.

Ia akan menjadi lebih mudah apabila anda memahami konsep teras ini Terima kasih kepada ciri tak segerak node.js, adalah mudah untuk melaksanakan BigPipe dengan node.js. Artikel ini akan menggunakan contoh langkah demi langkah untuk menerangkan asal usul teknologi BigPipe dan pelaksanaan mudah berdasarkan node.js.

Saya akan menggunakan ekspres untuk menunjukkan untuk memudahkan, kami memilih jed sebagai enjin templat, dan kami tidak menggunakan ciri sub-templat (separa) enjin, tetapi menggunakan HTML selepas sub-templat dipaparkan sebagai. data templat induk.

Mula-mula buat folder nodejs-bigpipe dan tulis fail package.json seperti berikut:

Salin kod Kod adalah seperti berikut:

{
"nama": "percubaan besar"
, "versi": "0.1.0"
, "peribadi": benar
, "dependencies": {
"express": "3.x.x"
, "consolidate": "terkini"
, "jed": "terkini"
}
}

Jalankan npm install untuk memasang ketiga-tiga perpustakaan ini digunakan untuk memudahkan panggilan jed.

Mari cuba yang paling mudah dahulu, dua fail:

app.js:

Salin kod Kod adalah seperti berikut:

var express = memerlukan('express')
, kontra = memerlukan('satukan')
, jed = memerlukan('jed')
, laluan = memerlukan('jalan')

apl var = ekspres()

app.engine('jed', kontra.jed)
app.set('views', path.join(__dirname, 'views'))
app.set('enjin lihat', 'jed')

app.use(fungsi (req, res) {
res.render('layout', {
s1: "Helo, saya bahagian pertama."
, s2: "Helo, saya bahagian kedua."
})
})

app.listen(3000)

pandangan/layout.jed

Salin kod Kod adalah seperti berikut:

doctype html

kepala
tajuk Hello, World!
gaya
Bahagian {
        jidar: 20px auto;
Sempadan: 1px bertitik kelabu;
       lebar: 80%;
Tinggi: 150px;
}

bahagian#s1!=s1
bahagian#s2!=s2

Kesannya adalah seperti berikut:

Seterusnya kami meletakkan dua templat bahagian ke dalam dua fail templat yang berbeza:

pandangan/s1.jed:

Salin kod Kod adalah seperti berikut:

h1 Separa 1
.content!=content

pandangan/s2.jed:

Salin kod Kod adalah seperti berikut:

h1 Separa 2
.content!=content

Tambahkan beberapa gaya pada gaya susun atur.jed

Salin kod Kod adalah seperti berikut:

bahagian h1 {
saiz fon: 1.5;
pelapik: 10px 20px;
jidar: 0;
bahagian bawah sempadan: 1px bertitik kelabu;
}
bahagian div {
jidar: 10px;
}

Tukar bahagian app.use() app.js kepada:

Salin kod Kod adalah seperti berikut:

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({ kandungan: "Helo, saya bahagian pertama." })
, s2: temp.s2({ kandungan: "Helo, saya bahagian kedua." })
})
})

Sebelum kami berkata "gunakan HTML selepas sub-templat dipaparkan sebagai data templat induk", itulah maksudnya kedua-dua kaedah temp.s1 dan temp.s2 akan menghasilkan dua fail, s1.jade dan kod HTML s2.jade, dan kemudian gunakan dua keping kod ini sebagai nilai dua pembolehubah s1 dan s2 dalam layout.jade.

Halaman sekarang kelihatan seperti ini:

Secara umumnya, data kedua-dua bahagian diperoleh secara berasingan - sama ada dengan menanyakan pangkalan data atau permintaan RESTful, kami menggunakan dua fungsi untuk mensimulasikan operasi tak segerak tersebut.

Salin kod Kod adalah seperti berikut:

var getData = {
d1: fungsi (fn) {
​​​ setTimeout(fn, 3000, null, { content: "Helo, saya bahagian pertama." })
}
, d2: fungsi (fn) {
​​​ setTimeout(fn, 5000, null, { content: "Hello, saya bahagian kedua." })
}
}

Dengan cara ini, logik dalam app.use() akan menjadi lebih rumit Cara paling mudah untuk menanganinya ialah:

Salin kod Kod adalah seperti berikut:

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

Ini juga boleh mendapatkan hasil yang kita inginkan, tetapi dalam kes ini, ia akan mengambil masa 8 saat penuh untuk kembali.

Malah, logik pelaksanaan menunjukkan bahawa getData.d2 dipanggil hanya selepas hasil getData.d1 dikembalikan, dan tiada pergantungan sedemikian antara kedua-duanya. Kita boleh menggunakan perpustakaan seperti async yang mengendalikan panggilan tak segerak JavaScript untuk menyelesaikan masalah ini, tetapi mari tulis dengan tangan di sini:

Salin kod Kod adalah seperti berikut:

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

Ini hanya akan mengambil masa 5 saat.

Sebelum pengoptimuman seterusnya, kami menambah perpustakaan jquery dan meletakkan gaya css ke dalam fail luaran, kami juga menambah fail runtime.js yang diperlukan untuk menggunakan templat jed pada bahagian penyemak imbas yang akan kami gunakan kemudian. . Jalankan dalam direktori yang mengandungi app.js:

Salin kod Kod adalah seperti berikut:

mkdir statik
cd statik
curl http://code.jquery.com/jquery-1.8.3.min.js -o jquery.js
ln -s ../node_modules/jade/runtime.min.js jade.js

Dan keluarkan kod dalam teg gaya dalam layout.jade dan masukkan ke dalam statik/style.css, dan kemudian tukar teg kepala kepada:

Salin kod Kod adalah seperti berikut:

kepala
tajuk Hello, World!
pautan(href="/static/style.css", rel="stylesheet")
skrip(src="/static/jquery.js")
skrip(src="/static/jade.js")

Dalam app.js, kami mensimulasikan kelajuan muat turun kedua-duanya kepada dua saat dan menambah:
sebelum app.use(function (req, res) {

Salin kod Kod adalah seperti berikut:

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

Halaman kami kini dimuatkan dalam masa sekitar 7 saat disebabkan oleh fail statik luaran.

Jika kami memulangkan bahagian kepala sebaik sahaja kami menerima permintaan HTTP, dan kemudian kedua-dua bahagian menunggu sehingga operasi tak segerak selesai sebelum kembali, ini menggunakan mekanisme pengekodan pemindahan chunked HTTP. Dalam node.js, selagi anda menggunakan kaedah res.write(), pengepala Pemindahan-Pengekodan: chunked akan ditambahkan secara automatik. Dengan cara ini, semasa penyemak imbas memuatkan fail statik, pelayan nod sedang menunggu keputusan panggilan tak segerak Mari kita padamkan kedua-dua baris ini dalam bahagian dalam susun atur. jed:

Salin kod Kod adalah seperti berikut:

bahagian#s1!=s1
bahagian#s2!=s2

Jadi kita tidak perlu memberikan objek { s1: …, s2: … } dalam res.render(), dan kerana res.render() akan memanggil res.end() secara lalai, kita perlu secara manual set render selepas selesai Fungsi panggil balik menggunakan kaedah res.write() di dalamnya. Kandungan layout.jade tidak perlu berada dalam fungsi panggil balik writeResult() Kami boleh kembali apabila menerima permintaan ini. Harap maklum bahawa kami menambahkan pengepala jenis kandungan:

Salin kod Kod adalah seperti berikut:

app.use(function (req, res) {
res.render('layout', function (err, str) {
Jika (err) kembalikan res.req.next(err)
res.setHeader('jenis kandungan', 'teks/html; charset=utf-8')
res.write(str)
})
var n = 2
getData.d1(fungsi (err, s1data) {
res.write('
' temp.s1(s1data) '
')
--n ||. res.end()
})
getData.d2(fungsi (err, s2data) {
res.write('
' temp.s2(s2data) '
')
--n ||. res.end()
})
})

Kelajuan pemuatan akhir kini kembali kepada sekitar 5 saat. Dalam operasi sebenar, penyemak imbas mula-mula menerima bahagian kepala kod, dan kemudian memuatkan tiga fail statik, yang mengambil masa dua saat, Kemudian pada saat ketiga, bahagian Separa 1 muncul, dan bahagian Separa 2 muncul pada saat ke-5, dan halaman web dimuatkan. Saya tidak akan mengambil tangkapan skrin, kesan tangkapan skrin adalah sama seperti tangkapan skrin 5 saat sebelumnya.

Tetapi sila ambil perhatian bahawa kesan ini boleh dicapai kerana getData.d1 lebih pantas daripada getData.d2 Dalam erti kata lain, sekatan dalam halaman web yang dikembalikan dahulu bergantung pada hasil panggilan tak segerak antara muka di belakang yang kembali dahulu. Jika kita meletakkan getData. Jika d1 ditukar kepada 8 saat untuk kembali, Separa 2 akan dikembalikan dahulu, dan susunan s1 dan s2 akan diterbalikkan.

Soalan ini akhirnya membawa kita ke BigPipe ialah teknologi yang boleh memisahkan susunan paparan setiap bahagian halaman web daripada pesanan penghantaran data.

Idea asasnya ialah memindahkan bingkai umum keseluruhan halaman web dahulu, dan bahagian yang perlu dipindahkan kemudian diwakili oleh div kosong (atau tag lain):

Salin kod Kod adalah seperti berikut:

res.render('layout', function (err, str) {
jika (err) kembalikan res.req.next(err)
res.setHeader('jenis kandungan', 'teks/html; charset=utf-8')
res.write(str)
res.write('
')
})

Kemudian tulis data yang dikembalikan menggunakan JavaScript

Salin kod Kod adalah seperti berikut:

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

s2 dikendalikan dengan cara yang sama. Pada masa ini, anda akan melihat bahawa dalam saat kedua meminta halaman web, dua kotak bertitik kosong muncul, pada saat kelima, bahagian Separa 2 muncul, dalam saat kelapan, bahagian Separa 1 muncul dan halaman web permintaan selesai.

Pada ketika ini, kami telah melengkapkan halaman web yang dilaksanakan dengan teknologi BigPipe yang paling mudah.

Perlu diambil perhatian bahawa serpihan halaman web yang akan ditulis mempunyai tag skrip Contohnya, tukar s1.jade kepada:

Salin kod Kod adalah seperti berikut:

h1 Separa 1
.content!=content
skrip
makluman("makluman daripada s1.jade")

Kemudian muat semula halaman web dan anda akan mendapati bahawa amaran tidak dilaksanakan dan akan terdapat ralat pada halaman web. Semak kod sumber dan ketahui bahawa ralat itu disebabkan oleh yang muncul dalam rentetan di dalam

Salin kod Kod adalah seperti berikut:
res.write('<script>$("#s1").html("' temp.s1(s1data).replace(/"/g, '\"').replace(/</script>/ g, '<
\/skrip>') '")')
Di atas kami telah menerangkan prinsip BigPipe dan kaedah asas untuk melaksanakan BigPipe menggunakan node.js. Dan bagaimana ia harus digunakan dalam amalan? Kaedah mudah disediakan di bawah, hanya untuk rujukan Kodnya adalah seperti berikut:

Salin kod Kod adalah seperti berikut:

var resProto = memerlukan('express/lib/response')
resProto.pipe = fungsi (pemilih, html, ganti) {
  this.write('<script>' '$("' selector '").' <br>     (ganti === benar ? 'replaceWith' : 'html') <br>     '("' html.replace(/"/g, '\"').replace(/</script>/g, '<\/script>')
    '")')
}
function PipeName (res, name) {
  res.pipeCount = res.pipeCount || 0
  res.pipeMap = res.pipeMap || {}
  jika (res.pipeMap[name]) kembali
  res.pipeCount
  res.pipeMap[name] = this.id = ['pipe', Math.random().toString().substring(2), (New Date()).valueOf()].join('_')
  this.res = res
  this.name = nama
}
resProto.pipeName = fungsi (nama) {
  kembalikan PipeName baharu(ini, nama)
}
resProto.pipeLayout = fungsi (pandangan, pilihan) {
  var res = ini
  Object.keys(options).forEach(function (key) {
    if (options[key] instanceof PipeName) options[key] = ''
  })
  res.render(pandangan, pilihan, fungsi (err, str) {
    jika (err) kembalikan res.req.next(err)
    res.setHeader('jenis kandungan', 'teks/html; charset=utf-8')
    res.write(str)
    jika (!res.pipeCount) res.end()
  })
}
resProto.pipePartial = fungsi (nama, paparan, pilihan) {
  var res = ini
  res.render(pandangan, pilihan, fungsi (err, str) {
    jika (err) kembalikan res.req.next(err)
    res.pipe('#' res.pipeMap[nama], str, true)
    --res.pipeCount || res.end()
  })
}
app.get('/', function (req, res) {
  res.pipeLayout('layout', {
      s1: res.pipeName('s1name')
    , s2: res.pipeName('s2name')
  })
  getData.d1(fungsi (err, s1data) {
    res.pipePartial('s1name', 's1', s1data)
  })
  getData.d2(fungsi (err, s2data) {
    res.pipePartial('s2name', 's2', s2data)
  })
})

还要在 susun atur.jed 把两个 bahagian 添加回来:

复制代码 代码如下:

bahagian#s1!=s1
bahagian#s2!=s2
pipa出给浏览器,用 jQuery 的 replaceWith 方法把占位的 span 元素替换掉。

本文的代码在

https://github.com/undozen/bigpipe-on-node ,我把每一步做成一个 commit 了,希望佡帐望佡menggodam 一下看看。因为后面几步涉及到加载顺序了,确实要自己打开浏览器才能体验到水王了(其实应该可以用 gif 动画实现,但是我懒得做了)。

关于 BigPipe 的实践还有很大的优化空间,比如说,要 pipe 的内容最好设置一个触发的是受用的数据很快返回,就不需要用 BigPipe,直接生成网页送出即可,可以等到数据请求超过一定时间才用 BigPipe。使用 BigPipe 相比 ajax 既节省了晨节省了浏览 node.求数,又节省了 node.js 服务器到数据源的请求数。不过具体的优化和实践方法,等到雪球网用上 BigPipe 以后再分享吧。

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn