1. 程式人生 > >測試基礎-http協議(轉)

測試基礎-http協議(轉)

min ref rom .cn devel 保存 連續 鏈接 時機

HTTP的特性

  • HTTP構建於TCP/IP協議之上,默認端口號是80
  • HTTP是無連接無狀態的

HTTP報文

請求報文

HTTP 協議是以 ASCII 碼傳輸,建立在 TCP/IP 協議之上的應用層規範。規範把 HTTP 請求分為三個部分:狀態行、請求頭、消息主體。類似於下面這樣:

<method> <request-URL> <version>
<headers>

<entity-body>

HTTP定義了與服務器交互的不同方法,最基本的方法有4種,分別是GETPOSTPUTDELETEURL全稱是資源描述符,我們可以這樣認為:一個URL

地址,它用於描述一個網絡上的資源,而 HTTP 中的GETPOSTPUTDELETE就對應著對這個資源的查,增,改,刪4個操作。

  1. GET用於信息獲取,而且應該是安全的 和 冪等的。

    所謂安全的意味著該操作用於獲取信息而非修改信息。換句話說,GET 請求一般不應產生副作用。就是說,它僅僅是獲取資源信息,就像數據庫查詢一樣,不會修改,增加數據,不會影響資源的狀態。

    冪等的意味著對同一URL的多個請求應該返回同樣的結果。

    GET請求報文示例:

     GET /books/?sex=man&name=Professional HTTP/1.1
     Host: www.example.com
     User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
     Gecko/20050225 Firefox/1.0.1
     Connection: Keep-Alive
    
  2. POST表示可能修改變服務器上的資源的請求。

     POST / HTTP/1.1
     Host: www.example.com
     User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
     Gecko/20050225 Firefox/1.0.1
     Content-Type: application/x-www-form-urlencoded
     Content-Length: 40
     Connection: Keep-Alive
    
     sex=man&name=Professional  
    
  3. 註意:

    • GET 可提交的數據量受到URL長度的限制,HTTP 協議規範沒有對 URL 長度進行限制。這個限制是特定的瀏覽器及服務器對它的限制
    • 理論上講,POST 是沒有大小限制的,HTTP 協議規範也沒有進行大小限制,出於安全考慮,服務器軟件在實現時會做一定限制
    • 參考上面的報文示例,可以發現 GET 和 POST 數據內容是一模一樣的,只是位置不同,一個在URL裏,一個在 HTTP 包的包體裏

POST 提交數據的方式

HTTP 協議中規定 POST 提交的數據必須在 body 部分中,但是協議中沒有規定數據使用哪種編碼方式或者數據格式。實際上,開發者完全可以自己決定消息主體的格式,只要最後發送的 HTTP 請求滿足上面的格式就可以。

但是,數據發送出去,還要服務端解析成功才有意義。一般服務端語言如 php、python 等,以及它們的 framework,都內置了自動解析常見數據格式的功能。服務端通常是根據請求頭(headers)中的 Content-Type 字段來獲知請求中的消息主體是用何種方式編碼,再對主體進行解析。所以說到 POST 提交數據方案,包含了 Content-Type 和消息主體編碼方式兩部分。下面就正式開始介紹它們:

  • application/x-www-form-urlencoded

這是最常見的 POST 數據提交方式。瀏覽器的原生 <form> 表單,如果不設置 enctype 屬性,那麽最終就會以 application/x-www-form-urlencoded 方式提交數據。上個小節當中的例子便是使用了這種提交方式。可以看到 body 當中的內容和 GET 請求是完全相同的。

  • multipart/form-data

這又是一個常見的 POST 數據提交的方式。我們使用表單上傳文件時,必須讓 <form> 表單的 enctype 等於 multipart/form-data。直接來看一個請求示例:

POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

這個例子稍微復雜點。首先生成了一個 boundary 用於分割不同的字段,為了避免與正文內容重復,boundary 很長很復雜。然後 Content-Type 裏指明了數據是以 multipart/form-data 來編碼,本次請求的 boundary 是什麽內容。消息主體裏按照字段個數又分為多個結構類似的部分,每部分都是以 --boundary 開始,緊接著是內容描述信息,然後是回車,最後是字段具體內容(文本或二進制)。如果傳輸的是文件,還要包含文件名和文件類型信息。消息主體最後以 --boundary-- 標示結束。關於 multipart/form-data 的詳細定義,請前往 RFC1867 查看(或者相對友好一點的 MDN 文檔)。

