瀏覽器異步加載和同源策略

靜態頁面

在瀏覽器腳本的概念沒有出現以前,全部的網頁都是靜態的。咱們知道瀏覽器的工做模式是:php

  1. 瀏覽器向網站服務器發起請求
  2. 網站接受瀏覽器的請求,返回一些字符串(好比一些組成頁面的 HTML 字符串)
  3. 瀏覽器接收到網站返回的用於組成頁面的字符串後,就能夠關閉鏈接了
  4. 瀏覽器將組成頁面的字符串渲染到屏幕上,使得用戶能夠看到一個可視化的結果

看起來就像下面這樣:jquery

 
                                       Client Request                              
              +-------------+                                     +--------+       
+------+      |  User Agent | +-------------------------------->  |        |       
| User +------>             |                                     | Server |       
+--^---+      |  (Browser)  | <--------------------------------+  |        |       
   |          +-------+-----+                                     +--------+       
   |                  |                Server Response                             
   |                  |                                                            
   |                  |                                                            
   |        +---------v--------+                                                   
   |        | Close Connection |                                                   
   |        +---------+--------+                                                   
   |                  |                                                            
   |                  |                                                            
   |         +--------v--------+                                                   
   ^---------+ Render response |                                                   
             +-----------------+                                                   
複製代碼

咱們看到,一旦用戶代理(瀏覽器)關閉了和服務器之間的連接以後,客戶端和服務器之間將不能繼續通訊。編程

動態頁面

爲了讓頁面能夠給用戶帶來更多的交互,瀏覽器開發廠商們製造出了名爲瀏覽器腳本的東西。好比你在瀏覽一個頁面的時候,你以爲頁面的字體過小了。在靜態頁面的時候,頁面製做者在右上角給你提供了名爲 「放大字體」 的按鈕,你點擊那個按鈕,而後開啓一輪新的請求,顯著的說就是說你感受到瀏覽器刷新了。這實際上是瀏覽器從新從服務器加載頁面的資源,只不過這一次的資源是用於顯示字體放大後的頁面。(儘管這個例子如今看來有些奇怪,由於若是僅僅是改變頁面字體大小的話,彷佛直接操做 element.style.fontSize 就能夠了,不過請求另一個包含更大字體的內聯 CSS 頁面在最終效果上也是說得通的,因此這裏還請多多包含了。)json

瀏覽器腳本就是一小段由瀏覽器執行的代碼,頁面製做者將這一小段代碼,和網頁面的內容(好比一篇優美的散文,和它右上角的 「放大字體」 按鈕)一塊兒返回給瀏覽器。瀏覽器接收到頁面資源後,首先就是先將散文和 「放大字體」 按鈕顯示出來。注意到返回的內容實際上還有一段由瀏覽器執行的代碼,頁面製做者在這段帶代碼中告訴瀏覽器:若是用戶點擊了 「放大字體」 按鈕,那麼你就將頁面的字體放大。因而,當你點擊 「放大字體」 按鈕以後,瀏覽器嚴格執行頁面製做者在腳本中撰寫的內容 - 將頁面的字體放大。瀏覽器

異步加載

注意在靜態頁面中瀏覽器和服務器之間的通訊過程。瀏覽器在向服務器發起了對頁面的請求以後,在服務器沒有將頁面的內容返回以前,頁面是沒法被顯示出來的,最顯著的特徵就是咱們在點擊了瀏覽器的 「刷新」 按鈕以後,頁面會 「白屏」 一小段時間。bash

起初瀏覽器腳本是沒有網絡通訊的功能的,只能作一些頁面的特效,好比「點擊按鈕放大了字體」。不過瀏覽器廠商發現,若是給腳本賦予網絡通訊的功能,將使得頁面製做者能夠給用戶提供更好的頁面交互體驗。因而在早期的 IE 瀏覽器中,首先賦予了瀏覽器腳本的通訊功能。服務器

瀏覽器腳本能夠和服務器進行網絡通訊以後,頁面製做者能夠作出具備更好體驗的頁面。好比你如今須要搜索商品,假設是要買一本編程的書,你在網頁的搜索框中輸入了 「編程的數」,很明顯是輸錯了,你將 「書」 錯輸成了 「數」。在你點擊了 「搜索」 按鈕以後,進太短暫的白屏以後,頁面中顯示了:網絡

找不到關於 「編程的數」 的產品,你是否是要找 「編程的書」app

