《Flutter實戰》電子書官網加速、定製分享(網站經過Gitbook生成)

本文主要經過《Flutter實戰》電子書官網案例,給你們分享一下如何在不修改gitbook源碼的基礎上對gitbook生成的網站進行站點加速、PV統計和自定義卡片插入。本文同時也是ajax-hook(一個用於全局攔截瀏覽器ajax請求的庫)的一個最佳實踐。javascript

去年的某一天(忘了具體時間),咱們在github上開源了《Flutter實戰》電子書後,用戶反響強烈,指望可以提供線上電子書。爲了讓用戶可以便捷的在線觀看,咱們使用gitbook生成了《Flutter實戰》電子書的web站點,地址https://book.flutterchina.club/。css

《Flutter實戰》電子書官網上線後,咱們遇到了三個問題:html

  1. 在訪問高峯時段,受服務器帶寬限制,網站很是卡。
  2. Gitbook 生成的網站,是類SPA網站,在無代碼侵入的狀況下,如何正確統計PV。
  3. 如何給每一個章節中插入咱們自定義的卡片。

同時咱們但願,這三個問題的解決方案是不須要去更改gitbook的源碼,是非侵入的。下面進行詳細說明:前端

網站加速

電子書上線後,日均PV達到5W,當時,咱們租的騰訊雲EMS,因爲咱們作社區都是共享免費的,沒有任何收入來源,因此服務器費用都是自討腰包,所以帶寬當時選的是最低配1M的,這就致使在上午和下午的訪問高峯時段,網站很是卡,常常一個頁面要等6-10s才能打開java

起初咱們也想去擴容服務器帶寬,但很明顯,這須要銀子,因此當時不少用戶反饋網站卡時,咱們只能建議他們去github上去閱讀。git

事情到了2020年3月份終於迎來了起色,CDN廠商貓雲找到了咱們,而且願意給咱們作免費的CDN加速。可是對gitbook生成的網站上CDN時須要對工程進行一些改造。咱們的CDN加速域名是https://pcdn.flutterchina.club ,咱們能夠將整個網站所有上CDN,這就有一個問題,咱們須要在用戶訪問https://book.flutterchina.club/ 時給重定向到https://pcdn.flutterchina.club ,但因爲咱們https://book.flutterchina.club/這個域名已經外發好久,在用戶羣體中已經有了認知,而且這個域名語義也比較明確,所以咱們不想更改域名,因此咱們須要在原網站中,讓更多的內容上CDN。github

咱們的作法是將整個網站所有上傳到CDN,而後在原站點中,將html中的圖片、css、js資源引用連接所有替換爲cdn連接,這樣圖片、css、js文件流量便會由CDN來承擔。web

光作到這點還不夠,由於電子書每個章節的內容都是一個html,而這些html請求仍是在咱們的服務器,而整個網站流量佔比中,html請求流量佔了80%,這是由於電子書基本都是文字,而文字最終會包含在html中。ajax

經過研究,gitbook網站在章節切換時,並非一次連接跳轉,而是經過ajax請求拉取新章節html文檔,而後再解析出正文部分替換掉當前頁面的正文。那麼方法便很清楚了:咱們只要將ajax請求的流量也打到CDN就能夠了!可是問題來了,咱們如何得到ajax請求的時機並進行連接替換呢?咱們先拋出問題,在本文最後解答。json

PV統計

咱們的網站是使用百度統計來記錄頁面瀏覽狀況,但因爲章節切換時是經過ajax請求拉取新章節html文檔,因此只有當用戶第一次進入咱們網站時頁面的PV纔會被統計到,然後續站內跳轉都不能被統計到,這和SPA網站路由切換不能被統計的緣由是相同的。通過分析,gitbook生成的網站頁面在切換時是經過瀏覽器history AP來同步瀏覽記錄的,這和單頁應用框架Vue/React Router的history模式原理是一致的,即:頁面跳轉時,調用history.pushState來插一條記錄,此時瀏覽器的url也會更新。那麼,要統計每個章節的瀏覽記錄,則只須要去監聽瀏覽器的url是否變化,在url變化時,手動調用一次統計上報。因此,如今問題轉化爲如何監聽url發生變化?

