前端性能優化方法總結

前端性能優化(一)


前端是龐大的,包括 HTML、 CSS、 Javascript、Image 、Flash等等各種各樣的資源。前端優化是複雜的,針對方方面面的資源都有不同的方式。那麼,前端優化的目的是什麼 ?
  1. 從用戶角度而言,優化能夠讓頁面加載得更快、對用戶的操作響應得更及時,能夠給用戶提供更爲友好的體驗。
  2. 從服務商角度而言,優化能夠減少頁面請求數、或者減小請求所佔帶寬,能夠節省可觀的資源。
  總之,恰當的優化不僅能夠改善站點的用戶體驗並且能夠節省相當的資源利用。
  前端優化的途徑有很多,按粒度大致可以分爲兩類,第一類是頁面級別的優化,例如 HTTP請求數、腳本的無阻塞加載、內聯腳本的位置優化等 ;第二類則是代碼級別的優化,例如 Javascript中的DOM 操作優化、CSS選擇符優化、圖片優化以及 HTML結構優化等等。另外,本着提高投入產出比的目的,後文提到的各種優化策略大致按照投入產出比從大到小的順序排列。
  一、頁面級優化
  1. 減少 HTTP請求數
  這條策略基本上所有前端人都知道,而且也是最重要最有效的。都說要減少 HTTP請求,那請求多了到底會怎麼樣呢 ?首先,每個請求都是有成本的,既包含時間成本也包含資源成本。一個完整的請求都需要經過 DNS尋址、與服務器建立連接、發送數據、等待服務器響應、接收數據這樣一個 “漫長” 而複雜的過程。時間成本就是用戶需要看到或者 “感受” 到這個資源是必須要等待這個過程結束的,資源上由於每個請求都需要攜帶數據,因此每個請求都需要佔用帶寬。另外,由於瀏覽器進行併發請求的請求數是有上限的 (具體參見此處 ),因此請求數多了以後,瀏覽器需要分批進行請求,因此會增加用戶的等待時間,會給用戶造成站點速度慢這樣一個印象,即使可能用戶能看到的第一屏的資源都已經請求完了,但是瀏覽器的進度條會一直存在。
  減少 HTTP請求數的主要途徑包括:
  (1). 從設計實現層面簡化頁面
  如果你的頁面像百度首頁一樣簡單,那麼接下來的規則基本上都用不着了。保持頁面簡潔、減少資源的使用時最直接的。如果不是這樣,你的頁面需要華麗的皮膚,則繼續閱讀下面的內容。
  (2). 合理設置 HTTP緩存
  緩存的力量是強大的,恰當的緩存設置可以大大的減少 HTTP請求。以有啊首頁爲例,當瀏覽器沒有緩存的時候訪問一共會發出 78個請求,共 600多 K數據 (如圖 1.1),而當第二次訪問即瀏覽器已緩存之後訪問則僅有 10個請求,共 20多 K數據 (如圖 1.2)。 (這裏需要說明的是,如果直接 F5刷新頁面的話效果是不一樣的,這種情況下請求數還是一樣,不過被緩存資源的請求服務器是 304響應,只有 Header沒有Body ,可以節省帶寬 )
  怎樣纔算合理設置 ?原則很簡單,能緩存越多越好,能緩存越久越好。例如,很少變化的圖片資源可以直接通過 HTTP Header中的Expires設置一個很長的過期頭 ;變化不頻繁而又可能會變的資源可以使用 Last-Modifed來做請求驗證。儘可能的讓資源能夠在緩存中待得更久。關於 HTTP緩存的具體設置和原理此處就不再詳述了,有興趣的可以參考下列文章:
HTTP1.1協議中關於緩存策略的描述
Fiddler HTTP Performance中關於緩存的介紹
  (3). 資源合併與壓縮
  如果可以的話,儘可能的將外部的腳本、樣式進行合併,多個合爲一個。另外, CSS、 Javascript、Image 都可以用相應的工具進行壓縮,壓縮後往往能省下不少空間。
  (4). CSS Sprites
  合併 CSS圖片,減少請求數的又一個好辦法。
  (5). Inline Images
  使用 data: URL scheme的方式將圖片嵌入到頁面或 CSS中,如果不考慮資源管理上的問題的話,不失爲一個好辦法。如果是嵌入頁面的話換來的是增大了頁面的體積,而且無法利用瀏覽器緩存。使用在 CSS中的圖片則更爲理想一些。
  (6). Lazy Load Images(自己對這一塊的內容還是不瞭解)
  這條策略實際上並不一定能減少 HTTP請求數,但是卻能在某些條件下或者頁面剛加載時減少 HTTP請求數。對於圖片而言,在頁面剛加載的時候可以只加載第一屏,當用戶繼續往後滾屏的時候才加載後續的圖片。這樣一來,假如用戶只對第一屏的內容感興趣時,那剩餘的圖片請求就都節省了。 有啊首頁 曾經的做法是在加載的時候把第一屏之後的圖片地址緩存在 Textarea標籤中,待用戶往下滾屏的時候才 “惰性” 加載。

  2. 將外部腳本置底(將腳本內容在頁面信息內容加載後再加載)
  前文有談到,瀏覽器是可以併發請求的,這一特點使得其能夠更快的加載資源,然而外鏈腳本在加載時卻會阻塞其他資源,例如在腳本加載完成之前,它後面的圖片、樣式以及其他腳本都處於阻塞狀態,直到腳本加載完成後纔會開始加載。如果將腳本放在比較靠前的位置,則會影響整個頁面的加載速度從而影響用戶體驗。解決這一問題的方法有很多,在 這裏有比較詳細的介紹 (這裏是譯文和 更詳細的例子 ),而最簡單可依賴的方法就是將腳本儘可能的往後挪,減少對併發下載的影響。
  3. 異步執行 inline腳本(其實原理和上面是一樣,保證腳本在頁面內容後面加載。)
  inline腳本對性能的影響與外部腳本相比,是有過之而無不及。首頁,與外部腳本一樣, inline腳本在執行的時候一樣會阻塞併發請求,除此之外,由於瀏覽器在頁面處理方面是單線程的,當 inline腳本在頁面渲染之前執行時,頁面的渲染工作則會被推遲。簡而言之, inline腳本在執行的時候,頁面處於空白狀態。鑑於以上兩點原因,建議將執行時間較長的 inline腳本異步執行,異步的方式有很多種,例如使用 script元素的defer 屬性(存在兼容性問題和其他一些問題,例如不能使用 document.write)、使用setTimeout ,此外,在HTML5中引入了 Web Workers的機制,恰恰可以解決此類問題。

  4. Lazy Load Javascript(只有在需要加載的時候加載,在一般情況下並不加載信息內容。)
  隨着 Javascript框架的流行,越來越多的站點也使用起了框架。不過,一個框架往往包括了很多的功能實現,這些功能並不是每一個頁面都需要的,如果下載了不需要的腳本則算得上是一種資源浪費 -既浪費了帶寬又浪費了執行花費的時間。目前的做法大概有兩種,一種是爲那些流量特別大的頁面專門定製一個專用的 mini版框架,另一種則是 Lazy Load。YUI 則使用了第二種方式,在 YUI的實現中,最初只加載核心模塊,其他模塊可以等到需要使用的時候才加載。

  5. 將 CSS放在 HEAD中
  如果將 CSS放在其他地方比如 BODY中,則瀏覽器有可能還未下載和解析到 CSS就已經開始渲染頁面了,這就導致頁面由無 CSS狀態跳轉到 CSS狀態,用戶體驗比較糟糕。除此之外,有些瀏覽器會在 CSS下載完成後纔開始渲染頁面,如果 CSS放在靠下的位置則會導致瀏覽器將渲染時間推遲。
  6. 異步請求 Callback(就是將一些行爲樣式提取出來,慢慢的加載信息的內容)
  在某些頁面中可能存在這樣一種需求,需要使用 script標籤來異步的請求數據。類似:
  Javascript:

function myCallback(info){ 
//do something here 

  HTML:

  cb返回的內容 :
myCallback('Hello world!');
像以上這種方式直接在頁面上寫 <script>對頁面的性能也是有影響的,即增加了頁面首次加載的負擔,推遲了 DOMLoaded和window.onload 事件的觸發時機。如果時效性允許的話,可以考慮在 DOMLoaded事件觸發的時候加載,或者使用 setTimeout方式來靈活的控制加載的時機。
  7. 減少不必要的 HTTP跳轉
  對於以目錄形式訪問的 HTTP鏈接,很多人都會忽略鏈接最後是否帶 ’/',假如你的服務器對此是區別對待的話,那麼你也需要注意,這其中很可能隱藏了 301跳轉,增加了多餘請求。具體參見下圖,其中第一個鏈接是以無 ’/'結尾的方式訪問的,於是服務器有了一次跳轉。
  8. 避免重複的資源請求
  這種情況主要是由於疏忽或頁面由多個模塊拼接而成,然後每個模塊中請求了同樣的資源時,會導致資源的重複請求

  二、代碼級優化
  1. Javascript
  (1). DOM
  DOM操作應該是腳本中最耗性能的一類操作,例如增加、修改、刪除 DOM元素或者對 DOM集合進行操作。如果腳本中包含了大量的 DOM操作則需要注意以下幾點:
  a. HTML Collection(HTML收集器,返回的是一個數組內容信息)
  在腳本中 document.images、document.forms 、getElementsByTagName()返回的都是 HTMLCollection類型的集合,在平時使用的時候大多將它作爲數組來使用,因爲它有 length屬性,也可以使用索引訪問每一個元素。不過在訪問性能上則比數組要差很多,原因是這個集合並不是一個靜態的結果,它表示的僅僅是一個特定的查詢,每次訪問該集合時都會重新執行這個查詢從而更新查詢結果。所謂的 “訪問集合” 包括讀取集合的 length屬性、訪問集合中的元素。
  因此,當你需要遍歷 HTML Collection的時候,儘量將它轉爲數組後再訪問,以提高性能。即使不轉換爲數組,也請儘可能少的訪問它,例如在遍歷的時候可以將 length屬性、成員保存到局部變量後再使用局部變量。
  b. Reflow & Repaint
  除了上面一點之外, DOM操作還需要考慮瀏覽器的 Reflow和Repaint ,因爲這些都是需要消耗資源的,具體的可以參加以下文章:
如何減少瀏覽器的repaint和reflow?
Understanding Internet Explorer Rendering Behaviour
Notes on HTML Reflow

  (2). 慎用 with
with(obj){ p = 1}; 代碼塊的行爲實際上是修改了代碼塊中的 執行環境 ,將obj放在了其作用域鏈的最前端,在 with代碼塊中訪問非局部變量是都是先從 obj上開始查找,如果沒有再依次按作用域鏈向上查找,因此使用 with相當於增加了作用域鏈長度。而每次查找作用域鏈都是要消耗時間的,過長的作用域鏈會導致查找性能下降。
  因此,除非你能肯定在 with代碼中只訪問 obj中的屬性,否則慎用 with,替代的可以使用局部變量緩存需要訪問的屬性。
  (3). 避免使用 eval和 Function
  每次 eval 或 Function 構造函數作用於字符串表示的源代碼時,腳本引擎都需要將源代碼轉換成可執行代碼。這是很消耗資源的操作 —— 通常比簡單的函數調用慢 100倍以上。
  eval 函數效率特別低,由於事先無法知曉傳給 eval 的字符串中的內容,eval在其上下文中解釋要處理的代碼,也就是說編譯器無法優化上下文,因此只能有瀏覽器在運行時解釋代碼。這對性能影響很大。
  Function 構造函數比 eval略好,因爲使用此代碼不會影響周圍代碼 ;但其速度仍很慢。
  此外,使用 eval和 Function也不利於Javascript 壓縮工具執行壓縮。
  (4). 減少作用域鏈查找(這方面設計到一些內容的相關問題)
  前文談到了作用域鏈查找問題,這一點在循環中是尤其需要注意的問題。如果在循環中需要訪問非本作用域下的變量時請在遍歷之前用局部變量緩存該變量,並在遍歷結束後再重寫那個變量,這一點對全局變量尤其重要,因爲全局變量處於作用域鏈的最頂端,訪問時的查找次數是最多的。
  低效率的寫法:
// 全局變量 
var globalVar = 1; 
function myCallback(info){ 
for( var i = 100000; i--;){ 
//每次訪問 globalVar 都需要查找到作用域鏈最頂端,本例中需要訪問 100000 次 
globalVar += i; 
}

  更高效的寫法:
// 全局變量 
var globalVar = 1; 
function myCallback(info){ 
//局部變量緩存全局變量 
var localVar = globalVar; 
for( var i = 100000; i--;){ 
//訪問局部變量是最快的 
localVar += i; 

//本例中只需要訪問 2次全局變量
在函數中只需要將 globalVar中內容的值賦給localVar 中區
globalVar = localVar; 
}
  此外,要減少作用域鏈查找還應該減少閉包的使用。
  (5). 數據訪問
  Javascript中的數據訪問包括直接量 (字符串、正則表達式 )、變量、對象屬性以及數組,其中對直接量和局部變量的訪問是最快的,對對象屬性以及數組的訪問需要更大的開銷。當出現以下情況時,建議將數據放入局部變量:
  a. 對任何對象屬性的訪問超過 1次
  b. 對任何數組成員的訪問次數超過 1次
  另外,還應當儘可能的減少對對象以及數組深度查找。
  (6). 字符串拼接
  在 Javascript中使用"+" 號來拼接字符串效率是比較低的,因爲每次運行都會開闢新的內存並生成新的字符串變量,然後將拼接結果賦值給新變量。與之相比更爲高效的做法是使用數組的 join方法,即將需要拼接的字符串放在數組中最後調用其 join方法得到結果。不過由於使用數組也有一定的開銷,因此當需要拼接的字符串較多的時候可以考慮用此方法。

  關於 Javascript優化的更詳細介紹請參考:
Write Efficient Javascript(PPT)
Efficient JavaScript
  2. CSS選擇符
  在大多數人的觀念中,都覺得瀏覽器對 CSS選擇符的解析式從左往右進行的,例如
#toc A { color: #444; }
  這樣一個選擇符,如果是從右往左解析則效率會很高,因爲第一個 ID選擇基本上就把查找的範圍限定了,但實際上瀏覽器對選擇符的解析是從右往左進行的。如上面的選擇符,瀏覽器必須遍歷查找每一個 A標籤的祖先節點,效率並不像之前想象的那樣高。根據瀏覽器的這一行爲特點,在寫選擇符的時候需要注意很多事項,有人已經一一列舉了, 詳情參考此處。

  3. HTML
  對 HTML本身的優化現如今也越來越多的受人關注了,詳情可以參見這篇 總結性文章 。

  4. Image壓縮
  圖片壓縮是個技術活,不過現如今這方面的工具也非常多,壓縮之後往往能帶來不錯的效果,具體的壓縮原理以及方法在《 Even Faster Web Sites》第10 章有很詳細的介紹,有興趣的可以去看看。
  總結
  本文從頁面級以及代碼級兩個粒度對前端優化的各種方式做了一個總結,這些方法基本上都是前端開發人員在開發的過程中可以借鑑和實踐的,除此之外,完整的前端優化還應該包括很多其他的途徑,例如 CDN、 Gzip、多域名、無 Cookie服務器等等

                                                                                                                       前端性能優化(二)

一、什麼是前端性能優化(what)?
     從用戶訪問資源到資源完整的展現在用戶面前的過程中,通過技術手段和優化策略,縮短每個步驟的處理時間從而提升整個資源的訪問和呈現速度。

二、爲什麼要做前端性能優化(why)?
在構建web站點的過程中,任何一個細節都有可能影響網站的訪問速度,如果不瞭解性能優化知識,很多不利網站訪問速度的因素會形成累加,從而嚴重影響網站的性能,導致網站訪問速度變慢,用戶體驗低下,最終導致用戶流失。
   

三、前端性能優化的原則(rule)
1、不要按照準則照本宣科的做,需要根據實際情況因地制宜;
2、不出bug!

四、從瀏覽器發起請求到頁面能正常瀏覽都有哪些階段(process)?

預處理——>DNS解析——>建立連接——>發起請求——>等待響應——>接受數據——>處理元素——>佈局渲染




五、性能優化的具體方法(way)
一)內容層面
1、DNS解析優化(DNS緩存、減少DNS查找、keep-alive、適當的主機域名)
  2、避免重定向(/還是需要的)
  3、切分到多個域名
  4、杜絕404

二)網絡傳輸階段
1、減少傳輸過程中實體的大小
    1)緩存
    2)cookie優化
    3)文件壓縮(Accept-Encoding:g-zip)

2、減少請求的次數
    1)文件適當的合併
    2)雪碧圖

3、異步加載(併發,requirejs)
4、預加載、延後加載、按需加載

三)渲染階段
1、js放底部,css放頂部
2、減少重繪和迴流
       3、合理使用Viewport 等meta頭部
       4、減少dom節點
      5、BigPipe

四)腳本執行階段
1、緩存節點,儘量減少節點的查找
2、減少節點的操作(innerHTML)
3、避免無謂的循環,break、continue、return的適當使用
4、事件委託



六、與性能優化相關的細節的探索
1、緩存
1)Expires  Cache-Control  Last-Modified  ETag  If-Modified-Since  If-None-Match 這些請求頭部在瀏覽器緩存中分別起什麼作用,如何起到緩存的作用?
1.當某一文件在瀏覽器中第一次被訪問的時候,這個時候瀏覽器是沒有緩存的,直接從服務器獲取文件,返回給客戶端,並且存入瀏覽器緩存;此時,返回狀態碼200,並且服務端可以設置響應頭部Expires或者Cache-Control,Last-Modified或者ETag。
2.如果設置了Expires或者Cache-Control,那麼在指定時間內再次請求該文件時,只要不強制刷新緩存(F5等),瀏覽器會直接讀取緩存而不再去請求服務器。
3.如果沒有設置Expires或者Cache-Control或者過期了,就需要再次請求服務器了,瀏覽器會發起條件驗證,發起請求時在請求頭加上If-Modified-Sinse或者If-None-Match,服務器端判斷最新的文件是否發生了更新,如果沒有,總則返回響應狀態碼304,並且不帶任何響應實體,也就是說,傳輸到客戶端的只有一些相應頭部,響應實體是空的,這樣就大大減少了傳輸的體積,瀏覽器接受到了304響應,就知道了要讀取瀏覽器緩存了。