這種方式一般用來上傳文件,各大服務端語言對它也有著良好的支持。

上面提到的這兩種 POST 數據的方式,都是瀏覽器原生支持的,而且現階段標準中原生 <form> 表單也只支持這兩種方式(通過 <form> 元素的 enctype 屬性指定,默認為 application/x-www-form-urlencoded。其實 enctype 還支持 text/plain,不過用得非常少)。

隨著越來越多的 Web 站點,尤其是 WebApp,全部使用 Ajax 進行數據交互之後,我們完全可以定義新的數據提交方式,例如 application/jsontext/xml,乃至 application/x-protobuf 這種二進制格式,只要服務器可以根據 Content-TypeContent-Encoding 正確地解析出請求,都是沒有問題的。

響應報文

HTTP 響應與 HTTP 請求相似,HTTP響應也由3個部分構成,分別是:

  • 狀態行
  • 響應頭(Response Header)
  • 響應正文

狀態行由協議版本、數字形式的狀態代碼、及相應的狀態描述,各元素之間以空格分隔。

常見的狀態碼有如下幾種:

  • 200 OK 客戶端請求成功
  • 301 Moved Permanently 請求永久重定向
  • 302 Moved Temporarily 請求臨時重定向
  • 304 Not Modified 文件未修改,可以直接使用緩存的文件。
  • 400 Bad Request 由於客戶端請求有語法錯誤,不能被服務器所理解。
  • 401 Unauthorized 請求未經授權。這個狀態代碼必須和WWW-Authenticate報頭域一起使用
  • 403 Forbidden 服務器收到請求,但是拒絕提供服務。服務器通常會在響應正文中給出不提供服務的原因
  • 404 Not Found 請求的資源不存在,例如,輸入了錯誤的URL
  • 500 Internal Server Error 服務器發生不可預期的錯誤,導致無法完成客戶端的請求。
  • 503 Service Unavailable 服務器當前不能夠處理客戶端的請求,在一段時間之後,服務器可能會恢復正常。

下面是一個HTTP響應的例子:

HTTP/1.1 200 OK

Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112

<html>...

條件 GET

HTTP 條件 GET 是 HTTP 協議為了減少不必要的帶寬浪費,提出的一種方案。詳見 RFC2616 。

  1. HTTP 條件 GET 使用的時機?

    客戶端之前已經訪問過某網站,並打算再次訪問該網站。

  2. HTTP 條件 GET 使用的方法?

    客戶端向服務器發送一個包詢問是否在上一次訪問網站的時間後是否更改了頁面,如果服務器沒有更新,顯然不需要把整個網頁傳給客戶端,客戶端只要使用本地緩存即可,如果服務器對照客戶端給出的時間已經更新了客戶端請求的網頁,則發送這個更新了的網頁給用戶。

    下面是一個具體的發送接受報文示例:

    客戶端發送請求:

     GET / HTTP/1.1  
     Host: www.sina.com.cn:80  
     If-Modified-Since:Thu, 4 Feb 2010 20:39:13 GMT  
     Connection: Close  
    

    第一次請求時,服務器端返回請求數據,之後的請求,服務器根據請求中的 If-Modified-Since 字段判斷響應文件沒有更新,如果沒有更新,服務器返回一個 304 Not Modified響應,告訴瀏覽器請求的資源在瀏覽器上沒有更新,可以使用已緩存的上次獲取的文件。

     HTTP/1.0 304 Not Modified  
     Date: Thu, 04 Feb 2010 12:38:41 GMT  
     Content-Type: text/html  
     Expires: Thu, 04 Feb 2010 12:39:41 GMT  
     Last-Modified: Thu, 04 Feb 2010 12:29:04 GMT  
     Age: 28  
     X-Cache: HIT from sy32-21.sina.com.cn  
     Connection: close 
    

    如果服務器端資源已經更新的話,就返回正常的響應。

持久連接

我們知道 HTTP 協議采用“請求-應答”模式,當使用普通模式,即非 Keep-Alive 模式時,每個請求/應答客戶和服務器都要新建一個連接,完成之後立即斷開連接(HTTP 協議為無連接的協議);當使用 Keep-Alive 模式(又稱持久連接、連接重用)時,Keep-Alive 功能使客戶端到服務器端的連接持續有效,當出現對服務器的後繼請求時,Keep-Alive 功能避免了建立或者重新建立連接。