咱們知道,url變化後瀏覽器不跳轉的狀況只有兩種,一種是hash(哈希,url中#號後面的內容)發生了變化,對應的會觸發onhashchange事件;另外一種就是經過調用history.pushState致使,而咱們的狀況正式這一種,那在調用history.pushState時瀏覽器有沒有像hash變化時會觸發一個事件呢?很遺憾,答案是否認的。那麼咱們該怎麼辦?

起初世界上沒有光,因而上帝說要有光,因而便有了光。

沒有事件,咱們造一個不就得了。

怎麼造?思路咱們能夠參考殺毒軟件,殺毒軟件的主動防護原理是經過攔截應用的一些操做行爲來辨識是否存在異常,具體實現是會在底層攔截操做系統API,攔截的方式是在操做系統API入口裏先寫入跳轉到自定義方法的指令,這樣一來,當應用程序調用某個系統API後,便會首先跳轉到殺毒軟件定義的方法中,而後殺毒軟件結合應用的整個行爲鏈條來判斷是否存在異常,若是沒有異常,則跳回原API繼續執行,若是存在異常則阻斷。這種攔截方式,有一個專用的術語叫API掛鉤(Hook)。咱們能夠採用這種思想來攔截history.pushState,代碼以下:

function hookAPI(api, ob, fn) {
    return function () {
        var result = api.apply(ob, [].slice.call(arguments));
        //延遲1s上報
        setTimeout(fn, 1000);
        return result;
    }
}

if (history.pushState) {
    history.pushState = hookAPI(history.pushState, history, function(){
     //上報PV
     _hmt.push(['_trackPageview', location.pathname]);
    });
}
複製代碼

如今咱們解決了PV上報的問題,可是,如今請打開思路,在咱們的網站中,難道咱們就只能經過這一種方式來得到頁面切換的時機嗎?能夠告訴你,答案是否認的!那還有什麼方法呢?咱們先按下不表,等再聊完最後一個問題,咱們再給出答案。

給文章內容中插卡片

《Flutter實戰》的紙質書3月中旬終於出版了,爲了告知讀者,咱們須要在電子書網站上在每一篇文章頂部插入一個購買卡片,效果以下:

插入卡片

直接能夠想到的做法有3種:

  1. 找到gitbook的佈局代碼,看看是否能夠自定義;通過簡單幾回嘗試,發現咱們要插入卡片的右側內容區域在站內頁面跳轉時都是動態生成的,並不能再gitbook的佈局代碼中修改,嘗試失敗。
  2. 給每一篇文章內容中經過腳本插一段卡片的的h5代碼。可行!但不優雅。
  3. 在頁面切換後,經過jQuery動態將卡片dom插入指定區域。代碼相對簡單(用過jQuery的人天然懂),可是如今問題又轉化爲如何獲取頁面切換的時機,這個咱們在上面PV統計部分已經討論過,不在贅述。

最終解決方案-銀彈

如今請你們仔細想一想上面提到的三個問題,有沒有一個銀彈,能夠同時解決這三個問題。答案是確定的!如今咱們先回顧一下上面三個問題的關鍵:

  1. 從cdn請求html,問題關鍵是如何得到ajax請求的時機並進行鏈接替換。

  2. PV統計和文章內容中插卡片,問題關鍵是如何獲取頁面切換的時機。

而咱們已經知道,頁面切換時是要經過ajax請求獲取新頁面的html文檔的,因此,咱們只須要能截獲到ajax請求的時機,即可以完成上面的三件事!

那麼如何攔截ajax請求呢?經過分析gitbook生成的網站的代碼,發現依賴了jQuery,若是gitbook的ajax請求都是經過jQuery發起,那麼咱們只需配置一下jQuery ajax的鉤子便可,但通過試驗,發現只有請求search_plus_index.json這麼一個是經過jQuery發起的,其它的ajax請求是gitbook本身經過XMLHttpRequest來發起的。那麼,咱們想在不侵入gitbook代碼的基礎上來攔截ajax請求,怎麼作?其實答案很簡單,若是讀者看過我以前的博客,那麼應該知道我曾開源過一個專門用於攔截瀏覽器XMLHttpRequest的庫ajax-hook,他很是精巧,具體使用能夠參考github上介紹,咱們這裏直接使用便可:

// 經過ajax-hook庫提供的hook方法直接全局攔截XMLHttpRequest對象
ah.hook({
    open: function (arg, xhr) {
        // 若是不是本地調試,ajax流量直接打到cdn, 注意CDN服務器啓用了CORS跨域
        if (location.hostname !== 'localhost') {
            if (arg[1][0] === '.') arg[1] = arg[1].slice(1);
            arg[1] = "https://pcdn.flutterchina.club" + arg[1]
        }
    },
    setRequestHeader: function (arg, xhr) {
        // 發現gitbook在發起ajax請求是會設置一些自定義頭,爲了使CORS生效,幹掉這些自定義頭
        // 使跨域請求變爲一個簡單請求,避免預檢失敗。
        if (arg[0] !== 'Accept') return true;
    },
    onload:function(){
        setTimeout(function(){
          //添加實體書卡片
           if ( $("#book-search-results .ad").length === 0) {
                $(".ad").clone().show().prependTo("#book-search-results")
            }
          //統計PV, 網站打開時會首先請求一次search_plus_index.json文件,若是判斷是該文件請求,則排除
           var extension=xhr.responseURL.split(".").pop()
           if(extension!=='json'){
               _hmt.push(['_trackPageview', location.pathname]);
           }
        });
    }
})
複製代碼

注意:咱們CDN開啓了CORS,容許前端跨域,關於CORS ,如不清楚請自行百度。

如今,咱們的三個問題就所有解決了,加速後,目前網站訪問高峯時段也能接近於秒開,同時PV已能準確收集,卡片也正常插入,讀者能夠自行去《Flutter實戰》電子書官網體驗效果。

帶點貨

《Flutter實戰》電子書官網加速和定製正是ajax-hook開源庫的一個最佳實踐,ajax-hook 最初是筆者爲了研究目的寫的一段代碼,當時因爲代碼量只有50行,筆者認爲很精巧,因而便傳到了github,沒想到發到github上以後受到了不少人star,更沒想到star能破千,還有不少一線互聯網公司都在用。考慮到最初的代碼只是一個攔截的核心功能(雖而後來有修改)但API仍是比較偏底層,使用起來很不方便,而且容易出錯,爲了完全解決這個問題,因而這個週末,筆者又重構了一次,發佈了全新的2.0版本,在2.0版本中,增長了更易用、更強大的的proxy(...) API,讀者有興趣能夠去github ajax-hook 主頁瞭解,若是以爲有用,也歡迎star、打賞。

相關文章
相關標籤/搜索