2)按回車、瀏覽器刷新按鈕、F5、Ctr+F5的區別?
1.按回車,瀏覽器會判斷是否有緩存,並且根據Expires或者Cache-Control判斷緩存是否過期,如果沒有,就不會發起請求,直接使用緩存。否則就需要像服務器發起請求再驗證。
2.瀏覽器刷新按鈕和F5效果相同,不管是否有Expires或者Cache-Control,都會強制去請求服務器,進行再驗證,根據If-Modified-Sinse或者If-None-Match判斷是否要返回304,如果是,瀏覽器就會繼續使用緩存。
3.按Ctr+F5時,也是不管是否有Expires或者Cache-Control,都會強制去請求服務器,但是並不會進行再驗證,服務器會直接把最新的內容返回給瀏覽器,壓根就不考慮緩存的存在或者是否過期。


3)爲什麼用Last-Modified還不夠,要用ETag實體標籤驗證?
1.有些文檔會被週期性的重寫,但實際包含的數據是一樣的。(儘管內容沒有變化,最後修改日期卻會發生變化)
2.有些文檔可能被修改了,但是修改並不重要,沒必要更新緩存。
3.有些服務器無法準確判定頁面的最後修改日期。
4.文檔在毫秒級間隙發生變化(如實時監控),以秒爲顆粒度的Last-Modified就不夠用了。
                  4)post請求拉取大量數據的緩存策略?

                 

場景:

      post拉取一個超大的數據,比如通訊錄等。


       爲了避免每次都要請求都要拉取超大數據,我們可以在第一次請求後,把這份超大數據本地存儲起來,下一次時,如果判斷本地數據沒有失效,就直接使用本地數據,而不用服務端傳遞龐大數據了,這樣就在一定程度上縮短了http傳遞數據的時間了。這裏的要點就是判斷數據是否失效的機制。流程圖不太好畫,就用僞碼吧。


這裏使用了nodejs作爲中間過渡層,大概的流程如下:

A:客戶端設置headers["cache-flag"]=1給nodejs;

B:nodejs返回大數據Data給客戶端

C:nodejs返回heades["cache-md5"] ="xxxx"給客戶端

D:客戶端本地存儲大數據Data

E:客戶端本地存儲headers.cache-md5的值xxxx

F:客戶端設置headers["cache-flag"]=1,並且從本地存儲中拿到xxxx設置headers["cache-md5"]="xxxx"給nodejs

G:nodejs返回headers["cache-target"]=1給客戶端

H:nodejs返回headers["cache-md5"]="newxxxx"給客戶端

I:客戶端使用本地存儲的Data



具體的解釋:

1、客戶端:如果要使用緩存機制,在發請求的時候,設置一個請求頭headers["cahce-flag"]=1;

2、nodeJs:(每次還是會請求底層服務端拿到數據),判斷請求頭有沒有cache-flag,如果沒有直接把從底層服務端拿到的數據返回給客戶端;如果有cache-flag標誌,再判斷有沒有headers["cache-md5"],如果沒有,統一直接返回數據,如果有,則把nodejs拿到的數據打一個md5值newxxx",並且和請求的headers.cache-md5的值xxx相比較,如不相等,則說明過期,直接返回大數據,同時設置響應頭,headers["cache-md5"]="newxxx";如果相等,說明沒有過期,則把content-length設置爲0,responseText設置爲空,同時返回兩個頭部給客戶端,headers["cache-target"] =1;headers["cache-md5"]="xxx"

3、客戶端判斷每次都會存儲大數據data(如果有的話),也會每次存儲nodejs返回的cache-md5,並且每次還會把這個cache-md5傳遞給nodejs,當客戶端判斷有header["cahce-target"],如果有,則說明緩存沒有失效,則使用本地緩存。

2、DNS解析過程

先說一下DNS的幾個基本概念:


一. 根域


就是所謂的“.”,其實我們的網址www.baidu.com在配置當中應該是www.baidu.com.(最後有一點),一般我們在瀏覽器裏輸入時會省略後面的點,而這也已經成爲了習慣。

根域服務器我們知道有13臺,但是這是錯誤的觀點。

根域服務器只是具有13個IP地址,但機器數量卻不是13臺,因爲這些IP地址藉助了任播的技術,所以我們可以在全球設立這些IP的鏡像站點,你訪問到的這個IP並不是唯一的那臺主機。

具體的鏡像分佈可以參考維基百科。這些主機的內容都是一樣的

二. 域的劃分

根域下來就是頂級域或者叫一級域,

有兩種劃分方式,一種互聯網剛興起時的按照行業性質劃分的com.,net.等,一種是按國家劃分的如cn.,jp.,等。

具體多少你可以自己去查,我們這裏不關心。

每個域都會有域名服務器,也叫權威域名服務器。

Baidu.com就是一個頂級域名,而www.baidu.com卻不是頂級域名,他是在baidu.com 這個域裏的一叫做www的主機。

一級域之後還有二級域,三級域,只要我買了一個頂級域,並且我搭建了自己BIND服務器(或者其他軟件搭建的)註冊到互聯網中,那麼我就可以隨意在前面多加幾個域了(當然長度是有限制的)。

比如a.www.baidu.com,在這個網址中,www.baidu.com變成了一個二級域而不是一臺主機,主機名是a。

三. 域名服務器

能提供域名解析的服務器,上面的記錄類型可以是A(address)記錄,NS記錄(name server),MX(mail),CNAME等。

(詳解參見博客:域名解析中A記錄、CNAME、MX記錄、NS記錄的區別和聯繫

A記錄是什麼意思呢,就是記錄一個IP地址和一個主機名字,比如我這個域名服務器所在的域test.baidu.com,我們知道這是一個二級的域名,然後我在裏面有一條A記錄,記錄了主機爲a的IP,查到了就返回給你了。

如果我現在要想baidu.com這個域名服務器查詢a.test.baidu.com,那麼這個頂級域名服務器就會發現你請求的這個網址在test.baidu.com這個域中,我這裏記錄了這個二級域的域名服務器test.baidu.com的NS的IP。我返回給你這個地址你再去查主機爲a的主機把。

這些域內的域名服務器都稱爲權威服務器,直接提供DNS查詢服務。(這些服務器可不會做遞歸哦)

四.解析過程

那麼我們的DNS是怎麼解析一個域名的呢?


1.現在我有一臺計算機,通過ISP接入了互聯網,那麼ISP就會給我分配一個DNS服務器,這個DNS服務器不是權威服務器,而是相當於一個代理的dns解析服務器,他會幫你迭代權威服務器返回的應答,然後把最終查到IP返回給你。

2.現在的我計算機要向這臺ISPDNS發起請求查詢www.baidu.com這個域名了,(經網友提醒:這裏其實準確來說不是ISPDNS,而應該是用戶自己電腦網絡設置裏的DNS,並不一定是ISPDNS。比如也有可能你手工設置了8.8.8.8)

3.ISPDNS拿到請求後,先檢查一下自己的緩存中有沒有這個地址,有的話就直接返回。這個時候拿到的ip地址,會被標記爲非權威服務器的應答

4.如果緩存中沒有的話,ISPDNS會從配置文件裏面讀取13個根域名服務器的地址(這些地址是不變的,直接在BIND的配置文件中),

5.然後像其中一臺發起請求。

6.根服務器拿到這個請求後,知道他是com.這個頂級域名下的,所以就會返回com域中的NS記錄,一般來說是13臺主機名和IP。

7.然後ISPDNS向其中一臺再次發起請求,com域的服務器發現你這請求是baidu.com這個域的,我一查發現了這個域的NS,那我就返回給你,你再去查。

(目前百度有4臺baidu.com的頂級域名服務器)。

8.ISPDNS不厭其煩的再次向baidu.com這個域的權威服務器發起請求,baidu.com收到之後,查了下有www的這臺主機,就把這個IP返回給你了,

9.然後ISPDNS拿到了之後,將其返回給了客戶端,並且把這個保存在高速緩存中。



下面我們來用 nslookup 這個工具詳細來說一下解析步驟:


從上圖我們可以看到:

          第一行Server是:DNS服務器的主機名--210.32.32.1

          第二行Address是: 它的IP地址--210.32.32.1#53

          下面的Name是:解析的URL--    www.jsjzx.com

          Address是:解析出來的IP--112.121.162.168


但是也有像百度這樣的DNS比較複雜的解析:

你會發現百度有一個cname = www.a.shifen.com  的別名。

這是怎麼一個過程呢?

我們用dig工具來跟蹤一下把(linux系統自帶有)

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Dig工具會在本地計算機做迭代,然後記錄查詢的過程。


第一步是向我這臺機器的ISPDNS獲取到根域服務區的13個IP和主機名[b-j].root-servers.net.。

第二步是向其中的一臺根域服務器(Servername就是末行小括號裏面的)發送www.baidu.com的查詢請求,他返回了com.頂級域的服務器IP(未顯示)和名稱,

第三步,便向com.域的一臺服務器192.33.4.12請求,www.baidu.com,他返回了baidu.com域的服務器IP(未顯示)和名稱,百度有四臺頂級域的服務器

     【此處可以用dig @192.33.4.12 www.baidu.com查看返回的百度頂級域名服務器IP地址】。


第四步呢,向百度的頂級域服務器(202.108.22.220)請求www.baidu.com,他發現這個www有個別名,而不是一臺主機,別名是www.a.shifen.com。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

按照一般的邏輯,當dns請求到別名的時候,查詢會終止,而是重新發起查詢別名的請求,所以此處應該返回的是www.a.shifen.com而已。

但是爲什麼返回a.shifen.com的這個域的NS呢?

我們可以嘗試下面的這個命令:dig +trace  shifen.com 看看有什麼結果。。。。。。。。


你會發現第三步時shifen.com這個頂級域的域名服務器和baidu.com這個域的域名服務器是同一臺主機(即:dns.baidu.com)!


當我拿到www.baidu.com的別名www.a.shifen.com的時候,我本來需要重新到com域查找shifen.com域的NS,但是因爲這兩個域在同一臺NS上,所以直接向本機發起了,

shifen.com域發現請求的www.a.shifen.com是屬於a.shifen.com這個域的,

於是就把a.shifen.com的這個NS和IP返回,讓我到a.shifen.com這個域的域名服務器上查詢www.a.shifen.com。

於是我便從ns X .a.shifen.com中一臺拿到了一條A記錄,最終的最終也便是www.baidu.com的IP地址了.【此處也可以用dig +trace www.a.shifen.com】跟蹤一下

用一個圖來說明一下(圖中第三步的全世界只有13臺是錯誤的)


以下內容爲在虛擬機中搭建local dns服務器得到的實驗數據,糾正上述結論

在上面的分析中,我們用dig工具進行了追蹤,但是dig沒有繼續追蹤當我們從baidu.com拿到cname和ns2.a.shifen.com的IP之後的事情。

我們就所以然的下結論認爲local dns會向ns2.a.shifen.com請求www.a.shifenc.om。

其實這個想法是錯誤,在自己的本地搭建一個local dns,抓取整個解析過程中是所有包,看看就明白拉。

實際的結果是雖然dns.baidu.com返回了a.shifen.com域的服務器地址和IP,

但是local dns並不是直接向上述返回的IP請求www.a.shifen.com,而是再一次去請求com域,得到shifen.com域的服務器(也就是baidu.com的那四臺),

然後又請求www.a.shifen.com,返回a.shifen.com的域的服務器,最後纔是去請求www.a.shifen.com,

雖然上面已經返回了IP,但是實驗的結果就是再走一遍shifen.com域的查詢。


上圖就是localdns在解析www.baidu.com的抓包全過程。藍色那條就是在收到cname和響應的a.shifen.com的域名服務器IP地址之後,繼續向com域請求shifen.com。

這個圖充分說明了返回cname的同時也返回了ns2.a.shifen.com的IP。

因此總結一下便是

         ①本機向local dns請求www.baidu.com

         ②local dns向根域請求www.baidu.com,根域返回com.域的服務器IP

         ③向com.域請求www.baidu.com,com.域返回baidu.com域的服務器IP

         ④向baidu.com請求www.baidu.com,返回cname www.a.shifen.com和a.shifen.com域的服務器IP

         ⑤向root域請求www.a.shifen.com

         ⑥向com.域請求www.a.shife.com

         ⑦向shifen.com請求

         ⑧向a.shifen.com域請求

         ⑨拿到www.a.shifen.com的IP

         ⑩localdns返回本機www.baidu.com cname www.a.shifen.com 以及 www.a.shifen.com的IP

3、HTTP
1)所有常用狀態碼的含義?

1xx消息

這一類型的狀態碼,代表請求已被接受,需要繼續處理。這類響應是臨時響應,只包含狀態行和某些可選的響應頭信息,並以空行結束。由於HTTP/1.0協議中沒有定義任何1xx狀態碼,所以除非在某些試驗條件下,服務器禁止向此類客戶端發送1xx響應。 這些狀態碼代表的響應都是信息性的,標示客戶應該採取的其他行動。

100 Continue

客戶端應當繼續發送請求。這個臨時響應是用來通知客戶端它的部分請求已經被服務器接收,且仍未被拒絕。客戶端應當繼續發送請求的剩餘部分,或者如果請求已經完成,忽略這個響應。服務器必須在請求完成後向客戶端發送一個最終響應。

101 Switching Protocols

服務器已經理解了客戶端的請求,並將通過Upgrade消息頭通知客戶端採用不同的協議來完成這個請求。在發送完這個響應最後的空行後,服務器將會切換到在Upgrade消息頭中定義的那些協議。 
只有在切換新的協議更有好處的時候才應該採取類似措施。例如,切換到新的HTTP版本比舊版本更有優勢,或者切換到一個實時且同步的協議以傳送利用此類特性的資源。

102 Processing

由WebDAV(RFC 2518)擴展的狀態碼,代表處理將被繼續執行。

2xx成功

這一類型的狀態碼,代表請求已成功被服務器接收、理解、並接受。

200 OK

請求已成功,請求所希望的響應頭或數據體將隨此響應返回。

201 Created

請求已經被實現,而且有一個新的資源已經依據請求的需要而創建,且其URI已經隨Location頭信息返回。假如需要的資源無法及時創建的話,應當返回’202 Accepted’。

202 Accepted

服務器已接受請求,但尚未處理。正如它可能被拒絕一樣,最終該請求可能會也可能不會被執行。在異步操作的場合下,沒有比發送這個狀態碼更方便的做法了。 
返回202狀態碼的響應的目的是允許服務器接受其他過程的請求(例如某個每天只執行一次的基於批處理的操作),而不必讓客戶端一直保持與服務器的連接直到批處理操作全部完成。在接受請求處理並返回202狀態碼的響應應當在返回的實體中包含一些指示處理當前狀態的信息,以及指向處理狀態監視器或狀態預測的指針,以便用戶能夠估計操作是否已經完成。

203 Non-Authoritative Information

服務器已成功處理了請求,但返回的實體頭部元信息不是在原始服務器上有效的確定集合,而是來自本地或者第三方的拷貝。當前的信息可能是原始版本的子集或者超集。例如,包含資源的元數據可能導致原始服務器知道元信息的超集。使用此狀態碼不是必須的,而且只有在響應不使用此狀態碼便會返回200 OK的情況下才是合適的。

204 No Content

服務器成功處理了請求,但不需要返回任何實體內容,並且希望返回更新了的元信息。響應可能通過實體頭部的形式,返回新的或更新後的元信息。如果存在這些頭部信息,則應當與所請求的變量相呼應。 
如果客戶端是瀏覽器的話,那麼用戶瀏覽器應保留髮送了該請求的頁面,而不產生任何文檔視圖上的變化,即使按照規範新的或更新後的元信息應當被應用到用戶瀏覽器活動視圖中的文檔。 
由於204響應被禁止包含任何消息體,因此它始終以消息頭後的第一個空行結尾。

205 Reset Content

服務器成功處理了請求,且沒有返回任何內容。但是與204響應不同,返回此狀態碼的響應要求請求者重置文檔視圖。該響應主要是被用於接受用戶輸入後,立即重置表單,以便用戶能夠輕鬆地開始另一次輸入。 
與204響應一樣,該響應也被禁止包含任何消息體,且以消息頭後的第一個空行結束。

206 Partial Content

服務器已經成功處理了部分GET請求。類似於FlashGet或者迅雷這類的HTTP 下載工具都是使用此類響應實現斷點續傳或者將一個大文檔分解爲多個下載段同時下載。 
該請求必須包含Range頭信息來指示客戶端希望得到的內容範圍,並且可能包含If-Range來作爲請求條件。 
響應必須包含如下的頭部域: 
Content-Range用以指示本次響應中返回的內容的範圍;如果是Content-Type爲multipart/byteranges的多段下載,則每一multipart段中都應包含Content-Range域用以指示本段的內容範圍。假如響應中包含Content-Length,那麼它的數值必須匹配它返回的內容範圍的真實字節數。 
Date 
ETag和/或Content-Location,假如同樣的請求本應該返回200響應。 
Expires, Cache-Control,和/或Vary,假如其值可能與之前相同變量的其他響應對應的值不同的話。 
假如本響應請求使用了If-Range強緩存驗證,那麼本次響應不應該包含其他實體頭;假如本響應的請求使用了If-Range弱緩存驗證,那麼本次響應禁止包含其他實體頭;這避免了緩存的實體內容和更新了的實體頭信息之間的不一致。否則,本響應就應當包含所有本應該返回200響應中應當返回的所有實體頭部域。 
假如ETag或Last-Modified頭部不能精確匹配的話,則客戶端緩存應禁止將206響應返回的內容與之前任何緩存過的內容組合在一起。 
任何不支持Range以及Content-Range頭的緩存都禁止緩存206響應返回的內容。

207 Multi-Status

由WebDAV(RFC 2518)擴展的狀態碼,代表之後的消息體將是一個XML消息,並且可能依照之前子請求數量的不同,包含一系列獨立的響應代碼。

3xx重定向

這類狀態碼代表需要客戶端採取進一步的操作才能完成請求。通常,這些狀態碼用來重定向,後續的請求地址(重定向目標)在本次響應的Location域中指明。 
當且僅當後續的請求所使用的方法是GET或者HEAD時,用戶瀏覽器纔可以在沒有用戶介入的情況下自動提交所需要的後續請求。客戶端應當自動監測無限循環重定向(例如:A→B→C→……→A或A→A),因爲這會導致服務器和客戶端大量不必要的資源消耗。按照HTTP/1.0版規範的建議,瀏覽器不應自動訪問超過5次的重定向。

300 Multiple Choices