很不錯,網站給了咱們一個提示,這樣咱們就能夠發現本身的輸入錯誤。不過這個體驗仍是有待提升的,由於每一次的搜索都會有一個短暫的 「白屏」,在白屏期間用戶只能等待。在瀏覽器腳本能夠通訊以後,搜索就能夠以一個異步的方式進行:異步

  1. 用戶在瀏覽器中輸入搜索頁面的地址 「search.shop.com
  2. 瀏覽器會向網站請求搜索頁面的內容,用於顯示這個頁面
  3. 網站在返回頁面的顯示內容的同時,包含了一小段腳本,腳本的內容是告訴瀏覽器 「用戶在點擊了搜索以後,你給用戶一個提示,讓用戶知道服務器正在緊張的搜索用戶所需的資源,而後你顯示了提示後,你再向服務器請求搜索的結果,當獲得搜索結果後,你再把搜索結果顯示給用戶」

這樣的話,用戶沒必要在搜索時面對頁面的刷新時的 「白屏」 了,有一個提示框告訴用戶稍等片刻。

同源策略

爲了定位網絡上的資源,咱們採用了統一資源定位符 URL,就像是一個門牌號同樣, URL 標識出資源在網絡上的位置。咱們瀏覽的網頁,其中的內容可能會來自不一樣的提供者,好比散文來自一位做家,而其中的配圖來自一位美術家。散文的 URL 是 http://writer.com/new-world,配圖的 URL 是 http://artist.com/new-world

咱們須要有一種方式將網絡上的資源(好比散文和圖畫)標識出來,區別它們是來自於不一樣的做者。若是咱們將顆粒度定位到每個獨立的資源,理論上是可行的,可是咱們知道做家不可能只有一篇散文,而美術家也不會只有一幅畫。因而咱們選擇了使用:通訊協議,完整的域名,以及端口號去描述一個源,只有三者都相同,才標識兩個資源是同源的。

下面的幾個資源是同源的:

http://example.com/ 
http://example.com:80/ 
http://example.com/path/file
複製代碼

下面的資源是不一樣源的:

http://example.com/ 
http://example.com:8080/ 
http://www.example.com/ 
https://example.com:80/ 
https://example.com/ 
http://example.org/ 
http://ietf.org/
複製代碼

如今知道了同源,那麼同源策略是什麼意思呢?同源策略就是,兩個不一樣源的資源相互是不能訪問對方的資源的。同源策略主要就是限制腳本的網絡訪問。

好比咱們打開了一個頁面 http://example.com,這個頁面有兩段腳本,一個段使用的內聯的方式稱爲 A,它主要就是在用戶點擊了按鈕以後顯示一段文字,告訴用戶點擊了按鈕;另外一段做爲外部資源進行加載稱爲 B,B 是 A 的基礎代碼,好比 B 是 jQuery,它被放在了 http://cdn.jquery.com 上。首先咱們知道,這兩段代碼若是按照同源的定義,確定是不一樣源的。也就是說咱們在 http://example.com 的頁面上是不能加載 http://cdn.jquery.com 上的資源的。

好像與現實狀況有點矛盾。之因此如今能夠,是由於瀏覽器爲了適應實際的生產狀況,放寬了對同源策略的檢查,由於咱們知道,不可能將全部的資源都放在同一臺機器上。那麼在頁面徹底加載好以後,頁面中的腳本(內聯的和外部引入)的都被瀏覽器概括到了和當前頁面相同的源,都屬於 http://example.com 了。這麼作的意思就是,腳本沒法訪問與之不一樣源的資源,也就是此時的腳本(內聯的和外部引入的)沒法訪問資源 https://example.com/user-info

繞過同源策略

有時好比上面的例子,咱們確實須要在腳本中加載和當前頁面不一樣源的資源,好比在 http://example.com 頁面中使用腳本加載 https://example.com/user-info 中的內容。那麼如何繞過瀏覽器的同源策略呢?

咱們知道直接在頁面中載入不一樣源的外部資源是能夠的,那麼咱們就能夠動態的載入一段外部的腳本。

首先,咱們的 http://example.com 中有這麼一段腳本:

(function () {
    window['showNickname'] = function (json) {
        alert(json['nickname']);
    };

    var userInfoServiceUrl = 'https://example.com/user-info';

    var doCrossSiteRequest = function (url, callback) {
        var script = document.createElement('script');
        script.src = url + '?callback=' + callback;
        var head = document.getElementsByTagName('head');
        if (head[0]) {
            head.append(script);
        }
    };

    document.querySelector('#btnShowNickName').addEventListener('click', function () {
        doCrossSiteRequest(userInfoServiceUrl, 'showNickname');
    });
})();
複製代碼

https://example.com/user-info 的服務端內容爲:

<?php

$callback = isset($_GET['callback']) ? $_GET['callback'] : null;
if ($callback === null) die('invalid request');

$userInfo = [
    'nickname' => 'net-user'
];
$json = json_encode($userInfo);

echo "{$callback}({$json});";
複製代碼

那麼在瀏覽器加載了 https://example.com/user-info 的腳本爲,獲得的是:

showNickname({"nickname":"net-user"});
複製代碼

這就和咱們最早在 http://example.com 留下的 window['showNickname'] 對接上了。

相關文章
相關標籤/搜索