This article mainly uses the website of Flutter Practical ebook to share how to implement site acceleration, PV statistics and custom card insertion of websites generated by Gitbook without modifying gitbook source code. This article is also a best practice for Ajax-hook, a library for globally intercepting browser Ajax requests.

One day last year (I don’t know when) we opened source the Ebook Flutter To Github, and there was a lot of reaction from users who wanted to offer an online ebook. Convenient to allow the user to watch online, we use gitbook generates the Flutter of actual combat the web site of the e-book, https://book.flutterchina.club/ address.

When the Website of Flutter Practical ebook was launched, we encountered three problems:

  1. During peak hours, the site is very congested due to server bandwidth limitations.
  2. The website generated by Gitbook is a spa-like website. In the case of no code invasion, how to correctly count PV.
  3. How to insert our custom cards into each chapter.

At the same time, we hope that the solution to these three problems is non-invasive and does not need to change the source code of Gitbook. The following is a detailed description:

Speed up

E-books online, the average daily PV reach 5 w, at that time, we rent tencent cloud EMS, because we do community are sharing free, no source of income, so the cost of server is the pocket, so the bandwidth was chosen is matched by a minimum of 1 m, this leads to in the morning and afternoon peak time of access, web site is very card, Often it takes 6-10s for a page to open.

At first we wanted to expand the bandwidth of the server, but it was clear that it would cost money, so when a lot of users came back with cards, we suggested that they go to Github and read them.

Things finally took a turning point in March 2020. The CDN manufacturer Maoyun found us and was willing to provide us with free CDN acceleration. However, the CDN generated by Gitbook on the website requires some engineering changes. Our CDN to accelerate the domain name https://pcdn.flutterchina.club, we can take the whole site on CDN, it has a problem, We need to redirect to users when they visit https://book.flutterchina.club/, https://pcdn.flutterchina.club, But because we https://book.flutterchina.club/ this domain name has been out for a long time, in the user groups have cognitive, semantic and the domain name also is more clear, so we don’t want to change the domain name, so we need to be in the original site, let more content on CDN.

What we do is to upload the whole website to CDN, and then replace all the links of images, CSS and JS resources in HTML with CDN links in the original site, so that the traffic of images, CSS and JS files will be borne by CDN.

That’s not enough, because each chapter of the e-book is an HTML, and the HTML requests are still on our server, and 80% of the traffic is HTML requests, because e-books are basically text, and text ends up in HTML.

Through research, gitbook website does not skip a link when switching chapters. Instead, it pulls the HTML document of the new chapter through Ajax request, and then parses the body part to replace the body of the current page. The method is clear: we simply call the traffic of ajax requests to the CDN as well! But the question is, how do we get the timing of ajax requests and do link substitution? We’ll start with a question that will be answered at the end of this article.

PV statistics

Our website is to use baidu statistics to record page views, but as a result of chapters when switching is a new chapter through ajax request pull the HTML document, so only when the user to enter our website for the first time the page PV will be counted, and the subsequent jump within the station can be counted, the routing switch and SPA website cannot be the cause of the statistics are the same. After analysis, the website pages generated by Gitbook are switched through the browser history AP to synchronize browsing records, which is consistent with the principle of the history mode of the single-page application framework Vue/React Router, namely: When the page jumps, call history.pushState to insert a record, and the browser URL will be updated. Therefore, to collect the browsing records of each chapter, you only need to monitor whether the URL of the browser changes. When the URL changes, you need to manually invoke a statistics report. So now the question becomes how do you listen for url changes?

As we know, there are only two cases when the browser does not jump after the URL changes. One is when the hash(the content after the hash in the URL) changes and the corresponding onHashchange event is triggered. The other way is by calling history.pushState, and in our case, does the browser hash an event when it does that? Sadly, the answer is no. So what do we do?

At first there was no light in the world, and God said, let there be light, and there was light.

There’s no event. Let’s just make one.

How to build? Thinking we can refer to antivirus software, antivirus software principle of active defense are some of the operations by intercepting application behavior to identify whether there is abnormal, the specific implementation is to be able to intercept in the underlying operating system API, to intercept the way in the entrance of the operating system apis to write to jump to the custom method instruction, so that when the application calls a system API, It will jump to the method defined by the anti-virus software first, and then the anti-virus software will judge whether there is an exception in combination with the whole behavior chain of the application. If there is no exception, it will jump back to the original API to continue execution, and if there is an exception, it will block. There is a term for this type of interception called AN API Hook. We can use this idea to intercept history.pushState as follows:

function hookAPI(api, ob, fn) {
    return function () {
        var result = api.apply(ob, [].slice.call(arguments));
        // Delay reporting for 1s
        setTimeout(fn, 1000);
        returnresult; }}if (history.pushState) {
    history.pushState = hookAPI(history.pushState, history, function(){
     / / report to PV
     _hmt.push(['_trackPageview', location.pathname]);
    });
}
Copy the code

Now we have solved the problem of PV reporting, but please open your mind now. In our website, can we only get the timing of page switching in this way? I can tell you, the answer is no! So what else is there to do? Let’s hit the “no” button, and we’ll give you the answer after we finish our last question.

Insert cards into the content of the article

The paper book of Flutter Practical was finally published in mid-March. To inform readers, we need to insert a purchase card at the top of every article on the e-book website. The effect is as follows:

Three approaches can be immediately thought of:

  1. Find the gitbook layout code to see if you can customize it; After several simple attempts, we found that the content area on the right side of the card we wanted to insert was dynamically generated when the page jumped in the site and could not be modified in the layout code of Gitbook, so the attempt failed.
  2. Insert a card h5 code into the script for each article content. Feasible! But not elegant.
  3. After the page switch, the card DOM is dynamically inserted into the specified area using jQuery. The code is relatively simple (anyone who has used jQuery will understand), but now the problem is how to get the timing of the page switch, which we discussed in PV statistics above.

Final solution – Silver bullet

Now please think about the three problems mentioned above. Is there a silver bullet that can solve all three problems at the same time? The answer is yes! Now let’s review the key points of the above three questions:

  1. Requesting HTML from the CDN, the key issue is how to timing the Ajax request and make the connection replacement.

  2. PV statistics and article content insert cards, the key problem is how to get the timing of page switching.

We already know that the HTML document of the new page is obtained through ajax request when the page is switched, so we just need to intercept the timing of the Ajax request to do the above three things!

So how do you intercept Ajax requests? Through the analysis of the code of the website generated by Gitbook, it is found that it relies on jQuery. If All the Ajax requests of Gitbook are initiated through jQuery, then we only need to configure the hooks of jQuery Ajax. However, after the test, Only one of the requests for search_plus_index.json is made using jQuery. The other Ajax requests are made by Gitbook itself using XMLHttpRequest. So what do we do if we want to intercept Ajax requests without intruding into the Gitbook code? In fact, the answer is very simple, if you read my previous blog, you should know that I open source a library for blocking browser XMLHttpRequest ajax-hook, he is very clever, specific use can refer to github introduction, we can directly use here:

// Directly intercept the XMLHttpRequest object globally through the hook method provided by the Ajax-hook library
ah.hook({
    open: function (arg, xhr) {
        // If it is not local debugging, ajax traffic is directed to the CDN. Note that the CDN server has CORS cross-domain enabled
        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) {
        // Find that Gitbook sets some custom headers when making ajax requests. In order for CORS to work, remove these custom headers
        // Make the cross-domain request a simple request, avoiding precheck failures.
        if (arg[0]! = ='Accept') return true;
    },
    onload:function(){
        setTimeout(function(){
          // Add a physical book card
           if($("#book-search-results .ad").length === 0) {$(".ad").clone().show().prependTo("#book-search-results")}Json file search_plus_index. Json file is requested first when the website opens
           var extension=xhr.responseURL.split(".").pop()
           if(extension! = ='json'){
               _hmt.push(['_trackPageview', location.pathname]); }}); }})Copy the code

Note: CORS is enabled on our CDN, which allows the front end to cross domains. If you are not clear about CORS, please search by yourself.

Now, all our three problems have been solved. After acceleration, the peak time of website access can be close to second, PV can be collected accurately, and cards can be inserted normally. Readers can go to the website of Flutter to experience the effect.

Bring some goods

The acceleration and customization of Flutter is one of the best practices of the Open source library ajax-Hook. Ajax-hook was originally a piece of code written by me for research purposes. It was only 50 lines of code at that time and I thought it was very clever, so it was uploaded to Github. I did not expect that after Posting it on Github, it received a lot of star, not to mention that STAR can break thousands, and many first-line Internet companies are using it. Considering that the original code is only a core function of interception (although it has been modified later), the API is still relatively low-level, which is very inconvenient to use and prone to error. In order to solve this problem completely, the author reconstructed it again this weekend and released a new version 2.0. In version 2.0, Added more user-friendly, more powerful proxies (…) API, readers interested can go to github Ajax-hook homepage to understand, if you feel useful, also welcome star, tips.