被請求的資源有一系列可供選擇的回饋信息,每個都有自己特定的地址和瀏覽器驅動的商議信息。用戶或瀏覽器能夠自行選擇一個首選的地址進行重定向。 
除非這是一個HEAD請求,否則該響應應當包括一個資源特性及地址的列表的實體,以便用戶或瀏覽器從中選擇最合適的重定向地址。這個實體的格式由Content-Type定義的格式所決定。瀏覽器可能根據響應的格式以及瀏覽器自身能力,自動作出最合適的選擇。當然,RFC 2616規範並沒有規定這樣的自動選擇該如何進行。 
如果服務器本身已經有了首選的回饋選擇,那麼在Location中應當指明這個回饋的URI;瀏覽器可能會將這個Location值作爲自動重定向的地址。此外,除非額外指定,否則這個響應也是可緩存的。

301 Moved Permanently

被請求的資源已永久移動到新位置,並且將來任何對此資源的引用都應該使用本響應返回的若干個URI之一。如果可能,擁有鏈接編輯功能的客戶端應當自動把請求的地址修改爲從服務器反饋回來的地址。除非額外指定,否則這個響應也是可緩存的。 
新的永久性的URI應當在響應的Location域中返回。除非這是一個HEAD請求,否則響應的實體中應當包含指向新的URI的超鏈接及簡短說明。 
如果這不是一個GET或者HEAD請求,因此瀏覽器禁止自動進行重定向,除非得到用戶的確認,因爲請求的條件可能因此發生變化。 
注意:對於某些使用HTTP/1.0協議的瀏覽器,當它們發送的POST請求得到了一個301響應的話,接下來的重定向請求將會變成GET方式。

302 Found

請求的資源現在臨時從不同的URI響應請求。由於這樣的重定向是臨時的,客戶端應當繼續向原有地址發送以後的請求。只有在Cache-Control或Expires中進行了指定的情況下,這個響應纔是可緩存的。 
新的臨時性的URI應當在響應的Location域中返回。除非這是一個HEAD請求,否則響應的實體中應當包含指向新的URI的超鏈接及簡短說明。 
如果這不是一個GET或者HEAD請求,那麼瀏覽器禁止自動進行重定向,除非得到用戶的確認,因爲請求的條件可能因此發生變化。 
注意:雖然RFC 1945和RFC 2068規範不允許客戶端在重定向時改變請求的方法,但是很多現存的瀏覽器將302響應視作爲303響應,並且使用GET方式訪問在Location中規定的URI,而無視原先請求的方法。狀態碼303和307被添加了進來,用以明確服務器期待客戶端進行何種反應。

303 See Other

對應當前請求的響應可以在另一個URI上被找到,而且客戶端應當採用GET的方式訪問那個資源。這個方法的存在主要是爲了允許由腳本激活的POST請求輸出重定向到一個新的資源。這個新的URI不是原始資源的替代引用。同時,303響應禁止被緩存。當然,第二個請求(重定向)可能被緩存。 
新的URI應當在響應的Location域中返回。除非這是一個HEAD請求,否則響應的實體中應當包含指向新的URI的超鏈接及簡短說明。 
注意:許多HTTP/1.1版以前的瀏覽器不能正確理解303狀態。如果需要考慮與這些瀏覽器之間的互動,302狀態碼應該可以勝任,因爲大多數的瀏覽器處理302響應時的方式恰恰就是上述規範要求客戶端處理303響應時應當做的。

304 Not Modified

如果客戶端發送了一個帶條件的GET請求且該請求已被允許,而文檔的內容(自上次訪問以來或者根據請求的條件)並沒有改變,則服務器應當返回這個狀態碼。304響應禁止包含消息體,因此始終以消息頭後的第一個空行結尾。 
該響應必須包含以下的頭信息: 
Date,除非這個服務器沒有時鐘。假如沒有時鐘的服務器也遵守這些規則,那麼代理服務器以及客戶端可以自行將Date字段添加到接收到的響應頭中去(正如RFC 2068中規定的一樣),緩存機制將會正常工作。 
ETag和/或Content-Location,假如同樣的請求本應返回200響應。 
Expires, Cache-Control,和/或Vary,假如其值可能與之前相同變量的其他響應對應的值不同的話。 
假如本響應請求使用了強緩存驗證,那麼本次響應不應該包含其他實體頭;否則(例如,某個帶條件的GET請求使用了弱緩存驗證),本次響應禁止包含其他實體頭;這避免了緩存了的實體內容和更新了的實體頭信息之間的不一致。 
假如某個304響應指明瞭當前某個實體沒有緩存,那麼緩存系統必須忽視這個響應,並且重複發送不包含限制條件的請求。 
假如接收到一個要求更新某個緩存條目的304響應,那麼緩存系統必須更新整個條目以反映所有在響應中被更新的字段的值。

305 Use Proxy

被請求的資源必須通過指定的代理才能被訪問。Location域中將給出指定的代理所在的URI信息,接收者需要重複發送一個單獨的請求,通過這個代理才能訪問相應資源。只有原始服務器才能創建305響應。 
注意:RFC 2068中沒有明確305響應是爲了重定向一個單獨的請求,而且只能被原始服務器創建。忽視這些限制可能導致嚴重的安全後果。

306 Switch Proxy

在最新版的規範中,306狀態碼已經不再被使用。

307 Temporary Redirect

請求的資源現在臨時從不同的URI響應請求。由於這樣的重定向是臨時的,客戶端應當繼續向原有地址發送以後的請求。只有在Cache-Control或Expires中進行了指定的情況下,這個響應纔是可緩存的。 
新的臨時性的URI應當在響應的Location域中返回。除非這是一個HEAD請求,否則響應的實體中應當包含指向新的URI的超鏈接及簡短說明。因爲部分瀏覽器不能識別307響應,因此需要添加上述必要信息以便用戶能夠理解並向新的URI發出訪問請求。 
如果這不是一個GET或者HEAD請求,那麼瀏覽器禁止自動進行重定向,除非得到用戶的確認,因爲請求的條件可能因此發生變化。

4xx客戶端錯誤

這類的狀態碼代表了客戶端看起來可能發生了錯誤,妨礙了服務器的處理。除非響應的是一個HEAD請求,否則服務器就應該返回一個解釋當前錯誤狀況的實體,以及這是臨時的還是永久性的狀況。這些狀態碼適用於任何請求方法。瀏覽器應當向用戶顯示任何包含在此類錯誤響應中的實體內容。 
如果錯誤發生時客戶端正在傳送數據,那麼使用TCP的服務器實現應當仔細確保在關閉客戶端與服務器之間的連接之前,客戶端已經收到了包含錯誤信息的數據包。如果客戶端在收到錯誤信息後繼續向服務器發送數據,服務器的TCP棧將向客戶端發送一個重置數據包,以清除該客戶端所有還未識別的輸入緩衝,以免這些數據被服務器上的應用程序讀取並干擾後者。

400 Bad Request

由於包含語法錯誤,當前請求無法被服務器理解。除非進行修改,否則客戶端不應該重複提交這個請求。

401 Unauthorized

當前請求需要用戶驗證。該響應必須包含一個適用於被請求資源的WWW-Authenticate信息頭用以詢問用戶信息。客戶端可以重複提交一個包含恰當的Authorization頭信息的請求。如果當前請求已經包含了Authorization證書,那麼401響應代表着服務器驗證已經拒絕了那些證書。如果401響應包含了與前一個響應相同的身份驗證詢問,且瀏覽器已經至少嘗試了一次驗證,那麼瀏覽器應當向用戶展示響應中包含的實體信息,因爲這個實體信息中可能包含了相關診斷信息。參見RFC 2617。

402 Payment Required

該狀態碼是爲了將來可能的需求而預留的。

403 Forbidden

服務器已經理解請求,但是拒絕執行它。與401響應不同的是,身份驗證並不能提供任何幫助,而且這個請求也不應該被重複提交。如果這不是一個HEAD請求,而且服務器希望能夠講清楚爲何請求不能被執行,那麼就應該在實體內描述拒絕的原因。當然服務器也可以返回一個404響應,假如它不希望讓客戶端獲得任何信息。

404 Not Found

請求失敗,請求所希望得到的資源未被在服務器上發現。沒有信息能夠告訴用戶這個狀況到底是暫時的還是永久的。假如服務器知道情況的話,應當使用410狀態碼來告知舊資源因爲某些內部的配置機制問題,已經永久的不可用,而且沒有任何可以跳轉的地址。404這個狀態碼被廣泛應用於當服務器不想揭示到底爲何請求被拒絕或者沒有其他適合的響應可用的情況下。

405 Method Not Allowed

請求行中指定的請求方法不能被用於請求相應的資源。該響應必須返回一個Allow頭信息用以表示出當前資源能夠接受的請求方法的列表。 
鑑於PUT,DELETE方法會對服務器上的資源進行寫操作,因而絕大部分的網頁服務器都不支持或者在默認配置下不允許上述請求方法,對於此類請求均會返回405錯誤。

406 Not Acceptable

請求的資源的內容特性無法滿足請求頭中的條件,因而無法生成響應實體。 
除非這是一個HEAD請求,否則該響應就應當返回一個包含可以讓用戶或者瀏覽器從中選擇最合適的實體特性以及地址列表的實體。實體的格式由Content-Type頭中定義的媒體類型決定。瀏覽器可以根據格式及自身能力自行作出最佳選擇。但是,規範中並沒有定義任何作出此類自動選擇的標準。

407 Proxy Authentication Required

與401響應類似,只不過客戶端必須在代理服務器上進行身份驗證。代理服務器必須返回一個Proxy-Authenticate用以進行身份詢問。客戶端可以返回一個Proxy-Authorization信息頭用以驗證。參見RFC 2617。

408 Request Timeout

請求超時。客戶端沒有在服務器預備等待的時間內完成一個請求的發送。客戶端可以隨時再次提交這一請求而無需進行任何更改。

409 Conflict

由於和被請求的資源的當前狀態之間存在衝突,請求無法完成。這個代碼只允許用在這樣的情況下才能被使用:用戶被認爲能夠解決衝突,並且會重新提交新的請求。該響應應當包含足夠的信息以便用戶發現衝突的源頭。 
衝突通常發生於對PUT請求的處理中。例如,在採用版本檢查的環境下,某次PUT提交的對特定資源的修改請求所附帶的版本信息與之前的某個(第三方)請求向衝突,那麼此時服務器就應該返回一個409錯誤,告知用戶請求無法完成。此時,響應實體中很可能會包含兩個衝突版本之間的差異比較,以便用戶重新提交歸併以後的新版本。

410 Gone

被請求的資源在服務器上已經不再可用,而且沒有任何已知的轉發地址。這樣的狀況應當被認爲是永久性的。如果可能,擁有鏈接編輯功能的客戶端應當在獲得用戶許可後刪除所有指向這個地址的引用。如果服務器不知道或者無法確定這個狀況是否是永久的,那麼就應該使用404狀態碼。除非額外說明,否則這個響應是可緩存的。 
410響應的目的主要是幫助網站管理員維護網站,通知用戶該資源已經不再可用,並且服務器擁有者希望所有指向這個資源的遠端連接也被刪除。這類事件在限時、增值服務中很普遍。同樣,410響應也被用於通知客戶端在當前服務器站點上,原本屬於某個個人的資源已經不再可用。當然,是否需要把所有永久不可用的資源標記爲’410 Gone’,以及是否需要保持此標記多長時間,完全取決於服務器擁有者。

411 Length Required

服務器拒絕在沒有定義Content-Length頭的情況下接受請求。在添加了表明請求消息體長度的有效Content-Length頭之後,客戶端可以再次提交該請求。

412 Precondition Failed

服務器在驗證在請求的頭字段中給出先決條件時,沒能滿足其中的一個或多個。這個狀態碼允許客戶端在獲取資源時在請求的元信息(請求頭字段數據)中設置先決條件,以此避免該請求方法被應用到其希望的內容以外的資源上。

413 Request Entity Too Large

服務器拒絕處理當前請求,因爲該請求提交的實體數據大小超過了服務器願意或者能夠處理的範圍。此種情況下,服務器可以關閉連接以免客戶端繼續發送此請求。 
如果這個狀況是臨時的,服務器應當返回一個Retry-After的響應頭,以告知客戶端可以在多少時間以後重新嘗試。

414 Request-URI Too Long

請求的URI長度超過了服務器能夠解釋的長度,因此服務器拒絕對該請求提供服務。這比較少見,通常的情況包括: 
本應使用POST方法的表單提交變成了GET方法,導致查詢字符串(Query String)過長。 
重定向URI“黑洞”,例如每次重定向把舊的URI作爲新的URI的一部分,導致在若干次重定向後URI超長。 
客戶端正在嘗試利用某些服務器中存在的安全漏洞攻擊服務器。這類服務器使用固定長度的緩衝讀取或操作請求的URI,當GET後的參數超過某個數值後,可能會產生緩衝區溢出,導致任意代碼被執行[1]。沒有此類漏洞的服務器,應當返回414狀態碼。

415 Unsupported Media Type

對於當前請求的方法和所請求的資源,請求中提交的實體並不是服務器中所支持的格式,因此請求被拒絕。

416 Requested Range Not Satisfiable

如果請求中包含了Range請求頭,並且Range中指定的任何數據範圍都與當前資源的可用範圍不重合,同時請求中又沒有定義If-Range請求頭,那麼服務器就應當返回416狀態碼。 
假如Range使用的是字節範圍,那麼這種情況就是指請求指定的所有數據範圍的首字節位置都超過了當前資源的長度。服務器也應當在返回416狀態碼的同時,包含一個Content-Range實體頭,用以指明當前資源的長度。這個響應也被禁止使用multipart/byteranges作爲其Content-Type。

417 Expectation Failed

在請求頭Expect中指定的預期內容無法被服務器滿足,或者這個服務器是一個代理服務器,它有明顯的證據證明在當前路由的下一個節點上,Expect的內容無法被滿足。

418 I’m a teapot

本操作碼是在1998年作爲IETF的傳統愚人節笑話, 在RFC 2324 超文本咖啡壺控制協議中定義的,並不需要在真實的HTTP服務器中定義。

421 There are too many connections from your internet address

從當前客戶端所在的IP地址到服務器的連接數超過了服務器許可的最大範圍。通常,這裏的IP地址指的是從服務器上看到的客戶端地址(比如用戶的網關或者代理服務器地址)。在這種情況下,連接數的計算可能涉及到不止一個終端用戶。

422 Unprocessable Entity

請求格式正確,但是由於含有語義錯誤,無法響應。(RFC 4918 WebDAV)

423 Locked

當前資源被鎖定。(RFC 4918 WebDAV)

424 Failed Dependency

由於之前的某個請求發生的錯誤,導致當前請求失敗,例如PROPPATCH。(RFC 4918 WebDAV)

425 Unordered Collection

在WebDav Advanced Collections草案中定義,但是未出現在《WebDAV順序集協議》(RFC 3658)中。

426 Upgrade Required

客戶端應當切換到TLS/1.0。(RFC 2817)

449 Retry With

由微軟擴展,代表請求應當在執行完適當的操作後進行重試。

5xx服務器錯誤

這類狀態碼代表了服務器在處理請求的過程中有錯誤或者異常狀態發生,也有可能是服務器意識到以當前的軟硬件資源無法完成對請求的處理。除非這是一個HEAD請求,否則服務器應當包含一個解釋當前錯誤狀態以及這個狀況是臨時的還是永久的解釋信息實體。瀏覽器應當向用戶展示任何在當前響應中被包含的實體。 
這些狀態碼適用於任何響應方法。

500 Internal Server Error

服務器遇到了一個未曾預料的狀況,導致了它無法完成對請求的處理。一般來說,這個問題都會在服務器的程序碼出錯時出現。

501 Not Implemented

服務器不支持當前請求所需要的某個功能。當服務器無法識別請求的方法,並且無法支持其對任何資源的請求。

502 Bad Gateway

作爲網關或者代理工作的服務器嘗試執行請求時,從上游服務器接收到無效的響應。

503 Service Unavailable

由於臨時的服務器維護或者過載,服務器當前無法處理請求。這個狀況是臨時的,並且將在一段時間以後恢復。如果能夠預計延遲時間,那麼響應中可以包含一個Retry-After頭用以標明這個延遲時間。如果沒有給出這個Retry-After信息,那麼客戶端應當以處理500響應的方式處理它。

504 Gateway Timeout

作爲網關或者代理工作的服務器嘗試執行請求時,未能及時從上游服務器(URI標識出的服務器,例如HTTP、FTP、LDAP)或者輔助服務器(例如DNS)收到響應。 
注意:某些代理服務器在DNS查詢超時時會返回400或者500錯誤。

505 HTTP Version Not Supported

服務器不支持,或者拒絕支持在請求中使用的HTTP版本。這暗示着服務器不能或不願使用與客戶端相同的版本。響應中應當包含一個描述了爲何版本不被支持以及服務器支持哪些協議的實體。

506 Variant Also Negotiates

由《透明內容協商協議》(RFC 2295)擴展,代表服務器存在內部配置錯誤:被請求的協商變元資源被配置爲在透明內容協商中使用自己,因此在一個協商處理中不是一個合適的重點。

507 Insufficient Storage

服務器無法存儲完成請求所必須的內容。這個狀況被認爲是臨時的。(WebDAV RFC 4918)

509 Bandwidth Limit Exceeded

服務器達到帶寬限制。這不是一個官方的狀態碼,但是仍被廣泛使用。

510 Not Extended

獲取資源所需要的策略並沒有被滿足。(RFC 2774)

2)301跳轉和302跳轉的區別?

一直對http狀態碼301和302的理解比較模糊,在遇到實際的問題和翻閱各種資料瞭解後,算是有了一定的理解。這裏記錄下,希望能有新的認識。大家也共勉。


官方的比較簡潔的說明:

        301 redirect: 301 代表永久性轉移(Permanently Moved)

        302 redirect: 302 代表暫時性轉移(Temporarily Moved )

        ps:這裏也順帶記住了兩個比較相近的英語單詞(permanently、temporarily),嘻哈!


        詳細來說,301和302狀態碼都表示重定向,就是說瀏覽器在拿到服務器返回的這個狀態碼後會自動跳轉到一個新的URL地址,這個地址可以從響應的Location首部中獲取(用戶看到的效果就是他輸入的地址A瞬間變成了另一個地址B)——這是它們的共同點。他們的不同在於。301表示舊地址A的資源已經被永久地移除了(這個資源不可訪問了),搜索引擎在抓取新內容的同時也將舊的網址交換爲重定向之後的網址;302表示舊地址A的資源還在(仍然可以訪問),這個重定向只是臨時地從舊地址A跳轉到地址B,搜索引擎會抓取新的內容而保存舊的網址。



      這裏開啓傻瓜自問自答模式(自己可能想到的疑問):