在 HTTP 1.0 版本中,並沒有官方的標準來規定 Keep-Alive 如何工作,因此實際上它是被附加到 HTTP 1.0協議上,如果客戶端瀏覽器支持 Keep-Alive ,那麽就在HTTP請求頭中添加一個字段 Connection: Keep-Alive,當服務器收到附帶有 Connection: Keep-Alive 的請求時,它也會在響應頭中添加一個同樣的字段來使用 Keep-Alive 。這樣一來,客戶端和服務器之間的HTTP連接就會被保持,不會斷開(超過 Keep-Alive 規定的時間,意外斷電等情況除外),當客戶端發送另外一個請求時,就使用這條已經建立的連接。

在 HTTP 1.1 版本中,默認情況下所有連接都被保持,如果加入 "Connection: close" 才關閉。目前大部分瀏覽器都使用 HTTP 1.1 協議,也就是說默認都會發起 Keep-Alive 的連接請求了,所以是否能完成一個完整的 Keep-Alive 連接就看服務器設置情況。

由於 HTTP 1.0 沒有官方的 Keep-Alive 規範,並且也已經基本被淘汰,以下討論均是針對 HTTP 1.1 標準中的 Keep-Alive 展開的。

註意:

  • HTTP Keep-Alive 簡單說就是保持當前的TCP連接,避免了重新建立連接。

  • HTTP 長連接不可能一直保持,例如 Keep-Alive: timeout=5, max=100,表示這個TCP通道可以保持5秒,max=100,表示這個長連接最多接收100次請求就斷開。

  • HTTP 是一個無狀態協議,這意味著每個請求都是獨立的,Keep-Alive 沒能改變這個結果。另外,Keep-Alive也不能保證客戶端和服務器之間的連接一定是活躍的,在 HTTP1.1 版本中也如此。唯一能保證的就是當連接被關閉時你能得到一個通知,所以不應該讓程序依賴於 Keep-Alive 的保持連接特性,否則會有意想不到的後果。

  • 使用長連接之後,客戶端、服務端怎麽知道本次傳輸結束呢?兩部分:1. 判斷傳輸數據是否達到了Content-Length 指示的大小;2. 動態生成的文件沒有 Content-Length ,它是分塊傳輸(chunked),這時候就要根據 chunked 編碼來判斷,chunked 編碼的數據在最後有一個空 chunked 塊,表明本次傳輸數據結束,詳見這裏。什麽是 chunked 分塊傳輸呢?下面我們就來介紹一下。

Transfer-Encoding

Transfer-Encoding 是一個用來標示 HTTP 報文傳輸格式的頭部值。盡管這個取值理論上可以有很多,但是當前的 HTTP 規範裏實際上只定義了一種傳輸取值——chunked。

如果一個HTTP消息(請求消息或應答消息)的Transfer-Encoding消息頭的值為chunked,那麽,消息體由數量未定的塊組成,並以最後一個大小為0的塊為結束。

每一個非空的塊都以該塊包含數據的字節數(字節數以十六進制表示)開始,跟隨一個CRLF (回車及換行),然後是數據本身,最後塊CRLF結束。在一些實現中,塊大小和CRLF之間填充有白空格(0x20)。

最後一塊是單行,由塊大小(0),一些可選的填充白空格,以及CRLF。最後一塊不再包含任何數據,但是可以發送可選的尾部,包括消息頭字段。消息最後以CRLF結尾。

一個示例響應如下:

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25
This is the data in the first chunk

1A
and this is the second one
0

註意:

  • chunked 和 multipart 兩個名詞在意義上有類似的地方,不過在 HTTP 協議當中這兩個概念則不是一個類別的。multipart 是一種 Content-Type,標示 HTTP 報文內容的類型,而 chunked 是一種傳輸格式,標示報頭將以何種方式進行傳輸。
  • chunked 傳輸不能事先知道內容的長度,只能靠最後的空 chunk 塊來判斷,因此對於下載請求來說,是沒有辦法實現進度的。在瀏覽器和下載工具中,偶爾我們也會看到有些文件是看不到下載進度的,即采用 chunked 方式進行下載。
  • chunked 的優勢在於,服務器端可以邊生成內容邊發送,無需事先生成全部的內容。HTTP/2 不支持 Transfer-Encoding: chunked,因為 HTTP/2 有自己的 streaming 傳輸方式。

HTTP Pipelining(HTTP 管線化)

默認情況下 HTTP 協議中每個傳輸層連接只能承載一個 HTTP 請求和響應,瀏覽器會在收到上一個請求的響應之後,再發送下一個請求。在使用持久連接的情況下,某個連接上消息的傳遞類似於請求1 -> 響應1 -> 請求2 -> 響應2 -> 請求3 -> 響應3

