雖然HTTP/2目前已經(jīng)逐漸的在各大網(wǎng)站上開始了使用,但是在目前最新的Node.js上仍然處于實(shí)驗(yàn)性API,還沒有能有效解決生產(chǎn)環(huán)境各種問題的應(yīng)用示例。因此在應(yīng)用HTTP/2的道路上我自己也遇到了許多坑,下面介紹了項(xiàng)目的主要架構(gòu)與開發(fā)中遇到的問題及解決方式,也許會(huì)對(duì)你有一點(diǎn)點(diǎn)啟示。
配置
雖然W3C的規(guī)范中沒有規(guī)定HTTP/2協(xié)議一定要使用ssl加密,但是支持非加密的HTTP/2協(xié)議的瀏覽器實(shí)在少的可憐,因此我們有必要申請(qǐng)一個(gè)自己的域名和一個(gè)ssl證書。
本項(xiàng)目的測(cè)試域名是 you.keyin.me ,首先我們?nèi)ビ蛎峁┥棠前褱y(cè)試服務(wù)器的地址綁定到這個(gè)域名上。然后使用Let's Encrypt生成一個(gè)免費(fèi)的SSL證書:
sudo certbot certonly --standalone -d you.keyin.me
輸入必要信息并通過驗(yàn)證之后就可以在 /etc/letsencrypt/live/you.keyin.me/ 下面找到生成的證書了。
改造Koa
Koa是一個(gè)非常簡潔高效的Node.js服務(wù)器框架,我們可以簡單改造一下來讓它支持HTTP/2協(xié)議:
class KoaOnHttps extends Koa { constructor() { super(); } get options() { return { key: fs.readFileSync(require.resolve('/etc/letsencrypt/live/you.keyin.me/privkey.pem')), cert: fs.readFileSync(require.resolve('/etc/letsencrypt/live/you.keyin.me/fullchain.pem')) }; } listen(...args) { const server = http2.createSecureServer(this.options, this.callback()); return server.listen(...args); } redirect(...args) { const server = http.createServer(this.callback()); return server.listen(...args); }}const app = new KoaOnHttps();app.use(sslify());//...app.listen(443, () => {logger.ok('app start at:', `https://you.keyin.cn`);});// receive all the http request, redirect them to httpsapp.redirect(80, () => {logger.ok('http redirect server start at', `http://you.keyin.me`);});上述代碼簡單基于Koa生成了一個(gè)HTTP/2服務(wù)器,并同時(shí)監(jiān)聽80端口,通過sslify中間件的幫助自動(dòng)將http協(xié)議的連接重定向到https協(xié)議。
靜態(tài)文件中間件
靜態(tài)文件中間件主要用來返回url所指向的本地靜態(tài)資源。在http/2服務(wù)器中我們可以在訪問html資源的時(shí)候通過服務(wù)器推送(Server push)將該頁面所依賴的js/css/font等資源一起推送回去。具體代碼如下:
const send = require('koa-send');const logger = require('../util/logger');const { push, acceptsHtml } = require('../util/helper');const depTree = require('../util/depTree');module.exports = (root = '') => { return async function serve(ctx, next) { let done = false; if (ctx.method === 'HEAD' || ctx.method === 'GET') { try { // 當(dāng)希望收到html時(shí),推送額外資源。 if (/(/.html|//[/w-]*)$/.test(ctx.path)) { depTree.currentKey = ctx.path; const encoding = ctx.acceptsEncodings('gzip', 'deflate', 'identity'); // server push for (const file of depTree.getDep()) { // server push must before response! // https://huangxuan.me/2017/07/12/upgrading-eleme-to-pwa/#fast-skeleton-painting-with-settimeout-hack push(ctx.res.stream, file, encoding); } } done = await send(ctx, ctx.path, { root }); } catch (err) { if (err.status !== 404) { logger.error(err); throw err; } } } if (!done) { await next(); } };};
新聞熱點(diǎn)
疑難解答
圖片精選