1、什麼是重定向啊?

        就是地址A跳轉到地址B啦。百度百科的解釋:重定向(Redirect)就是通過各種方法將各種網絡請求重新定個方向轉到其它位置(如:網頁重定向、域名的重定向、路由選擇的變化也是對數據報文經由路徑的一種重定向)。


2、可是,爲什麼要進行重定向啊?什麼時候需要重定向呢?

        想跳就跳,就跳的漂亮。還是借鑑百度百科:

1)網站調整(如改變網頁目錄結構);
2)網頁被移到一個新地址;
3)網頁擴展名改變(如應用需要把.php改成.Html或.shtml)。
        這種情況下,如果不做重定向,則用戶收藏夾或搜索引擎數據庫中舊地址只能讓訪問客戶得到一個404頁面錯誤信息,訪問流量白白喪失;再者某些註冊了多個域名的網站,也需要通過重定向讓訪問這些域名的用戶自動跳轉到主站點等。

3、那麼,什麼時候進行301或者302跳轉呢?
        當一個網站或者網頁24—48小時內臨時移動到一個新的位置,這時候就要進行302跳轉,打個比方說,我有一套房子,但是最近走親戚去親戚家住了,過兩天我還回來的。而使用301跳轉的場景就是之前的網站因爲某種原因需要移除掉,然後要到新的地址訪問,是永久性的,就比如你的那套房子其實是租的,現在租期到了,你又在另一個地方找到了房子,之前租的房子不住了。
    清晰明確而言:
使用301跳轉的場景:
1)域名到期不想續費(或者發現了更適合網站的域名),想換個域名。
2)在搜索引擎的搜索結果中出現了不帶www的域名,而帶www的域名卻沒有收錄,這個時候可以用301重定向來告訴搜索引擎我們目標的域名是哪一個。
3)空間服務器不穩定,換空間的時候。

使用302跳轉的場景:
        --儘量使用301跳轉!

4、爲什麼儘量要使用301跳轉?——網址劫持!
        這裏摘錄百度百科上的解釋:
       從網址A 做一個302 重定向到網址B 時,主機服務器的隱含意思是網址A 隨時有可能改主意,重新顯示本身的內容或轉向其他的地方。大部分的搜索引擎在大部分情況下,當收到302 重定向時,一般只要去抓取目標網址就可以了,也就是說網址B。如果搜索引擎在遇到302 轉向時,百分之百的都抓取目標網址B 的話,就不用擔心網址URL 劫持了。問題就在於,有的時候搜索引擎,尤其是Google,並不能總是抓取目標網址。比如說,有的時候A 網址很短,但是它做了一個302 重定向到B 網址,而B 網址是一個很長的亂七八糟的URL 網址,甚至還有可能包含一些問號之類的參數。很自然的,A 網址更加用戶友好,而B 網址既難看,又不用戶友好。這時Google 很有可能會仍然顯示網址A。由於搜索引擎排名算法只是程序而不是人,在遇到302 重定向的時候,並不能像人一樣的去準確判定哪一個網址更適當,這就造成了網址URL 劫持的可能性。也就是說,一個不道德的人在他自己的網址A 做一個302 重定向到你的網址B,出於某種原因, Google 搜索結果所顯示的仍然是網址A,但是所用的網頁內容卻是你的網址B 上的內容,這種情況就叫做網址URL 劫持。你辛辛苦苦所寫的內容就這樣被別人偷走了。302 重定向所造成的網址URL 劫持現象,已經存在一段時間了。不過到目前爲止,似乎也沒有什麼更好的解決方法。在正在進行的谷歌大爸爸數據中心轉換中,302 重定向問題也是要被解決的目標之一。從一些搜索結果來看,網址劫持現象有所改善,但是並沒有完全解決。
        我的理解是,從網站A(網站比較爛)上做了一個302跳轉到網站B(搜索排名很靠前),這時候有時搜索引擎會使用網站B的內容,但卻收錄了網站A的地址,這樣在不知不覺間,網站B在爲網站A作貢獻,網站A的排名就靠前了。
      301跳轉對查找引擎是一種對照馴良的跳轉編制,也是查找引擎能夠遭遇的跳轉編制,它告訴查找引擎,這個地址棄用了,永遠轉向一個新地址,可以轉移新域名的權重。而302重定向很容易被搜索引擎誤認爲是利用多個域名指向同一網站,那麼你的網站就會被封掉,罪名是“利用重複的內容來干擾Google搜索結果的網站排名”。

        自問自答模式先告一段落,這裏分享下我在NodeJs中實現跳轉的場景:

        之前做過一個重構的項目,由於各種原因,我們的網站的登錄以及註冊部分需要剝離爲另一個網站,域名和之前的不同,所以,我們需要保證舊的地址也能重定向到地址中去,我們就在舊的系統的node層中作了一個重定向,代碼類似這樣:



這裏沒有設置狀態碼,發現默認是302跳轉,然後我們設置了301狀態碼,類似這樣:



用fiddle抓包(上面的302調整我就不上圖了),看到效果:



以上是使用Express,用nodejs原生的代碼實現類似這樣:

3)http頭部信息詳解?

HTTP是一個屬於應用層的面向對象的協議,由於其簡捷、快速的方式,適用於分佈式超媒體信息系統。它於1990年提出,經過幾年的使用與發展,得到不斷地完善和擴展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的規範化工作正在進行之中,而且HTTP-NG(Next Generation of HTTP)的建議已經提出。
HTTP協議的主要特點可概括如下:
1.支持客戶/服務器模式。
2.簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法常用的有GET、HEAD、POST。每種方法規定了客戶與服務器聯繫的類型不同。由於HTTP協議簡單,使得HTTP服務器的程序規模小,因而通信速度很快。
3.靈活:HTTP允許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type加以標記。
4.無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開連接。採用這種方式可以節省傳輸時間。
5.無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果後續處理需要前面的信息,則它必須重傳,這樣可能導致每次連接傳送的數據量增大。另一方面,在服務器不需要先前信息時它的應答就較快。

 

一、HTTP協議詳解之URL篇

    http(超文本傳輸協議)是一個基於請求與響應模式的、無狀態的、應用層的協議,常基於TCP的連接方式,HTTP1.1版本中給出一種持續連接的機制,絕大多數的Web開發,都是構建在HTTP協議之上的Web應用。

HTTP URL (URL是一種特殊類型的URI,包含了用於查找某個資源的足夠的信息)的格式如下:
http://host[":"port][abs_path]
http表示要通過HTTP協議來定位網絡資源;host表示合法的Internet主機域名或者IP地址;port指定一個端口號,爲空則使用缺省端口80;abs_path指定請求資源的URI;如果URL中沒有給出abs_path,那麼當它作爲請求URI時,必須以“/”的形式給出,通常這個工作瀏覽器自動幫我們完成。
eg:
1、輸入:
www.guet.edu.cn
瀏覽器自動轉換成:http://www.guet.edu.cn/
2、http:192.168.0.116:8080/index.jsp 

 

二、HTTP協議詳解之請求篇

    http請求由三部分組成,分別是:請求行、消息報頭、請求正文

1、請求行以一個方法符號開頭,以空格分開,後面跟着請求的URI和協議的版本,格式如下:Method Request-URI HTTP-Version CRLF  
其中 Method表示請求方法;Request-URI是一個統一資源標識符;HTTP-Version表示請求的HTTP協議版本;CRLF表示回車和換行(除了作爲結尾的CRLF外,不允許出現單獨的CR或LF字符)。

請求方法(所有方法全爲大寫)有多種,各個方法的解釋如下:
GET     請求獲取Request-URI所標識的資源
POST    在Request-URI所標識的資源後附加新的數據
HEAD    請求獲取由Request-URI所標識的資源的響應消息報頭
PUT     請求服務器存儲一個資源,並用Request-URI作爲其標識
DELETE  請求服務器刪除Request-URI所標識的資源
TRACE   請求服務器回送收到的請求信息,主要用於測試或診斷
CONNECT 保留將來使用
OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求
應用舉例:
GET方法:在瀏覽器的地址欄中輸入網址的方式訪問網頁時,瀏覽器採用GET方法向服務器獲取資源,eg:GET /form.html HTTP/1.1 (CRLF)

POST方法要求被請求服務器接受附在請求後面的數據,常用於提交表單。
eg:POST /reg.jsp HTTP/ (CRLF)
Accept:image/gif,image/x-xbit,... (CRLF)
...
HOST:www.guet.edu.cn (CRLF)
Content-Length:22 (CRLF)
Connection:Keep-Alive (CRLF)
Cache-Control:no-cache (CRLF)
(CRLF)         //該CRLF表示消息報頭已經結束,在此之前爲消息報頭
user=jeffrey&pwd=1234  //此行以下爲提交的數據

HEAD方法與GET方法幾乎是一樣的,對於HEAD請求的迴應部分來說,它的HTTP頭部中包含的信息與通過GET請求所得到的信息是相同的。利用這個方法,不必傳輸整個資源內容,就可以得到Request-URI所標識的資源的信息。該方法常用於測試超鏈接的有效性,是否可以訪問,以及最近是否更新。
2、請求報頭後述
3、請求正文(略) 

 

三、HTTP協議詳解之響應篇

    在接收和解釋請求消息後,服務器返回一個HTTP響應消息。

HTTP響應也是由三個部分組成,分別是:狀態行、消息報頭、響應正文
1、狀態行格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
其中,HTTP-Version表示服務器HTTP協議的版本;Status-Code表示服務器發回的響應狀態代碼;Reason-Phrase表示狀態代碼的文本描述。
狀態代碼有三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:
1xx:指示信息--表示請求已接收,繼續處理
2xx:成功--表示請求已被成功接收、理解、接受
3xx:重定向--要完成請求必須進行更進一步的操作
4xx:客戶端錯誤--請求有語法錯誤或請求無法實現
5xx:服務器端錯誤--服務器未能實現合法的請求
常見狀態代碼、狀態描述、說明:
200 OK      //客戶端請求成功
400 Bad Request  //客戶端請求有語法錯誤,不能被服務器所理解
401 Unauthorized //請求未經授權,這個狀態代碼必須和WWW-Authenticate報頭域一起使用 
403 Forbidden  //服務器收到請求,但是拒絕提供服務
404 Not Found  //請求資源不存在,eg:輸入了錯誤的URL
500 Internal Server Error //服務器發生不可預期的錯誤
503 Server Unavailable  //服務器當前不能處理客戶端的請求,一段時間後可能恢復正常
eg:HTTP/1.1 200 OK (CRLF)

2、響應報頭後述

3、響應正文就是服務器返回的資源的內容 

 

四、HTTP協議詳解之消息報頭篇

    HTTP消息由客戶端到服務器的請求和服務器到客戶端的響應組成。請求消息和響應消息都是由開始行(對於請求消息,開始行就是請求行,對於響應消息,開始行就是狀態行),消息報頭(可選),空行(只有CRLF的行),消息正文(可選)組成。

HTTP消息報頭包括普通報頭、請求報頭、響應報頭、實體報頭。
每一個報頭域都是由名字+“:”+空格+值 組成,消息報頭域的名字是大小寫無關的。

1、普通報頭
在普通報頭中,有少數報頭域用於所有的請求和響應消息,但並不用於被傳輸的實體,只用於傳輸的消息。
eg:
Cache-Control   用於指定緩存指令,緩存指令是單向的(響應中出現的緩存指令在請求中未必會出現),且是獨立的(一個消息的緩存指令不會影響另一個消息處理的緩存機制),HTTP1.0使用的類似的報頭域爲Pragma。
請求時的緩存指令包括:no-cache(用於指示請求或響應消息不能緩存)、no-store、max-age、max-stale、min-fresh、only-if-cached;
響應時的緩存指令包括:public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage.
eg:爲了指示IE瀏覽器(客戶端)不要緩存頁面,服務器端的JSP程序可以編寫如下:response.sehHeader("Cache-Control","no-cache");
//response.setHeader("Pragma","no-cache");作用相當於上述代碼,通常兩者//合用
這句代碼將在發送的響應消息中設置普通報頭域:Cache-Control:no-cache


Date普通報頭域表示消息產生的日期和時間

Connection普通報頭域允許發送指定連接的選項。例如指定連接是連續,或者指定“close”選項,通知服務器,在響應完成後,關閉連接

2、請求報頭
請求報頭允許客戶端向服務器端傳遞請求的附加信息以及客戶端自身的信息。
常用的請求報頭
Accept
Accept請求報頭域用於指定客戶端接受哪些類型的信息。eg:Accept:image/gif,表明客戶端希望接受GIF圖象格式的資源;Accept:text/html,表明客戶端希望接受html文本。
Accept-Charset
Accept-Charset請求報頭域用於指定客戶端接受的字符集。eg:Accept-Charset:iso-8859-1,gb2312.如果在請求消息中沒有設置這個域,缺省是任何字符集都可以接受。
Accept-Encoding
Accept-Encoding請求報頭域類似於Accept,但是它是用於指定可接受的內容編碼。eg:Accept-Encoding:gzip.deflate.如果請求消息中沒有設置這個域服務器假定客戶端對各種內容編碼都可以接受。
Accept-Language
Accept-Language請求報頭域類似於Accept,但是它是用於指定一種自然語言。eg:Accept-Language:zh-cn.如果請求消息中沒有設置這個報頭域,服務器假定客戶端對各種語言都可以接受。
Authorization
Authorization請求報頭域主要用於證明客戶端有權查看某個資源。當瀏覽器訪問一個頁面時,如果收到服務器的響應代碼爲401(未授權),可以發送一個包含Authorization請求報頭域的請求,要求服務器對其進行驗證。
Host(發送請求時,該報頭域是必需的)
Host請求報頭域主要用於指定被請求資源的Internet主機和端口號,它通常從HTTP URL中提取出來的,eg:
我們在瀏覽器中輸入:
http://www.guet.edu.cn/index.html
瀏覽器發送的請求消息中,就會包含Host請求報頭域,如下:
Host:
www.guet.edu.cn
此處使用缺省端口號80,若指定了端口號,則變成:Host:www.guet.edu.cn:指定端口號
User-Agent
我們上網登陸論壇的時候,往往會看到一些歡迎信息,其中列出了你的操作系統的名稱和版本,你所使用的瀏覽器的名稱和版本,這往往讓很多人感到很神奇,實際上,服務器應用程序就是從User-Agent這個請求報頭域中獲取到這些信息。User-Agent請求報頭域允許客戶端將它的操作系統、瀏覽器和其它屬性告訴服務器。不過,這個報頭域不是必需的,如果我們自己編寫一個瀏覽器,不使用User-Agent請求報頭域,那麼服務器端就無法得知我們的信息了。
請求報頭舉例:
GET /form.html HTTP/1.1 (CRLF)
Accept:image/gif,image/x-xbitmap,image/jpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/* (CRLF)
Accept-Language:zh-cn (CRLF)
Accept-Encoding:gzip,deflate (CRLF)
If-Modified-Since:Wed,05 Jan 2007 11:21:25 GMT (CRLF)
If-None-Match:W/"80b1a4c018f3c41:8317" (CRLF)
User-Agent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0) (CRLF)
Host:www.guet.edu.cn (CRLF)
Connection:Keep-Alive (CRLF)
(CRLF)

3、響應報頭
響應報頭允許服務器傳遞不能放在狀態行中的附加響應信息,以及關於服務器的信息和對Request-URI所標識的資源進行下一步訪問的信息。
常用的響應報頭
Location
Location響應報頭域用於重定向接受者到一個新的位置。Location響應報頭域常用在更換域名的時候。
Server
Server響應報頭域包含了服務器用來處理請求的軟件信息。與User-Agent請求報頭域是相對應的。下面是
Server響應報頭域的一個例子:
Server:Apache-Coyote/1.1
WWW-Authenticate
WWW-Authenticate響應報頭域必須被包含在401(未授權的)響應消息中,客戶端收到401響應消息時候,併發送Authorization報頭域請求服務器對其進行驗證時,服務端響應報頭就包含該報頭域。
eg:WWW-Authenticate:Basic realm="Basic Auth Test!"  //可以看出服務器對請求資源採用的是基本驗證機制。


4、實體報頭
請求和響應消息都可以傳送一個實體。一個實體由實體報頭域和實體正文組成,但並不是說實體報頭域和實體正文要在一起發送,可以只發送實體報頭域。實體報頭定義了關於實體正文(eg:有無實體正文)和請求所標識的資源的元信息。
常用的實體報頭
Content-Encoding
Content-Encoding實體報頭域被用作媒體類型的修飾符,它的值指示了已經被應用到實體正文的附加內容的編碼,因而要獲得Content-Type報頭域中所引用的媒體類型,必須採用相應的解碼機制。Content-Encoding這樣用於記錄文檔的壓縮方法,eg:Content-Encoding:gzip
Content-Language
Content-Language實體報頭域描述了資源所用的自然語言。沒有設置該域則認爲實體內容將提供給所有的語言閱讀
者。eg:Content-Language:da
Content-Length
Content-Length實體報頭域用於指明實體正文的長度,以字節方式存儲的十進制數字來表示。
Content-Type
Content-Type實體報頭域用語指明發送給接收者的實體正文的媒體類型。eg:
Content-Type:text/html;charset=ISO-8859-1
Content-Type:text/html;charset=GB2312
Last-Modified
Last-Modified實體報頭域用於指示資源的最後修改日期和時間。
Expires
Expires實體報頭域給出響應過期的日期和時間。爲了讓代理服務器或瀏覽器在一段時間以後更新緩存中(再次訪問曾訪問過的頁面時,直接從緩存中加載,縮短響應時間和降低服務器負載)的頁面,我們可以使用Expires實體報頭域指定頁面過期的時間。eg:Expires:Thu,15 Sep 2006 16:23:12 GMT
HTTP1.1的客戶端和緩存必須將其他非法的日期格式(包括0)看作已經過期。eg:爲了讓瀏覽器不要緩存頁面,我們也可以利用Expires實體報頭域,設置爲0,jsp中程序如下:response.setDateHeader("Expires","0");

 

五、利用telnet觀察http協議的通訊過程

    實驗目的及原理:
    利用MS的telnet工具,通過手動輸入http請求信息的方式,向服務器發出請求,服務器接收、解釋和接受請求後,會返回一個響應,該響應會在telnet窗口上顯示出來,從而從感性上加深對http協議的通訊過程的認識。

    實驗步驟:

1、打開telnet
1.1 打開telnet
運行-->cmd-->telnet

1.2 打開telnet回顯功能
set localecho

2、連接服務器併發送請求
2.1 open 
www.guet.edu.cn 80  //注意端口號不能省略

    HEAD /index.asp HTTP/1.0
    Host:www.guet.edu.cn
    
   /*我們可以變換請求方法,請求桂林電子主頁內容,輸入消息如下*/
    open 