HTTP Pipelining(管線化)是將多個 HTTP 請求整批提交的技術,在傳送過程中不需等待服務端的回應。使用 HTTP Pipelining 技術之後,某個連接上的消息變成了類似這樣請求1 -> 請求2 -> 請求3 -> 響應1 -> 響應2 -> 響應3

註意下面幾點:

  • 管線化機制通過持久連接(persistent connection)完成,僅 HTTP/1.1 支持此技術(HTTP/1.0不支持)
  • 只有 GET 和 HEAD 請求可以進行管線化,而 POST 則有所限制
  • 初次創建連接時不應啟動管線機制,因為對方(服務器)不一定支持 HTTP/1.1 版本的協議
  • 管線化不會影響響應到來的順序,如上面的例子所示,響應返回的順序並未改變
  • HTTP /1.1 要求服務器端支持管線化,但並不要求服務器端也對響應進行管線化處理,只是要求對於管線化的請求不失敗即可
  • 由於上面提到的服務器端問題,開啟管線化很可能並不會帶來大幅度的性能提升,而且很多服務器端和代理程序對管線化的支持並不好,因此現代瀏覽器如 Chrome 和 Firefox 默認並未開啟管線化支持

更多關於 HTTP Pipelining 的知識可以參考這裏。

會話跟蹤

  1. 什麽是會話?

    客戶端打開與服務器的連接發出請求到服務器響應客戶端請求的全過程稱之為會話。

  2. 什麽是會話跟蹤?

    會話跟蹤指的是對同一個用戶對服務器的連續的請求和接受響應的監視。

  3. 為什麽需要會話跟蹤?

    瀏覽器與服務器之間的通信是通過HTTP協議進行通信的,而HTTP協議是”無狀態”的協議,它不能保存客戶的信息,即一次響應完成之後連接就斷開了,下一次的請求需要重新連接,這樣就需要判斷是否是同一個用戶,所以才有會話跟蹤技術來實現這種要求。

  1. 會話跟蹤常用的方法:

    1. URL重寫

      URL(統一資源定位符)是Web上特定頁面的地址,URL重寫的技術就是在URL結尾添加一個附加數據以標識該會話,把會話ID通過URL的信息傳遞過去,以便在服務器端進行識別不同的用戶。

    2. 隱藏表單域

      將會話ID添加到HTML表單元素中提交到服務器,此表單元素並不在客戶端顯示

    3. Cookie

      Cookie是Web服務器發送給客戶端的一小段信息,客戶端請求時可以讀取該信息發送到服務器端,進而進行用戶的識別。對於客戶端的每次請求,服務器都會將Cookie發送到客戶端,在客戶端可以進行保存,以便下次使用。

      客戶端可以采用兩種方式來保存這個Cookie對象,一種方式是保存在客戶端內存中,稱為臨時Cookie,瀏覽器關閉後這個Cookie對象將消失。另外一種方式是保存在客戶機的磁盤上,稱為永久Cookie。以後客戶端只要訪問該網站,就會將這個Cookie再次發送到服務器上,前提是這個Cookie在有效期內,這樣就實現了對客戶的跟蹤。

      Cookie是可以被禁止的。

    4. Session:

      每一個用戶都有一個不同的session,各個用戶之間是不能共享的,是每個用戶所獨享的,在session中可以存放信息。

      在服務器端會創建一個session對象,產生一個sessionID來標識這個session對象,然後將這個sessionID放入到Cookie中發送到客戶端,下一次訪問時,sessionID會發送到服務器,在服務器端進行識別不同的用戶。

      Session的實現依賴於Cookie,如果Cookie被禁用,那麽session也將失效。