www.guet.edu.cn 80 
   
    GET /index.asp HTTP/1.0  //請求資源的內容
    Host:www.guet.edu.cn  

2.2 open www.sina.com.cn 80  //在命令提示符號下直接輸入telnet www.sina.com.cn 80
    HEAD /index.asp HTTP/1.0
    Host:www.sina.com.cn
 

3 實驗結果:

3.1 請求信息2.1得到的響應是:

HTTP/1.1 200 OK                                              //請求成功
Server: Microsoft-IIS/5.0                                    //web服務器
Date: Thu,08 Mar 200707:17:51 GMT
Connection: Keep-Alive                                 
Content-Length: 23330
Content-Type: text/html
Expries: Thu,08 Mar 2007 07:16:51 GMT
Set-Cookie: ASPSESSIONIDQAQBQQQB=BEJCDGKADEDJKLKKAJEOIMMH; path=/
Cache-control: private

//資源內容省略

3.2 請求信息2.2得到的響應是:

HTTP/1.0 404 Not Found       //請求失敗
Date: Thu, 08 Mar 2007 07:50:50 GMT
Server: Apache/2.0.54 <Unix>
Last-Modified: Thu, 30 Nov 2006 11:35:41 GMT
ETag: "6277a-415-e7c76980"
Accept-Ranges: bytes
X-Powered-By: mod_xlayout_jh/0.0.1vhs.markII.remix
Vary: Accept-Encoding
Content-Type: text/html
X-Cache: MISS from zjm152-78.sina.com.cn
Via: 1.0 zjm152-78.sina.com.cn:80<squid/2.6.STABLES-20061207>
X-Cache: MISS from th-143.sina.com.cn
Connection: close


失去了跟主機的連接

按任意鍵繼續...

4 .注意事項:1、出現輸入錯誤,則請求不會成功。
          2、報頭域不分大小寫。
          3、更深一步瞭解HTTP協議,可以查看RFC2616,在
http://www.letf.org/rfc上找到該文件。
          4、開發後臺程序必須掌握http協議

六、HTTP協議相關技術補充

    1、基礎:
    高層協議有:文件傳輸協議FTP、電子郵件傳輸協議SMTP、域名系統服務DNS、網絡新聞傳輸協議NNTP和HTTP協議等
中介由三種:代理(Proxy)、網關(Gateway)和通道(Tunnel),一個代理根據URI的絕對格式來接受請求,重寫全部或部分消息,通過 URI的標識把已格式化過的請求發送到服務器。網關是一個接收代理,作爲一些其它服務器的上層,並且如果必須的話,可以把請求翻譯給下層的服務器協議。一 個通道作爲不改變消息的兩個連接之間的中繼點。當通訊需要通過一箇中介(例如:防火牆等)或者是中介不能識別消息的內容時,通道經常被使用。
     代理(Proxy):一箇中間程序,它可以充當一個服務器,也可以充當一個客戶機,爲其它客戶機建立請求。請求是通過可能的翻譯在內部或經過傳遞到其它的 服務器中。一個代理在發送請求信息之前,必須解釋並且如果可能重寫它。代理經常作爲通過防火牆的客戶機端的門戶,代理還可以作爲一個幫助應用來通過協議處 理沒有被用戶代理完成的請求。
網關(Gateway):一個作爲其它服務器中間媒介的服務器。與代理不同的是,網關接受請求就好象對被請求的資源來說它就是源服務器;發出請求的客戶機並沒有意識到它在同網關打交道。
網關經常作爲通過防火牆的服務器端的門戶,網關還可以作爲一個協議翻譯器以便存取那些存儲在非HTTP系統中的資源。
    通道(Tunnel):是作爲兩個連接中繼的中介程序。一旦激活,通道便被認爲不屬於HTTP通訊,儘管通道可能是被一個HTTP請求初始化的。當被中繼 的連接兩端關閉時,通道便消失。當一個門戶(Portal)必須存在或中介(Intermediary)不能解釋中繼的通訊時通道被經常使用。

2、協議分析的優勢—HTTP分析器檢測網絡攻擊
以模塊化的方式對高層協議進行分析處理,將是未來入侵檢測的方向。
HTTP及其代理的常用端口80、3128和8080在network部分用port標籤進行了規定

3、HTTP協議Content Lenth限制漏洞導致拒絕服務攻擊
使用POST方法時,可以設置ContentLenth來定義需要傳送的數據長度,例如ContentLenth:999999999,在傳送完成前,內 存不會釋放,攻擊者可以利用這個缺陷,連續向WEB服務器發送垃圾數據直至WEB服務器內存耗盡。這種攻擊方法基本不會留下痕跡。
http://www.cnpaf.net/Class/HTTP/0532918532667330.html

4、利用HTTP協議的特性進行拒絕服務攻擊的一些構思
服務器端忙於處理攻擊者僞造的TCP連接請求而無暇理睬客戶的正常請求(畢竟客戶端的正常請求比率非常之小),此時從正常客戶的角度看來,服務器失去響應,這種情況我們稱作:服務器端受到了SYNFlood攻擊(SYN洪水攻擊)。
而Smurf、TearDrop等是利用ICMP報文來Flood和IP碎片攻擊的。本文用“正常連接”的方法來產生拒絕服務攻擊。
19端口在早期已經有人用來做Chargen攻擊了,即Chargen_Denial_of_Service,但是!他們用的方法是在兩臺Chargen 服務器之間產生UDP連接,讓服務器處理過多信息而DOWN掉,那麼,幹掉一臺WEB服務器的條件就必須有2個:1.有Chargen服務2.有HTTP 服務
方法:攻擊者僞造源IP給N臺Chargen發送連接請求(Connect),Chargen接收到連接後就會返回每秒72字節的字符流(實際上根據網絡實際情況,這個速度更快)給服務器。

5、Http指紋識別技術
   Http指紋識別的原理大致上也是相同的:記錄不同服務器對Http協議執行中的微小差別進行識別.Http指紋識別比TCP/IP堆棧指紋識別複雜許 多,理由是定製Http服務器的配置文件、增加插件或組件使得更改Http的響應信息變的很容易,這樣使得識別變的困難;然而定製TCP/IP堆棧的行爲 需要對核心層進行修改,所以就容易識別.
      要讓服務器返回不同的Banner信息的設置是很簡單的,象Apache這樣的開放源代碼的Http服務器,用戶可以在源代碼裏修改Banner信息,然 後重起Http服務就生效了;對於沒有公開源代碼的Http服務器比如微軟的IIS或者是Netscape,可以在存放Banner信息的Dll文件中修 改,相關的文章有討論的,這裏不再贅述,當然這樣的修改的效果還是不錯的.另外一種模糊Banner信息的方法是使用插件。
常用測試請求:
1:HEAD/Http/1.0發送基本的Http請求
2:DELETE/Http/1.0發送那些不被允許的請求,比如Delete請求
3:GET/Http/3.0發送一個非法版本的Http協議請求
4:GET/JUNK/1.0發送一個不正確規格的Http協議請求
Http指紋識別工具Httprint,它通過運用統計學原理,組合模糊的邏輯學技術,能很有效的確定Http服務器的類型.它可以被用來收集和分析不同Http服務器產生的簽名。

6、其他:爲了提高用戶使用瀏覽器時的性能,現代瀏覽器還支持併發的訪問方式,瀏覽一個網頁時同時建立多個連接,以迅速獲得一個網頁上的多個圖標,這樣能更快速完成整個網頁的傳輸。
HTTP1.1中提供了這種持續連接的方式,而下一代HTTP協議:HTTP-NG更增加了有關會話控制、豐富的內容協商等方式的支持,來提供
更高效率的連接。

4)併發連接數、請求數、併發用戶數分別是什麼意思?

概念

併發連接數-SBC(Simultaneous Browser Connections

併發連接數指的是客戶端向服務器發起請求,並建立了TCP連接。每秒鐘服務器鏈接的總TCP數量,就是併發連接數。

請求數-QPS(Query Per Second)/RPS(Request Per Second)

請求數有2個縮寫,可以叫QPS也可以叫RPS。單位是每秒多少請求。Query=查詢,也相當於請求。請求數指的是客戶端在建立完連接後,向http服務發出GET/POST/HEAD數據包,服務器返回了請求結果後有兩種情況:

  • http數據包頭包含Close字樣,關閉本次TCP連接;
  • http數據包頭包含Keep-Alive字樣,本次連接不關閉,可繼續通過該連接繼續向http服務發送請求,用於減少TCP併發連接數。

服務器性能怎麼測?

通常情況下,我們測試的是QPS,也就是每秒請求數。不過爲了衡量服務器的總體性能,測試時最好一起測試併發連接數和請求數。

測試原理

  • 測試併發連接數採用每個併發1請求,多個併發進行;
  • 測試請求數採用多併發、每個併發多個請求進行,總的請求數將會=併發數*單併發請求數,需要注意的是不同的併發和單併發請求數得出來的結果會不同,因此最好測試多次取平均值。

區分請求數意義何在?

大家打開Chrome瀏覽器,按下F12,切換到Network選項卡,隨便打開一個網頁,按下F5刷新,將會看到刷刷一堆的請求。這裏給出某大牛收集來的不同瀏覽器產生的單站點併發連接數:

瀏覽器HTTP 1.1HTTP 1.0
IE 6,724
IE 866
Firefox 228
Firefox 366
Safari 3, 444
Chrome 1,26?
Chrome 344
Opera 9.63,10.00alpha44

以Chrome爲例,假設服務器設置的是Close(非持久連接),瀏覽器打開網頁後,首先打開4個併發加載數據,在這些請求完成後關閉4個連接,再打開4個併發連接加載數據。也就是說,並不是這個網頁有100個請求就會產生100併發,而是4個併發連接並行。假設服務器設置的是keep-alive(持久連接),瀏覽器打開網頁後,首先打開4個併發加載數據,在這些請求完成後不關閉連接,而是繼續發出請求,節約重新打開連接的時間。【前面紅色標出的是keep-alive持久連接和close非持久的區別,持久連接除了Squid(這貨用了特殊方法在http 1.0實現持久連接),只在http 1.1協議中有效!】

主機到底能多少人在線?

看到這裏相信你已經知道答案了,這個問題無解,根據網頁的內容大小和單網頁的請求數和服務器的配置而定,這個數據的浮動值非常大所以無法測量。因此能承諾保證多少用戶在線就是坑爹的主機商!

併發用戶

併發用戶數量,有兩種常見的錯誤觀點。一種錯誤觀點是把併發用戶數量理解爲使用系統的全部用戶的數量,理由是這些用戶可能同時使用系統;還有一種比較接近正確的觀點是把用戶在線數量理解爲併發用戶數量。實際上,在線用戶不一定會和其他用戶發生併發,例如正在瀏覽網頁的用戶,對服務器是沒有任何影響的。但是,用戶在線數量是統計併發用戶數量的主要依據之一。
併發主要是針對服務器而言,是否併發的關鍵是看用戶操作是否對服務器產生了影響。因此,併發用戶數量的正確理解爲:在同一時刻與服務器進行了交互的在線用戶數量。這些用戶的最大特徵是和服務器產生了交互,這種交互既可以是單向的傳輸數據,也可以是雙向的傳送數據。
併發用戶數量的統計的方法目前還沒有準確的公式,因爲不同系統會有不同的併發特點。例如OA系統統計併發用戶數量的經驗公式爲:使用系統用戶數量*(5%~20%)。對於這個公式是沒有必要拘泥於計算的結果,因爲爲了保證系統的擴展空間,測試時的併發用戶數量要稍微大一些,除非是要測試系統能承載的最大併發用戶數量。舉例說明:如果一個OA系統的期望用戶爲1000個,只要測試出系統能支持200個併發用戶就可以了。


4、瀏覽器
1)瀏覽器加載渲染網頁的過程

瀏覽器的工作機制,一句話概括起來就是:web瀏覽器與web服務器之間通過HTTP協議進行通信的過程。所以,C/S之間握手的協議就是HTTP協議。瀏覽器接收完畢開始渲染之前大致過程如下:

瀏覽器加載渲染網頁過程解析 - 落楓loven - SEO|網絡營銷|百度競價 - 林宗輝
 


從瀏覽器地址欄的請求鏈接開始,瀏覽器通過DNS解析查到域名映射的IP地址,成功之後瀏覽器端向此IP地址取得連接,成功連接之後,瀏覽器端將請 求頭信息 通過HTTP協議向此IP地址所在服務器發起請求,服務器接受到請求之後等待處理,最後向瀏覽器端發回響應,此時在HTTP協議下,瀏覽器從服務器接收到 text/html類型的代碼,瀏覽器開始顯示此html,並獲取其中內嵌資源地址,然後瀏覽器再發起請求來獲取這些資源,並在瀏覽器的html中顯示。

離我們最近並能直接顯示一個完整通信過程的工具就是Firebug了,看下圖:

瀏覽器加載渲染網頁過程解析 - 落楓loven - SEO|網絡營銷|百度競價 - 林宗輝
 


其中黃色的tips浮層告訴了我們”colorBox.html”從發起請求到關閉連接整個過程中每個環節的時長(域名解析 -> 建立連接 -> 發起請求 -> 等待響應 -> 接收數據),點擊該請求,可以獲得HTTP的headers信息,包含響應頭信息與請求頭信息,如:
//響應頭信息 HTTP/1.1 304 Not Modified Date: Wed, 02 Mar 2011 08:20:06 GMT Server: Apache/2.2.4 (Win32) PHP/5.2.1 Connection: Keep-Alive Keep-Alive: timeout=5, max=100 Etag: "1e483-1324-a86f5621"
//請求頭信息 GET /Docs/eva/api/colorBox.html HTTP/1.1 Host: ued.com User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Referer: http://ued.com/Docs/ If-Modified-Since: Thu, 17 Feb 2011 10:14:07 GMT If-None-Match: "1e483-1324-a86f5621" Cache-Control: max-age=0

另外,ajax異步請求同樣遵循HTTP協議,原理大同小異。

瀏覽器加載顯示html頁面內容的順序

我們經常看到瀏覽器在加載某個頁面時,部分內容先顯示出來,又有些內容後顯示。那麼瀏覽器加載顯示html究竟是按什麼順序進行的呢?

其實瀏覽器加載顯示html的順序是按下面的順序進行的:
1、IE下載的順序是從上到下,渲染的順序也是從上到下,下載和渲染是同時進行的。
2、在渲染到頁面的某一部分時,其上面的所有部分都已經下載完成(並不是說所有相關聯的元素都已經下載完)。
3、如果遇到語義解釋性的標籤嵌入文件(JS腳本,CSS 劍 敲創聳盜E的下載過程會啓用單獨連接進行下載。
4、並且在下載後進行解析,解析過程中,停止頁面所有往下元素的下載。
5、樣式表在下載完成後,將和以前下載的所有樣式表一起進行解析,解析完成後,將對此前所有元素(含以前已經渲染的)重新進行渲染。
6、JS、CSS中如有重定義,後定義函數將覆蓋前定義函數。

Firefox處理下載和渲染順序大體相同,只是在細微之處有些差別,例如:iframe的渲染

如果你的網頁比較大,希望部分內容先顯示出來,粘住瀏覽者,那麼你可以按照上面的規則合理的佈局你的網頁,達到預期的目的。

 JS的加載
不能並行下載和解析(阻塞下載)
當 引用了JS的時候,瀏覽器發送1個jsrequest就會一直等待該request的返回。因爲瀏覽器需要1個穩定的DOM樹結構,而JS中很有可能有代 碼直接改變了DOM樹結構,比如使用 document.write 或 appendChild,甚至是直接使用的location.href進行跳轉,瀏覽器爲了防止出現JS修改DOM樹,需要重新構建DOM樹的情況,所以 就會阻塞其他的下載和呈現.

 

  爲了更清楚的顯示頁面元素的加載順序,動手寫了一個程序,程序對頁面中的每個元素都延遲10秒。

程序的位置在見附件。

首先查看TestHtmlOrder.aspx這個頁面,使用HttpWatcher來檢測頁面元素的加載。

從下面的圖中可以看到加載順序。


瀏覽器加載渲染網頁過程解析 - 落楓loven - SEO|網絡營銷|百度競價 - 林宗輝
 

IE首先加載了主頁面TestHtmlOrder.aspx,

下載了主頁面後,頁面首先顯示的是“紅色劍靈”、“藍色劍靈”幾個字,但此時顯示的是隻是黑色字體,沒有樣式,因爲樣式還沒有下載下來。

接下來頁面中的標籤是JS標籤,屬於嵌入文件,因此IE需要將其下載下來。這有兩個文件,雖然IE同時能夠和WebServer建立兩個鏈接,但是此時並沒有使用兩個連接,而是使用一個連接,在下載完成後,接下來才下載另外一個文件。

究其原因,是因爲JS包含了語法定義,在第二個文件裏面的函數可能用到了第一個文件裏面的變量和函數,IE沒有辦法判斷,或者需要很耗時的判斷,才 能判斷文件下載的先後順序。而在解釋方面,IE對JS文件是下載一個,解釋一個(可以執行文件TestJsOrder2.aspx)。如果先下載的是第二 個文件,此時就會發生解釋錯誤。因此需要開發者自己在放置JS文件位置時,按先後順序放好,IE依次下載進行解釋。後面的函數覆蓋前面的函數定義

在下載完成後,我們看到helloWorld,helloworld2,開始順序執行。而此時字體的樣式表和圖片仍然沒有下載下來。

在helloWorld,helloWorld2執行過程時,此時頁面停留在函數執行的中斷點(alert部分)。此時IE並沒有去下載CSS的文件。由此說明JS函數的執行會阻塞IE的下載。

接下來我們看到CSS文件的下載也是使用了一個連接,也是串行下載。其串行下載的原因和JS串行下載原因是一樣的。

在兩個CSS文件下載過程中,我們看到“紅色劍靈”,“藍色劍靈”依次變爲紅色和藍色,兩者顏色的轉換時間相差在10秒,說明樣式文件和JS文件一樣是下載完一個解析一個的。

現在轉到TestCssOrder.aspx看一下,可以看到 開始時“紅色劍靈”,“紅色強壯劍靈”,顯示爲紅色,過了10秒“藍色劍靈”顯示爲藍色,再過10秒,“紅色強壯劍靈”字體變粗了,同時“紅色強壯劍靈 2”開始出現。在剛開始“紅色劍靈”,“紅色強壯劍靈”顯示紅色時,第三個樣式還沒有下載下來,此時IE使用已經下載到樣式對上面的元素渲染了一遍,此時 雖然“紅色劍靈”,“紅色強壯劍靈”樣式定義不同,但是顯示效果一樣。第三個文件下載後,此時IE又重新對“紅色強壯劍靈”渲染了一遍,此時其變爲加粗, 以上所有的文件加載並且渲染完成後,開始渲染下面的標籤“紅色強壯劍靈2”

有一點需要證明:在IE使用樣式對標籤進行渲染時,是不是停止了其他頁面元素的下載?原來我想通過加長渲染時間(利用濾鏡,將標籤元素數目增大)來檢測,不過沒有驗證成功。只是從JS函數的執行推斷CSS的渲染也是如此。

接下來看到的是圖片文件下載,此時看到的是兩個圖片同時開始下載,而且是下載完成後,立即在頁面上開始顯示,直到所有的圖片下載完成。

注:一個測試文件在網絡傳輸上所花費時間的辦法。

首先需要明白檢測中w ait值的意義:wait = 服務器所花時間 + 網絡時間

服務器所花時間我們可以用Thread.Sleep(10000);來讓其休息10s,

比如這個:

瀏覽器加載渲染網頁過程解析 - 落楓loven - SEO|網絡營銷|百度競價 - 林宗輝
 

由此大概可以計算出 10.002-10 = 0.002秒,這就是大概在網絡上所花的時間。


2)瀏覽器工作原理

簡介

瀏覽器可以被認爲是使用最廣泛的軟件,本文將介紹瀏覽器的工作原理,我們將看到,從你在地址欄輸入google.com到你看到google主頁過程中都發生了什麼。

將討論的瀏覽器

今天,有五種主流瀏覽器——IE、Firefox、Safari、Chrome及Opera。

本文將基於一些開源瀏覽器的例子——Firefox、 Chrome及Safari,Safari是部分開源的。

根據W3C(World Wide Web Consortium 萬維網聯盟)的瀏覽器統計數據,當前(2011年5月),Firefox、Safari及Chrome的市場佔有率綜合已接近60%。(原文爲2009年10月,數據沒有太大變化)因此,可以說開源瀏覽器已經佔據了瀏覽器市場的半壁江山。

瀏覽器的主要功能

瀏覽器的主要功能是將用戶選擇得web資源呈現出來,它需要從服務器請求資源,並將其顯示在瀏覽器窗口中,資源的格式通常是HTML,也包括PDF、image及其他格式。用戶用URI(Uniform Resource Identifier 統一資源標識符)來指定所請求資源的位置,在網絡一章有更多討論。

HTML和CSS規範中規定了瀏覽器解釋html文檔的方式,由 W3C組織對這些規範進行維護,W3C是負責制定web標準的組織。