跨站攻擊

  • CSRF(Cross-site request forgery,跨站請求偽造)

    CSRF(XSRF) 顧名思義,是偽造請求,冒充用戶在站內的正常操作。

    例如,一論壇網站的發貼是通過 GET 請求訪問,點擊發貼之後 JS 把發貼內容拼接成目標 URL 並訪問:

      http://example.com/bbs/create_post.php?title=標題&content=內容
    

    那麽,我們只需要在論壇中發一帖,包含一鏈接:

      http://example.com/bbs/create_post.php?title=我是腦殘&content=哈哈
    

    只要有用戶點擊了這個鏈接,那麽他們的帳戶就會在不知情的情況下發布了這一帖子。可能這只是個惡作劇,但是既然發貼的請求可以偽造,那麽刪帖、轉帳、改密碼、發郵件全都可以偽造。

    如何防範 CSRF 攻擊?可以註意以下幾點:

    • 關鍵操作只接受POST請求

    • 驗證碼

      CSRF攻擊的過程,往往是在用戶不知情的情況下構造網絡請求。所以如果使用驗證碼,那麽每次操作都需要用戶進行互動,從而簡單有效的防禦了CSRF攻擊。

      但是如果你在一個網站作出任何舉動都要輸入驗證碼會嚴重影響用戶體驗,所以驗證碼一般只出現在特殊操作裏面,或者在註冊時候使用。

    • 檢測 Referer

      常見的互聯網頁面與頁面之間是存在聯系的,比如你在www.baidu.com應該是找不到通往www.google.com的鏈接的,再比如你在論壇留言,那麽不管你留言後重定向到哪裏去了,之前的那個網址一定會包含留言的輸入框,這個之前的網址就會保留在新頁面頭文件的 Referer

      通過檢查Referer的值,我們就可以判斷這個請求是合法的還是非法的,但是問題出在服務器不是任何時候都能接受到Referer的值,所以 Referer Check 一般用於監控 CSRF 攻擊的發生,而不用來抵禦攻擊。

    • Token

      目前主流的做法是使用 Token 抵禦 CSRF 攻擊。下面通過分析 CSRF 攻擊來理解為什麽 Token 能夠有效

      CSRF攻擊要成功的條件在於攻擊者能夠預測所有的參數從而構造出合法的請求。所以根據不可預測性原則,我們可以對參數進行加密從而防止CSRF攻擊。

      另一個更通用的做法是保持原有參數不變,另外添加一個參數Token,其值是隨機的。這樣攻擊者因為不知道Token而無法構造出合法的請求進行攻擊。

      Token 使用原則

      • Token 要足夠隨機————只有這樣才算不可預測
      • Token 是一次性的,即每次請求成功後要更新Token————這樣可以增加攻擊難度,增加預測難度
      • Token 要註意保密性————敏感操作使用 post,防止 Token 出現在 URL 中

      註意:過濾用戶輸入的內容不能阻擋 csrf,我們需要做的是過濾請求的來源。

  • XSS(Cross Site Scripting,跨站腳本攻擊)

    XSS 全稱“跨站腳本”,是註入攻擊的一種。其特點是不對服務器端造成任何傷害,而是通過一些正常的站內交互途徑,例如發布評論,提交含有 JavaScript 的內容文本。這時服務器端如果沒有過濾或轉義掉這些腳本,作為內容發布到了頁面上,其他用戶訪問這個頁面的時候就會運行這些腳本。

    運行預期之外的腳本帶來的後果有很多中,可能只是簡單的惡作劇——一個關不掉的窗口:

      while (true) {
          alert("你關不掉我~");
      }
    

    也可以是盜號或者其他未授權的操作。

    XSS 是實現 CSRF 的諸多途徑中的一條,但絕對不是唯一的一條。一般習慣上把通過 XSS 來實現的 CSRF 稱為 XSRF。

    如何防禦 XSS 攻擊?

    理論上,所有可輸入的地方沒有對輸入數據進行處理的話,都會存在XSS漏洞,漏洞的危害取決於攻擊代碼的威力,攻擊代碼也不局限於script。防禦 XSS 攻擊最簡單直接的方法,就是過濾用戶的輸入。

    如果不需要用戶輸入 HTML,可以直接對用戶的輸入進行 HTML escape 。下面一小段腳本:

      <script>window.location.href=”http://www.baidu.com”;</script>
    

    經過 escape 之後就成了:

      &lt;script&gt;window.location.href=&quot;http://www.baidu.com&quot;&lt;/script&gt;
    

    它現在會像普通文本一樣顯示出來,變得無毒無害,不能執行了。

    當我們需要用戶輸入 HTML 的時候,需要對用戶輸入的內容做更加小心細致的處理。僅僅粗暴地去掉 script 標簽是沒有用的,任何一個合法 HTML 標簽都可以添加 onclick 一類的事件屬性來執行 JavaScript。更好的方法可能是,將用戶的輸入使用 HTML 解析庫進行解析,獲取其中的數據。然後根據用戶原有的標簽屬性,重新構建 HTML 元素樹。構建的過程中,所有的標簽、屬性都只從白名單中拿取。

測試基礎-http協議(轉)