HTML規範的最新版本是HTML4(http://www.w3.org/TR/html401/),HTML5還在制定中(譯註:兩年前),最新的CSS規範版本是2(http://www.w3.org/TR/CSS2),CSS3也還正在制定中(譯註:同樣兩年前)。

這些年來,瀏覽器廠商紛紛開發自己的擴展,對規範的遵循並不完善,這爲web開發者帶來了嚴重的兼容性問題。

但是,瀏覽器的用戶界面則差不多,常見的用戶界面元素包括:

·     用來輸入URI的地址欄

·     前進、後退按鈕

·     書籤選項

·     用於刷新及暫停當前加載文檔的刷新、暫停按鈕

·     用於到達主頁的主頁按鈕

奇怪的是,並沒有哪個正式公佈的規範對用戶界面做出規定,這些是多年來各瀏覽器廠商之間相互模仿和不斷改進得結果。

Html5並沒有規定瀏覽器必須具有的UI元素,但列出了一些常用元素,包括地址欄、狀態欄及工具欄。還有一些瀏覽器有自己專有得功能,比如Firefox得下載管理。更多相關內容將在後面討論用戶界面時介紹。

瀏覽器的主要構成High Level Structure

瀏覽器的主要組件包括:

1.     用戶界面-包括地址欄、後退/前進按鈕、書籤目錄等,也就是你所看到的除了用來顯示你所請求頁面的主窗口之外的其他部分

2.     瀏覽器引擎-用來查詢及操作渲染引擎的接口

3.     渲染引擎-用來顯示請求的內容,例如,如果請求內容爲html,它負責解析html及css,並將解析後的結果顯示出來

4.     網絡-用來完成網絡調用,例如http請求,它具有平臺無關的接口,可以在不同平臺上工作

5.     UI後端-用來繪製類似組合選擇框及對話框等基本組件,具有不特定於某個平臺的通用接口,底層使用操作系統的用戶接口

6.     JS解釋器-用來解釋執行JS代碼

7.     數據存儲-屬於持久層,瀏覽器需要在硬盤中保存類似cookie的各種數據,HTML5定義了web database技術,這是一種輕量級完整的客戶端存儲技術

 

                                           圖1:瀏覽器主要組件

需要注意的是,不同於大部分瀏覽器,Chrome爲每個Tab分配了各自的渲染引擎實例,每個Tab就是一個獨立的進程。

對於構成瀏覽器的這些組件,後面會逐一詳細討論。

組件間的通信 Communication between the components

Firefox和Chrome都開發了一個特殊的通信結構,後面將有專門的一章進行討論。

渲染引擎 The rendering engine

渲染引擎的職責就是渲染,即在瀏覽器窗口中顯示所請求的內容。

默認情況下,渲染引擎可以顯示html、xml文檔及圖片,它也可以藉助插件(一種瀏覽器擴展)顯示其他類型數據,例如使用PDF閱讀器插件,可以顯示PDF格式,將由專門一章講解插件及擴展,這裏只討論渲染引擎最主要的用途——顯示應用了CSS之後的html及圖片。

渲染引擎 Rendering engines

本文所討論得瀏覽器——Firefox、Chrome和Safari是基於兩種渲染引擎構建的,Firefox使用Geoko——Mozilla自主研發的渲染引擎,Safari和Chrome都使用webkit。

Webkit是一款開源渲染引擎,它本來是爲Linux平臺研發的,後來由Apple移植到Mac及Windows上,相關內容請參考http://webkit.org

主流程 The main flow

渲染引擎首先通過網絡獲得所請求文檔的內容,通常以8K分塊的方式完成。

下面是渲染引擎在取得內容之後的基本流程:

解析html以構建dom樹->構建render樹->佈局render樹->繪製render樹

                                            圖2:渲染引擎基本流程

渲染引擎開始解析html,並將標籤轉化爲內容樹中的dom節點。接着,它解析外部CSS文件及style標籤中的樣式信息。這些樣式信息以及html中的可見性指令將被用來構建另一棵樹——render樹。

Render樹由一些包含有顏色和大小等屬性的矩形組成,它們將被按照正確的順序顯示到屏幕上。

Render樹構建好了之後,將會執行佈局過程,它將確定每個節點在屏幕上的確切座標。再下一步就是繪製,即遍歷render樹,並使用UI後端層繪製每個節點。

值得注意的是,這個過程是逐步完成的,爲了更好的用戶體驗,渲染引擎將會儘可能早的將內容呈現到屏幕上,並不會等到所有的html都解析完成之後再去構建和佈局render樹。它是解析完一部分內容就顯示一部分內容,同時,可能還在通過網絡下載其餘內容。

 

                                                       圖3:webkit主流程

 

                                   圖4:Mozilla的Geoko 渲染引擎主流程

從圖3和4中可以看出,儘管webkit和Gecko使用的術語稍有不同,他們的主要流程基本相同。Gecko稱可見的格式化元素組成的樹爲frame樹,每個元素都是一個frame,webkit則使用render樹這個名詞來命名由渲染對象組成的樹。Webkit中元素的定位稱爲佈局,而Gecko中稱爲迴流。Webkit稱利用dom節點及樣式信息去構建render樹的過程爲attachment,Gecko在html和dom樹之間附加了一層,這層稱爲內容接收器,相當製造dom元素的工廠。下面將討論流程中的各個階段。

解析 Parsing-general

既然解析是渲染引擎中一個非常重要的過程,我們將稍微深入的研究它。首先簡要介紹一下解析。

解析一個文檔即將其轉換爲具有一定意義的結構——編碼可以理解和使用的東西。解析的結果通常是表達文檔結構的節點樹,稱爲解析樹或語法樹。

例如,解析“2+3-1”這個表達式,可能返回這樣一棵樹。

                      

                             圖5:數學表達式樹節點

文法 Grammars

解析基於文檔依據的語法規則——文檔的語言或格式。每種可被解析的格式必須具有由詞彙及語法規則組成的特定的文法,稱爲上下文無關文法。人類語言不具有這一特性,因此不能被一般的解析技術所解析。

解析器-詞法分析器 Parser-Lexer combination

解析可以分爲兩個子過程——語法分析及詞法分析

詞法分析就是將輸入分解爲符號,符號是語言的詞彙表——基本有效單元的集合。對於人類語言來說,它相當於我們字典中出現的所有單詞。

語法分析指對語言應用語法規則。

解析器一般將工作分配給兩個組件——詞法分析器(有時也叫分詞器)負責將輸入分解爲合法的符號,解析器則根據語言的語法規則分析文檔結構,從而構建解析樹,詞法分析器知道怎麼跳過空白和換行之類的無關字符。

圖6:從源文檔到解析樹

解析過程是迭代的,解析器從詞法分析器處取道一個新的符號,並試着用這個符號匹配一條語法規則,如果匹配了一條規則,這個符號對應的節點將被添加到解析樹上,然後解析器請求另一個符號。如果沒有匹配到規則,解析器將在內部保存該符號,並從詞法分析器取下一個符號,直到所有內部保存的符號能夠匹配一項語法規則。如果最終沒有找到匹配的規則,解析器將拋出一個異常,這意味着文檔無效或是包含語法錯誤。

轉換 Translation

很多時候,解析樹並不是最終結果。解析一般在轉換中使用——將輸入文檔轉換爲另一種格式。編譯就是個例子,編譯器在將一段源碼編譯爲機器碼的時候,先將源碼解析爲解析樹,然後將該樹轉換爲一個機器碼文檔。

圖7:編譯流程

解析實例 Parsing example

圖5中,我們從一個數學表達式構建了一個解析樹,這裏定義一個簡單的數學語言來看下解析過程。

詞彙表:我們的語言包括整數、加號及減號。

語法:

1.     該語言的語法基本單元包括表達式、term及操作符

2.     該語言可以包括多個表達式

3.     一個表達式定義爲兩個term通過一個操作符連接

4.     操作符可以是加號或減號

5.     term可以是一個整數或一個表達式

現在來分析一下“2+3-1”這個輸入

第一個匹配規則的子字符串是“2”,根據規則5,它是一個term,第二個匹配的是“2+3”,它符合第2條規則——一個操作符連接兩個term,下一次匹配發生在輸入的結束處。“2+3-1”是一個表達式,因爲我們已經知道“2+3”是一個term,所以我們有了一個term緊跟着一個操作符及另一個term。“2++”將不會匹配任何規則,因此是一個無效輸入。

詞彙表及語法的定義

詞彙表通常利用正則表達式來定義。

例如上面的語言可以定義爲:

INTEGER:0|[1-9][0-9]*

PLUS:+

MINUS:-

正如看到的,這裏用正則表達式定義整數。

語法通常用BNF格式定義,我們的語言可以定義爲:

expression := term operation term

operation := PLUS | MINUS

term := INTEGER | expression

如果一個語言的文法是上下文無關的,則它可以用正則解析器來解析。對上下文無關文法的一個直觀的定義是,該文法可以用BNF來完整的表達。可查看http://en.wikipedia.org/wiki/Context-free_grammar

解析器類型 Types of parsers

有兩種基本的解析器——自頂向下解析及自底向上解析。比較直觀的解釋是,自頂向下解析,查看語法的最高層結構並試着匹配其中一個;自底向上解析則從輸入開始,逐步將其轉換爲語法規則,從底層規則開始直到匹配高層規則。

來看一下這兩種解析器如何解析上面的例子:

自頂向下解析器從最高層規則開始——它先識別出“2+3“,將其視爲一個表達式,然後識別出”2+3-1“爲一個表達式(識別表達式的過程中匹配了其他規則,但出發點是最高層規則)。

自底向上解析會掃描輸入直到匹配了一條規則,然後用該規則取代匹配的輸入,直到解析完所有輸入。部分匹配的表達式被放置在解析堆棧中。

Stack

Input

 

2 + 3 – 1

term

+ 3 - 1

term operation

3 – 1

expression

- 1

expression operation

1

expression

 

自底向上解析器稱爲shift reduce 解析器,因爲輸入向右移動(想象一個指針首先指向輸入開始處,並向右移動),並逐漸簡化爲語法規則。

自動化解析 Generating parsers automatically

解析器生成器這個工具可以自動生成解析器,只需要指定語言的文法——詞彙表及語法規則,它就可以生成一個解析器。創建一個解析器需要對解析有深入的理解,而且手動的創建一個由較好性能的解析器並不容易,所以解析生成器很有用。Webkit使用兩個知名的解析生成器——用於創建語法分析器的Flex及創建解析器的Bison(你可能接觸過Lex和Yacc)。Flex的輸入是一個包含了符號定義的正則表達式,Bison的輸入是用BNF格式表示的語法規則。

HTML解析器 HTML Parser

HTML解析器的工作是將html標識解析爲解析樹。

HTML文法定義 The HTML grammar definition

W3C組織制定規範定義了HTML的詞彙表和語法。

非上下文無關文法 Not a context free grammar

正如在解析簡介中提到的,上下文無關文法的語法可以用類似BNF的格式來定義。

不幸的是,所有的傳統解析方式都不適用於html(當然我提出它們並不只是因爲好玩,它們將用來解析css和js),html不能簡單的用解析所需的上下文無關文法來定義。

Html 有一個正式的格式定義——DTD(Document Type Definition 文檔類型定義)——但它並不是上下文無關文法,html更接近於xml,現在有很多可用的xml解析器,html有個xml的變體——xhtml,它們間的不同在於,html更寬容,它允許忽略一些特定標籤,有時可以省略開始或結束標籤。總的來說,它是一種soft語法,不像xml呆板、固執。

顯然,這個看起來很小的差異卻帶來了很大的不同。一方面,這是html流行的原因——它的寬容使web開發人員的工作更加輕鬆,但另一方面,這也使很難去寫一個格式化的文法。所以,html的解析並不簡單,它既不能用傳統的解析器解析,也不能用xml解析器解析。

HTML DTD

Html適用DTD格式進行定義,這一格式是用於定義SGML家族的語言,包括了對所有允許元素及它們的屬性和層次關係的定義。正如前面提到的,htmlDTD並沒有生成一種上下文無關文法。

DTD有一些變種,標準模式只遵守規範,而其他模式則包含了對瀏覽器過去所使用標籤的支持,這麼做是爲了兼容以前內容。最新的標準DTD在http://www.w3.org/TR/html4/strict.dtd

DOM

輸出的樹,也就是解析樹,是由DOM元素及屬性節點組成的。DOM是文檔對象模型的縮寫,它是html文檔的對象表示,作爲html元素的外部接口供js等調用。

樹的根是“document”對象。

DOM和標籤基本是一一對應的關係,例如,如下的標籤:

<html>

   <body>

       <p>

          Hello DOM

       </p>

       <div><img src=”example.png” /></div>

   </body>

</html>

將會被轉換爲下面的DOM樹:

圖8:示例標籤對應的DOM樹

和html一樣,DOM的規範也是由W3C組織制定的。訪問http://www.w3.org/DOM/DOMTR,這是使用文檔的一般規範。一個模型描述一種特定的html元素,可以在http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.htm 查看html定義。

這裏所謂的樹包含了DOM節點是說樹是由實現了DOM接口的元素構建而成的,瀏覽器使用已被瀏覽器內部使用的其他屬性的具體實現。

解析算法 The parsing algorithm

正如前面章節中討論的,hmtl不能被一般的自頂向下或自底向上的解析器所解析。

原因是:

1.     這門語言本身的寬容特性

2.     瀏覽器對一些常見的非法html有容錯機制

3.     解析過程是往復的,通常源碼不會在解析過程中發生改變,但在html中,腳本標籤包含的“document.write ”可能添加標籤,這說明在解析過程中實際上修改了輸入

不能使用正則解析技術,瀏覽器爲html定製了專屬的解析器。

Html5規範中描述了這個解析算法,算法包括兩個階段——符號化及構建樹。

符號化是詞法分析的過程,將輸入解析爲符號,html的符號包括開始標籤、結束標籤、屬性名及屬性值。

符號識別器識別出符號後,將其傳遞給樹構建器,並讀取下一個字符,以識別下一個符號,這樣直到處理完所有輸入。

圖9:HTML解析流程

符號識別算法 The tokenization algorithm

算法輸出html符號,該算法用狀態機表示。每次讀取輸入流中的一個或多個字符,並根據這些字符轉移到下一個狀態,當前的符號狀態及構建樹狀態共同影響結果,這意味着,讀取同樣的字符,可能因爲當前狀態的不同,得到不同的結果以進入下一個正確的狀態。

這個算法很複雜,這裏用一個簡單的例子來解釋這個原理。

基本示例——符號化下面的html:

<html>

     <body>

         Helloworld

     </body>

</html>

初始狀態爲“Data State”,當遇到“<”字符,狀態變爲“Tag open state”,讀取一個a-z的字符將產生一個開始標籤符號,狀態相應變爲“Tag name state”,一直保持這個狀態直到讀取到“>”,每個字符都附加到這個符號名上,例子中創建的是一個html符號。

當讀取到“>”,當前的符號就完成了,此時,狀態回到“Data state”,“<body>”重複這一處理過程。到這裏,html和body標籤都識別出來了。現在,回到“Data state”,讀取“Hello world”中的字符“H”將創建並識別出一個字符符號,這裏會爲“Hello world”中的每個字符生成一個字符符號。

這樣直到遇到“</body>”中的“<”。現在,又回到了“Tag open state”,讀取下一個字符“/”將創建一個閉合標籤符號,並且狀態轉移到“Tag name state”,還是保持這一狀態,直到遇到“>”。然後,產生一個新的標籤符號並回到“Data state”。後面的“</html>”將和“</body>”一樣處理。

圖10:符號化示例輸入

樹的構建算法 Tree construction algorithm

在樹的構建階段,將修改以Document爲根的DOM樹,將元素附加到樹上。每個由符號識別器識別生成的節點將會被樹構造器進行處理,規範中定義了每個符號相對應的Dom元素,對應的Dom元素將會被創建。這些元素除了會被添加到Dom樹上,還將被添加到開放元素堆棧中。這個堆棧用來糾正嵌套的未匹配和未閉合標籤,這個算法也是用狀態機來描述,所有的狀態採用插入模式。

來看一下示例中樹的創建過程:

<html>

     <body>

         Helloworld

     </body>

</html>

構建樹這一階段的輸入是符號識別階段生成的符號序列。

首先是“initial mode”,接收到html符號後將轉換爲“before html”模式,在這個模式中對這個符號進行再處理。此時,創建了一個HTMLHtmlElement元素,並將其附加到根Document對象上。

狀態此時變爲“before head”,接收到body符號時,即使這裏沒有head符號,也將自動創建一個HTMLHeadElement元素並附加到樹上。

現在,轉到“in head”模式,然後是“after head”。到這裏,body符號會被再次處理,將創建一個HTMLBodyElement並插入到樹中,同時,轉移到“in body”模式。

然後,接收到字符串“Hello world”的字符符號,第一個字符將導致創建並插入一個text節點,其他字符將附加到該節點。

接收到body結束符號時,轉移到“afterbody”模式,接着接收到html結束符號,這個符號意味着轉移到了“after after body”模式,當接收到文件結束符時,整個解析過程結束。

圖11:示例html樹的構建過程

解析結束時的處理 Action when the parsing is finished

在這個階段,瀏覽器將文檔標記爲可交互的,並開始解析處於延時模式中的腳本——這些腳本在文檔解析後執行。

文檔狀態將被設置爲完成,同時觸發一個load事件。

Html5規範中有符號化及構建樹的完整算法(http://www.w3.org/TR/html5/syntax.html#html-parser)。

瀏覽器容錯 Browsers error tolerance

你從來不會在一個html頁面上看到“無效語法”這樣的錯誤,瀏覽器修復了無效內容並繼續工作。

以下面這段html爲例:

<html>

 <mytag>

 </mytag>

 <div>

  <p>

 </div>

     Really lousy HTML

 </p>

</html>

這段html違反了很多規則(mytag不是合法的標籤,p及div錯誤的嵌套等等),但是瀏覽器仍然可以沒有任何怨言的繼續顯示,它在解析的過程中修復了html作者的錯誤。

瀏覽器都具有錯誤處理的能力,但是,另人驚訝的是,這並不是html最新規範的內容,就像書籤及前進後退按鈕一樣,它只是瀏覽器長期發展的結果。一些比較知名的非法html結構,在許多站點中出現過,瀏覽器都試着以一種和其他瀏覽器一致的方式去修復。

Html5規範定義了這方面的需求,webkit在html解析類開始部分的註釋中做了很好的總結。

解析器將符號化的輸入解析爲文檔並創建文檔,但不幸的是,我們必須處理很多沒有很好格式化的html文檔,至少要小心下面幾種錯誤情況。

1.     在未閉合的標籤中添加明確禁止的元素。這種情況下,應該先將前一標籤閉合

2.     不能直接添加元素。有些人在寫文檔的時候會忘了中間一些標籤(或者中間標籤是可選的),比如HTML HEAD BODY TR TD LI等

3.     想在一個行內元素中添加塊狀元素。關閉所有的行內元素,直到下一個更高的塊狀元素

4.     如果這些都不行,就閉合當前標籤直到可以添加該元素。

下面來看一些webkit容錯的例子:

</br>替代<br>

一些網站使用</br>替代<br>,爲了兼容IE和Firefox,webkit將其看作<br>。

代碼:

if (t->isCloseTag(brTag) &&m_document->inCompatMode()) {

    reportError(MalformedBRError);

    t->beginTag = true;

}

Note-這裏的錯誤處理在內部進行,用戶看不到。

迷路的表格

這指一個表格嵌套在另一個表格中,但不在它的某個單元格內。

比如下面這個例子:

<table>

     <table>

         <tr><td>innertable</td></tr>

        </table>

     <tr><td>outertable</td></tr>

</table>

webkit將會將嵌套的表格變爲兩個兄弟表格:

<table>

     <tr><td>outertable</td></tr>

</table>

<table>

     <tr><td>innertable</td></tr>

 </table>

代碼:

if (m_inStrayTableContent && localName ==tableTag)

       popBlock(tableTag);

webkit使用堆棧存放當前的元素內容,它將從外部表格的堆棧中彈出內部的表格,則它們變爲了兄弟表格。

嵌套的表單元素

用戶將一個表單嵌套到另一個表單中,則第二個表單將被忽略。

代碼:

if (!m_currentFormElement) {

       m_currentFormElement = new HTMLFormElement(formTag,    m_document);

}

太深的標籤繼承

www.liceo.edu.mx是一個由嵌套層次的站點的例子,最多隻允許20個相同類型的標籤嵌套,多出來的將被忽略。

代碼:

bool HTMLParser::allowNestedRedundantTag(const AtomicString&tagName)

{

 

unsigned i = 0;

for (HTMLStackElem* curr = m_blockStack;

         i< cMaxRedundantTagDepth && curr && curr->tagName ==tagName;

     curr =curr->next, i++) { }

return i != cMaxRedundantTagDepth;

}

放錯了地方的html、body閉合標籤

又一次不言自明。

支持不完整的html。我們從來不閉合body,因爲一些愚蠢的網頁總是在還未真正結束時就閉合它。我們依賴調用end方法去執行關閉的處理。

代碼:

if (t->tagName == htmlTag || t->tagName ==bodyTag )

       return;

所以,web開發者要小心了,除非你想成爲webkit容錯代碼的範例,否則還是寫格式良好的html吧。

CSS解析 CSS parsing

還記得簡介中提到的解析的概念嗎,不同於html,css屬於上下文無關文法,可以用前面所描述的解析器來解析。Css規範定義了css的詞法及語法文法。

看一些例子:

每個符號都由正則表達式定義了詞法文法(詞彙表):

comment       ///*[^*]*/*+([^/*][^*]*/*+)*//

num      [0-9]+|[0-9]*"."[0-9]+

nonascii [/200-/377]

nmstart       [_a-z]|{nonascii}|{escape}

nmchar        [_a-z0-9-]|{nonascii}|{escape}

name     {nmchar}+

ident         {nmstart}{nmchar}*

“ident”是識別器的縮寫,相當於一個class名,“name”是一個元素id(用“#”引用)。

語法用BNF進行描述:

ruleset

  :selector [ ',' S* selector ]*

    '{' S*declaration [ ';' S* declaration ]* '}' S*

  ;

selector

  :simple_selector [ combinator selector | S+ [ combinator selector ] ]

  ;

simple_selector

  :element_name [ HASH | class | attrib | pseudo ]*

  | [ HASH| class | attrib | pseudo ]+

  ;

class

  : '.'IDENT

  ;

element_name

  : IDENT |'*'

  ;

attrib

  : '[' S*IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*

    [ IDENT| STRING ] S* ] ']'

  ;

pseudo

  : ':' [IDENT | FUNCTION S* [IDENT S*] ')' ]

  ;

說明:一個規則集合有這樣的結構

div.error , a.error {

     color:red;

     font-weight:bold;

}

div.error和a.error時選擇器,大括號中的內容包含了這條規則集合中的規則,這個結構在下面的定義中正式的定義了:

ruleset

  :selector [ ',' S* selector ]*

    '{' S*declaration [ ';' S* declaration ]* '}' S*

  ;

這說明,一個規則集合具有一個或是可選個數的多個選擇器,這些選擇器以逗號和空格(S表示空格)進行分隔。每個規則集合包含大括號及大括號中的一條或多條以分號隔開的聲明。聲明和選擇器在後面進行定義。

Webkit CSS 解析器 Webkit CSS parser

Webkit使用Flex和Bison解析生成器從CSS語法文件中自動生成解析器。回憶一下解析器的介紹,Bison創建一個自底向上的解析器,Firefox使用自頂向下解析器。它們都是將每個css文件解析爲樣式表對象,每個對象包含css規則,css規則對象包含選擇器和聲明對象,以及其他一些符合css語法的對象。

圖12:解析css

腳本解析 Parsing scripts

本章將介紹JavaScript

處理腳本及樣式表的順序 Theorder of processing scripts and style sheets

腳本

web的模式是同步的,開發者希望解析到一個script標籤時立即解析執行腳本,並阻塞文檔的解析直到腳本執行完。如果腳本是外引的,則網絡必須先請求到這個資源——這個過程也是同步的,會阻塞文檔的解析直到資源被請求到。這個模式保持了很多年,並且在html4及html5中都特別指定了。開發者可以將腳本標識爲defer,以使其不阻塞文檔解析,並在文檔解析結束後執行。Html5增加了標記腳本爲異步的選項,以使腳本的解析執行使用另一個線程。

預解析Speculative parsing

Webkit和Firefox都做了這個優化,當執行腳本時,另一個線程解析剩下的文檔,並加載後面需要通過網絡加載的資源。這種方式可以使資源並行加載從而使整體速度更快。需要注意的是,預解析並不改變Dom樹,它將這個工作留給主解析過程,自己只解析外部資源的引用,比如外部腳本、樣式表及圖片。

樣式表 Style sheets

樣式表採用另一種不同的模式。理論上,既然樣式表不改變Dom樹,也就沒有必要停下文檔的解析等待它們,然而,存在一個問題,腳本可能在文檔的解析過程中請求樣式信息,如果樣式還沒有加載和解析,腳本將得到錯誤的值,顯然這將會導致很多問題,這看起來是個邊緣情況,但確實很常見。Firefox在存在樣式表還在加載和解析時阻塞所有的腳本,而chrome只在當腳本試圖訪問某些可能被未加載的樣式表所影響的特定的樣式屬性時才阻塞這些腳本。

渲染樹的構造 Render tree construction

當Dom樹構建完成時,瀏覽器開始構建另一棵樹——渲染樹。渲染樹由元素顯示序列中的可見元素組成,它是文檔的可視化表示,構建這棵樹是爲了以正確的順序繪製文檔內容。

Firefox將渲染樹中的元素稱爲frames,webkit則用renderer或渲染對象來描述這些元素。

一個渲染對象直到怎麼佈局及繪製自己及它的children。

RenderObject是Webkit的渲染對象基類,它的定義如下:

class RenderObject{

     virtualvoid layout();

     virtualvoid paint(PaintInfo);

     virtualvoid rect repaintRect();

     Node*node;  //the DOM node

     RenderStyle*style;  // the computed style

     RenderLayer*containgLayer; //the containing z-index layer

}

每個渲染對象用一個和該節點的css盒模型相對應的矩形區域來表示,正如css2所描述的那樣,它包含諸如寬、高和位置之類的幾何信息。盒模型的類型受該節點相關的display樣式屬性的影響(參考樣式計算章節)。下面的webkit代碼說明了如何根據display屬性決定某個節點創建何種類型的渲染對象。

RenderObject* RenderObject::createObject(Node*node, RenderStyle* style)

{

   Document* doc = node->document();

   RenderArena* arena = doc->renderArena();

    ...

   RenderObject* o = 0;

 

    switch(style->display()) {

       case NONE:

           break;

       case INLINE:

           o = new (arena) RenderInline(node);

           break;

       case BLOCK:

           o = new (arena) RenderBlock(node);

           break;

       case INLINE_BLOCK:

           o = new (arena) RenderBlock(node);

           break;

       case LIST_ITEM:

           o = new (arena) RenderListItem(node);

           break;

       ...

    }

 

    returno;

}

 

元素的類型也需要考慮,例如,表單控件和表格帶有特殊的框架。

在webkit中,如果一個元素想創建一個特殊的渲染對象,它需要複寫“createRenderer”方法,使渲染對象指向不包含幾何信息的樣式對象。

渲染樹和Dom樹的關係 The render tree relation to the DOMtree

渲染對象和Dom元素相對應,但這種對應關係不是一對一的,不可見的Dom元素不會被插入渲染樹,例如head元素。另外,display屬性爲none的元素也不會在渲染樹中出現(visibility屬性爲hidden的元素將出現在渲染樹中)。

還有一些Dom元素對應幾個可見對象,它們一般是一些具有複雜結構的元素,無法用一個矩形來描述。例如,select元素有三個渲染對象——一個顯示區域、一個下拉列表及一個按鈕。同樣,當文本因爲寬度不夠而折行時,新行將作爲額外的渲染元素被添加。另一個多個渲染對象的例子是不規範的html,根據css規範,一個行內元素只能僅包含行內元素或僅包含塊狀元素,在存在混合內容時,將會創建匿名的塊狀渲染對象包裹住行內元素。

一些渲染對象和所對應的Dom節點不在樹上相同的位置,例如,浮動和絕對定位的元素在文本流之外,在兩棵樹上的位置不同,渲染樹上標識出真實的結構,並用一個佔位結構標識出它們原來的位置。

圖12:渲染樹及對應的Dom樹

創建樹的流程 The flow of constructing the tree

Firefox中,表述爲一個監聽Dom更新的監聽器,將frame的創建委派給Frame Constructor,這個構建器計算樣式(參看樣式計算)並創建一個frame。

Webkit中,計算樣式並生成渲染對象的過程稱爲attachment,每個Dom節點有一個attach方法,attachment的過程是同步的,調用新節點的attach方法將節點插入到Dom樹中。

處理html和body標籤將構建渲染樹的根,這個根渲染對象對應被css規範稱爲containing block的元素——包含了其他所有塊元素的頂級塊元素。它的大小就是viewport——瀏覽器窗口的顯示區域,Firefox稱它爲viewPortFrame,webkit稱爲RenderView,這個就是文檔所指向的渲染對象,樹中其他的部分都將作爲一個插入的Dom節點被創建。

樣式計算 Style Computation

創建渲染樹需要計算出每個渲染對象的可視屬性,這可以通過計算每個元素的樣式屬性得到。

樣式包括各種來源的樣式表,行內樣式元素及html中的可視化屬性(例如bgcolor),可視化屬性轉化爲css樣式屬性。

樣式表來源於瀏覽器默認樣式表,及頁面作者和用戶提供的樣式表——有些樣式是瀏覽器用戶提供的(瀏覽器允許用戶定義喜歡的樣式,例如,在Firefox中,可以通過在Firefox Profile目錄下放置樣式表實現)。

計算樣式的一些困難:

1.     樣式數據是非常大的結構,保存大量的樣式屬性會帶來內存問題

2.     如果不進行優化,找到每個元素匹配的規則會導致性能問題,爲每個元素查找匹配的規則都需要遍歷整個規則表,這個過程有很大的工作量。選擇符可能有複雜的結構,匹配過程如果沿着一條開始看似正確,後來卻被證明是無用的路徑,則必須去嘗試另一條路徑。

例如,下面這個複雜選擇符

div div div div{…}

這意味着規則應用到三個div的後代div元素,選擇樹上一條特定的路徑去檢查,這可能需要遍歷節點樹,最後卻發現它只是兩個div的後代,並不使用該規則,然後則需要沿着另一條路徑去嘗試

3.     應用規則涉及非常複雜的級聯,它們定義了規則的層次

我們來看一下瀏覽器如何處理這些問題:

共享樣式數據

webkit節點引用樣式對象(渲染樣式),某些情況下,這些對象可以被節點間共享,這些節點需要是兄弟或是表兄弟節點,並且:

1.     這些元素必須處於相同的鼠標狀態(比如不能一個處於hover,而另一個不是)

2.     不能有元素具有id

3.     標籤名必須匹配

4.     class屬性必須匹配

5.     對應的屬性必須相同

6.     鏈接狀態必須匹配

7.     焦點狀態必須匹配

8.     不能有元素被屬性選擇器影響

9.     元素不能有行內樣式屬性

10.  不能有生效的兄弟選擇器,webcore在任何兄弟選擇器相遇時只是簡單的拋出一個全局轉換,並且在它們顯示時使整個文檔的樣式共享失效,這些包括+選擇器和類似:first-child和:last-child這樣的選擇器。

Firefox規則樹 Firefox rule tree

Firefox用兩個樹用來簡化樣式計算-規則樹和樣式上下文樹,webkit也有樣式對象,但它們並沒有存儲在類似樣式上下文樹這樣的樹中,只是由Dom節點指向其相關的樣式。

圖14:Firefox樣式上下文樹

樣式上下文包含最終值,這些值是通過以正確順序應用所有匹配的規則,並將它們由邏輯值轉換爲具體的值,例如,如果邏輯值爲屏幕的百分比,則通過計算將其轉化爲絕對單位。樣式樹的使用確實很巧妙,它使得在節點中共享的這些值不需要被多次計算,同時也節省了存儲空間。

所有匹配的規則都存儲在規則樹中,一條路徑中的底層節點擁有最高的優先級,這棵樹包含了所找到的所有規則匹配的路徑(譯註:可以取巧理解爲每條路徑對應一個節點,路徑上包含了該節點所匹配的所有規則)。規則樹並不是一開始就爲所有節點進行計算,而是在某個節點需要計算樣式時,才進行相應的計算並將計算後的路徑添加到樹中。

我們將樹上的路徑看成辭典中的單詞,假如已經計算出瞭如下的規則樹:

假如需要爲內容樹中的另一個節點匹配規則,現在知道匹配的規則(以正確的順序)爲B-E-I,因爲我們已經計算出了路徑A-B-E-I-L,所以樹上已經存在了這條路徑,剩下的工作就很少了。

現在來看一下樹如何保存。

結構化

樣式上下文按結構劃分,這些結構包括類似border或color這樣的特定分類的樣式信息。一個結構中的所有特性不是繼承的就是非繼承的,對繼承的特性,除非元素自身有定義,否則就從它的parent繼承。非繼承的特性(稱爲reset特性)如果沒有定義,則使用默認的值。

樣式上下文樹緩存完整的結構(包括計算後的值),這樣,如果底層節點沒有爲一個結構提供定義,則使用上層節點緩存的結構。

使用規則樹計算樣式上下文

當爲一個特定的元素計算樣式時,首先計算出規則樹中的一條路徑,或是使用已經存在的一條,然後使用路徑中的規則去填充新的樣式上下文,從樣式的底層節點開始,它具有最高優先級(通常是最特定的選擇器),遍歷規則樹,直到填滿結構。如果在那個規則節點沒有定義所需的結構規則,則沿着路徑向上,直到找到該結構規則。

如果最終沒有找到該結構的任何規則定義,那麼如果這個結構是繼承型的,則找到其在內容樹中的parent的結構,這種情況下,我們也成功的共享了結構;如果這個結構是reset型的,則使用默認的值。

如果特定的節點添加了值,那麼需要做一些額外的計算以將其轉換爲實際值,然後在樹上的節點緩存該值,使它的children可以使用。

當一個元素和它的一個兄弟元素指向同一個樹節點時,完整的樣式上下文可以被它們共享。

來看一個例子:假設有下面這段html

<html>

  <body>

    <div class="err"id="div1">

       <p>this is a

         <spanclass="big"> big error </span>

            this is also a

         <spanclass="big"> very  big  error</span>

            error

       </p>

     </div>

     <divclass="err" id="div2">another error</div>

   </body>

</html>

以及下面這些規則

1.   div{margin:5px;color:black}

2.   .err{color:red}

3.   .big{margin-top:3px}

4.   divspan {margin-bottom:4px}

5.   #div1{color:blue}

6.   #div2{color:green}

簡化下問題,我們只填充兩個結構——color和margin,color結構只包含一個成員-顏色,margin結構包含四邊。

生成的規則樹如下(節點名:指向的規則)

上下文樹如下(節點名:指向的規則節點)

 

假設我們解析html,遇到第二個div標籤,我們需要爲這個節點創建樣式上下文,並填充它的樣式結構。

我們進行規則匹配,找到這個div匹配的規則爲1、2、6,我們發現規則樹上已經存在了一條我們可以使用的路徑1、2,我們只需爲規則6新增一個節點添加到下面(就是規則樹中的F)。

然後創建一個樣式上下文並將其放到上下文樹中,新的樣式上下文將指向規則樹中的節點F。

現在我們需要填充這個樣式上下文,先從填充margin結構開始,既然最後一個規則節點沒有添加margin結構,沿着路徑向上,直到找到緩存的前面插入節點計算出的結構,我們發現B是最近的指定margin值的節點。因爲已經有了color結構的定義,所以不能使用緩存的結構,既然color只有一個屬性,也就不需要沿着路徑向上填充其他屬性。計算出最終值(將字符串轉換爲RGB等),並緩存計算後的結構。

第二個span元素更簡單,進行規則匹配後發現它指向規則G,和前一個span一樣,既然有兄弟節點指向同一個節點,就可以共享完整的樣式上下文,只需指向前一個span的上下文。

因爲結構中包含繼承自parent的規則,上下文樹做了緩存(color特性是繼承來的,但Firefox將其視爲reset並在規則樹中緩存)。

例如,如果我們爲一個paragraph的文字添加規則:

p {font-family:Verdana;fontsize:10px;font-weight:bold}

那麼這個p在內容樹中的子節點div,會共享和它parent一樣的font結構,這種情況發生在沒有爲這個div指定font規則時。

Webkit中,並沒有規則樹,匹配的聲明會被遍歷四次,先是應用非important的高優先級屬性(之所以先應用這些屬性,是因爲其他的依賴於它們-比如display),其次是高優先級important的,接着是一般優先級非important的,最後是一般優先級important的規則。這樣,出現多次的屬性將被按照正確的級聯順序進行處理,最後一個生效。

總結一下,共享樣式對象(結構中完整或部分內容)解決了問題1和3,Firefox的規則樹幫助以正確的順序應用規則。

對規則進行處理以簡化匹配過程

樣式規則有幾個來源:

·     外部樣式表或style標籤內的css規則

·     行內樣式屬性

·     html可視化屬性(映射爲相應的樣式規則)

後面兩個很容易匹配到元素,因爲它們所擁有的樣式屬性和html屬性可以將元素作爲key進行映射。

就像前面問題2所提到的,css的規則匹配可能很狡猾,爲了解決這個問題,可以先對規則進行處理,以使其更容易被訪問。

解析完樣式表之後,規則會根據選擇符添加一些hash映射,映射可以是根據id、class、標籤名或是任何不屬於這些分類的綜合映射。如果選擇符爲id,規則將被添加到id映射,如果是class,則被添加到class映射,等等。

這個處理是匹配規則更容易,不需要查看每個聲明,我們能從映射中找到一個元素的相關規則,這個優化使在進行規則匹配時減少了95+%的工作量。

來看下面的樣式規則:

p.error {color:red}

#messageDiv {height:50px}

div {margin:5px}

第一條規則將被插入class映射,第二條插入id映射,第三條是標籤映射。

下面這個html片段:

<p class="error">an erroroccurred </p>

<div id=" messageDiv">this is amessage</div>

我們首先找到p元素對應的規則,class映射將包含一個“error”的key,找到p.error的規則,div在id映射和標籤映射中都有相關的規則,剩下的工作就是找出這些由key對應的規則中哪些確實是正確匹配的。

例如,如果div的規則是

table div {margin:5px}

這也是標籤映射產生的,因爲key是最右邊的選擇符,但它並不匹配這裏的div元素,因爲這裏的div沒有table祖先。

Webkit和Firefox都會做這個處理。

以正確的級聯順序應用規則

樣式對象擁有對應所有可見屬性的屬性,如果特性沒有被任何匹配的規則所定義,那麼一些特性可以從parent的樣式對象中繼承,另外一些使用默認值。

這個問題的產生是因爲存在不止一處的定義,這裏用級聯順序解決這個問題。

樣式表的級聯順序

一個樣式屬性的聲明可能在幾個樣式表中出現,或是在一個樣式表中出現多次,因此,應用規則的順序至關重要,這個順序就是級聯順序。根據css2的規範,級聯順序爲(從低到高):

1.     瀏覽器聲明

2.     用戶聲明

3.     作者的一般聲明

4.     作者的important聲明

5.     用戶important聲明

瀏覽器聲明是最不重要的,用戶只有在聲明被標記爲important時纔會覆蓋作者的聲明。具有同等級別的聲明將根據specifity以及它們被定義時的順序進行排序。Html可視化屬性將被轉換爲匹配的css聲明,它們被視爲最低優先級的作者規則。

Specifity

Css2規範中定義的選擇符specifity如下:

·     如果聲明來自style屬性,而不是一個選擇器的規則,則計1,否則計0(=a)

·     計算選擇器中id屬性的數量(=b)

·     計算選擇器中class及僞類的數量(=c)

·     計算選擇器中元素名及僞元素的數量(=d)

連接a-b-c-d四個數量(用一個大基數的計算系統)將得到specifity。這裏使用的基數由分類中最高的基數定義。例如,如果a爲14,可以使用16進制。不同情況下,a爲17時,則需要使用阿拉伯數字17作爲基數,這種情況可能在這個選擇符時發生html body div div …(選擇符中有17個標籤,一般不太可能)。

一些例子:

*            {}  /* a=0 b=0 c=0 d=0 ->specificity = 0,0,0,0 */

 li            {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */

 li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity =0,0,0,2 */

 ul li         {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */

 ulol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity =0,0,0,3 */

 h1 +*[rel=up]{}  /* a=0 b=0 c=1 d=1 ->specificity = 0,0,1,1 */

 ul ol li.red  {}  /*a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */

 li.red.level {}  /* a=0 b=0 c=2 d=1 ->specificity = 0,0,2,1 */

 #x34y         {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */

 style=""          /* a=1 b=0 c=0 d=0 -> specificity= 1,0,0,0 */

規則排序

規則匹配後,需要根據級聯順序對規則進行排序,webkit先將小列表用冒泡排序,再將它們合併爲一個大列表,webkit通過爲規則複寫“>”操作來執行排序:

static bool operator >(CSSRuleData& r1,CSSRuleData& r2)

{

    intspec1 = r1.selector()->specificity();

    intspec2 = r2.selector()->specificity();

    return(spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;

}

逐步處理 Gradual process

webkit使用一個標誌位標識所有頂層樣式表都已加載,如果在attch時樣式沒有完全加載,則放置佔位符,並在文檔中標記,一旦樣式表完成加載就重新進行計算。

佈局 Layout

當渲染對象被創建並添加到樹中,它們並沒有位置和大小,計算這些值的過程稱爲layout或reflow。

Html使用基於流的佈局模型,意味着大部分時間,可以以單一的途徑進行幾何計算。流中靠後的元素並不會影響前面元素的幾何特性,所以佈局可以在文檔中從右向左、自上而下的進行。也存在一些例外,比如html tables。

座標系統相對於根frame,使用top和left座標。

佈局是一個遞歸的過程,由根渲染對象開始,它對應html文檔元素,佈局繼續遞歸的通過一些或所有的frame層級,爲每個需要幾何信息的渲染對象進行計算。

根渲染對象的位置是0,0,它的大小是viewport-瀏覽器窗口的可見部分。

所有的渲染對象都有一個layout或reflow方法,每個渲染對象調用需要佈局的children的layout方法。

Dirty bit 系統

爲了不因爲每個小變化都全部重新佈局,瀏覽器使用一個dirty bit系統,一個渲染對象發生了變化或是被添加了,就標記它及它的children爲dirty-需要layout。存在兩個標識-dirty及children are dirty,children are dirty說明即使這個渲染對象可能沒問題,但它至少有一個child需要layout。

全局和增量 layout

當layout在整棵渲染樹觸發時,稱爲全局layout,這可能在下面這些情況下發生:

1.     一個全局的樣式改變影響所有的渲染對象,比如字號的改變

2.     窗口resize

layout也可以是增量的,這樣只有標誌爲dirty的渲染對象會重新佈局(也將導致一些額外的佈局)。增量 layout會在渲染對象dirty時異步觸發,例如,當網絡接收到新的內容並添加到Dom樹後,新的渲染對象會添加到渲染樹中。

圖20:增量 layout

異步和同步layout

增量layout的過程是異步的,Firefox爲增量layout生成了reflow隊列,以及一個調度執行這些批處理命令。Webkit也有一個計時器用來執行增量layout-遍歷樹,爲dirty狀態的渲染對象重新佈局。

另外,當腳本請求樣式信息時,例如“offsetHeight”,會同步的觸發增量佈局。

全局的layout一般都是同步觸發。

有些時候,layout會被作爲一個初始layout之後的回調,比如滑動條的滑動。

優化

當一個layout因爲resize或是渲染位置改變(並不是大小改變)而觸發時,渲染對象的大小將會從緩存中讀取,而不會重新計算。

一般情況下,如果只有子樹發生改變,則layout並不從根開始。這種情況發生在,變化發生在元素自身並且不影響它周圍元素,例如,將文本插入文本域(否則,每次擊鍵都將觸發從根開始的重排)。

layout過程

layout一般有下面這幾個部分:

1.     parent渲染對象決定它的寬度

2.     parent渲染對象讀取chilidren,並:

1.     放置child渲染對象(設置它的x和y)

2.     在需要時(它們當前爲dirty或是處於全局layout或者其他原因)調用child渲染對象的layout,這將計算child的高度

3.     parent渲染對象使用child渲染對象的累積高度,以及margin和padding的高度來設置自己的高度-這將被parent渲染對象的parent使用

4.     將dirty標識設置爲false

Firefox使用一個“state”對象(nsHTMLReflowState)做爲參數去佈局(firefox稱爲reflow),state包含parent的寬度及其他內容。

Firefox佈局的輸出是一個“metrics”對象(nsHTMLReflowMetrics)。它包括渲染對象計算出的高度。

寬度計算

渲染對象的寬度使用容器的寬度、渲染對象樣式中的寬度及margin、border進行計算。例如,下面這個div的寬度:

<div style="width:30%"/>

webkit中寬度的計算過程是(RenderBox類的calcWidth方法):

·     容器的寬度是容器的可用寬度和0中的最大值,這裏的可用寬度爲:contentWidth=clientWidth()-paddingLeft()-paddingRight(),clientWidth和clientHeight代表一個對象內部的不包括border和滑動條的大小

·     元素的寬度指樣式屬性width的值,它可以通過計算容器的百分比得到一個絕對值

·     加上水平方向上的border和padding

到這裏是最佳寬度的計算過程,現在計算寬度的最大值和最小值,如果最佳寬度大於最大寬度則使用最大寬度,如果小於最小寬度則使用最小寬度。最後緩存這個值,當需要layout但寬度未改變時使用。

Line breaking

當一個渲染對象在佈局過程中需要折行時,則暫停並告訴它的parent它需要折行,parent將創建額外的渲染對象並調用它們的layout。

繪製 Painting

繪製階段,遍歷渲染樹並調用渲染對象的paint方法將它們的內容顯示在屏幕上,繪製使用UI基礎組件,這在UI的章節有更多的介紹。

全局和增量

和佈局一樣,繪製也可以是全局的-繪製完整的樹-或增量的。在增量的繪製過程中,一些渲染對象以不影響整棵樹的方式改變,改變的渲染對象使其在屏幕上的矩形區域失效,這將導致操作系統將其看作dirty區域,併產生一個paint事件,操作系統很巧妙的處理這個過程,並將多個區域合併爲一個。Chrome中,這個過程更復雜些,因爲渲染對象在不同的進程中,而不是在主進程中。Chrome在一定程度上模擬操作系統的行爲,表現爲監聽事件並派發消息給渲染根,在樹中查找到相關的渲染對象,重繪這個對象(往往還包括它的children)。

繪製順序

css2定義了繪製過程的順序-http://www.w3.org/TR/CSS21/zindex.html。這個就是元素壓入堆棧的順序,這個順序影響着繪製,堆棧從後向前進行繪製。

一個塊渲染對象的堆棧順序是:

1.     背景色

2.     背景圖

3.     border

4.     children

5.     outline

Firefox顯示列表

Firefox讀取渲染樹併爲繪製的矩形創建一個顯示列表,該列表以正確的繪製順序包含這個矩形相關的渲染對象。

用這樣的方法,可以使重繪時只需查找一次樹,而不需要多次查找——繪製所有的背景、所有的圖片、所有的border等等。

Firefox優化了這個過程,它不添加會被隱藏的元素,比如元素完全在其他不透明元素下面。

Webkit矩形存儲

重繪前,webkit將舊的矩形保存爲位圖,然後只繪製新舊矩形的差集。

動態變化

瀏覽器總是試着以最小的動作響應一個變化,所以一個元素顏色的變化將只導致該元素的重繪,元素位置的變化將大致元素的佈局和重繪,添加一個Dom節點,也會大致這個元素的佈局和重繪。一些主要的變化,比如增加html元素的字號,將會導致緩存失效,從而引起整數的佈局和重繪。

渲染引擎的線程

渲染引擎是單線程的,除了網絡操作以外,幾乎所有的事情都在單一的線程中處理,在Firefox和Safari中,這是瀏覽器的主線程,Chrome中這是tab的主線程。

網絡操作由幾個並行線程執行,並行連接的個數是受限的(通常是2-6個)。

事件循環

瀏覽器主線程是一個事件循環,它被設計爲無限循環以保持執行過程的可用,等待事件(例如layout和paint事件)並執行它們。下面是Firefox的主要事件循環代碼。

while (!mExiting)

   NS_ProcessNextEvent(thread);

CSS2 可視模型 CSS2 visual module

畫布The Canvas

根據CSS2規範,術語canvas用來描述格式化的結構所渲染的空間——瀏覽器繪製內容的地方。畫布對每個維度空間都是無限大的,但瀏覽器基於viewport的大小選擇了一個初始寬度。

根據http://www.w3.org/TR/CSS2/zindex.html的定義,畫布如果是包含在其他畫布內則是透明的,否則瀏覽器會指定一個顏色。

CSS盒模型

CSS盒模型描述了矩形盒,這些矩形盒是爲文檔樹中的元素生成的,並根據可視的格式化模型進行佈局。每個box包括內容區域(如圖片、文本等)及可選的四周padding、border和margin區域。

每個節點生成0-n個這樣的box。

所有的元素都有一個display屬性,用來決定它們生成box的類型,例如:

block-生成塊狀box

inline-生成一個或多個行內box

none-不生成box

默認的是inline,但瀏覽器樣式表設置了其他默認值,例如,div元素默認爲block。可以訪問http://www.w3.org/TR/CSS2/sample.html查看更多的默認樣式表示例。

定位策略 Position scheme

這裏有三種策略:

1.     normal-對象根據它在文檔的中位置定位,這意味着它在渲染樹和在Dom樹中位置一致,並根據它的盒模型和大小進行佈局

2.     float-對象先像普通流一樣佈局,然後儘可能的向左或是向右移動

3.     absolute-對象在渲染樹中的位置和Dom樹中位置無關

static和relative是normal,absolute和fixed屬於absolute。

在static定位中,不定義位置而使用默認的位置。其他策略中,作者指定位置——top、bottom、left、right。

Box佈局的方式由這幾項決定:box的類型、box的大小、定位策略及擴展信息(比如圖片大小和屏幕尺寸)。

Box類型

Block box:構成一個塊,即在瀏覽器窗口上有自己的矩形

Inline box:並沒有自己的塊狀區域,但包含在一個塊狀區域內

block一個挨着一個垂直格式化,inline則在水平方向上格式化。

Inline盒模型放置在行內或是line box中,每行至少和最高的box一樣高,當box以baseline對齊時——即一個元素的底部和另一個box上除底部以外的某點對齊,行高可以比最高的box高。當容器寬度不夠時,行內元素將被放到多行中,這在一個p元素中經常發生。

定位 Position

Relative

相對定位——先按照一般的定位,然後按所要求的差值移動。

Floats

一個浮動的box移動到一行的最左邊或是最右邊,其餘的box圍繞在它周圍。下面這段html:

<p>

<img style="float:right"src="images/image.gif" width="100"height="100">Lorem ipsum dolor sit amet, consectetuer...

</p>

將顯示爲:

Absolute和Fixed

這種情況下的佈局完全不顧普通的文檔流,元素不屬於文檔流的一部分,大小取決於容器。Fixed時,容器爲viewport(可視區域)。

圖17:fixed

注意-fixed即使在文檔流滾動時也不會移動。

Layered representation

這個由CSS屬性中的z-index指定,表示盒模型的第三個大小,即在z軸上的位置。Box分發到堆棧中(稱爲堆棧上下文),每個堆棧中靠後的元素將被較早繪製,棧頂靠前的元素離用戶最近,當發生交疊時,將隱藏靠後的元素。堆棧根據z-index屬性排序,擁有z-index屬性的box形成了一個局部堆棧,viewport有外部堆棧,例如:

<STYLE type="text/css">

      div {

       position: absolute;

       left: 2in;

       top: 2in;

      }

   </STYLE>

 

 <P>  

         <DIV

              style="z-index:3;background-color:red; width: 1in; height: 1in; ">

         </DIV>

         <DIV

              style="z-index:1;background-color:green;width: 2in; height: 2in;">

         </DIV>

  </p>

結果是:

雖然綠色div排在紅色div後面,可能在正常流中也已經被繪製在後面,但z-index有更高優先級,所以在根box的堆棧中更靠前。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章