一、Http狀態(tài)碼
狀態(tài)碼的職責(zé)是當(dāng)客戶端向服務(wù)器端發(fā)送請求時,描述返回的請求結(jié)果。借助狀態(tài)碼,用戶可以知道服務(wù)器端是正常處理了請求,還是出現(xiàn)了錯誤。
1.狀態(tài)碼的分類
2.常見的狀態(tài)碼
僅記錄在 RFC2616 上的 HTTP 狀態(tài)碼就達(dá) 40 種,若再加上 WebDAV(RFC4918、5842)和附加 HTTP 狀態(tài)碼 (RFC6585)等擴(kuò)展,數(shù)量就達(dá) 60 余種。別看種類繁多,實際上經(jīng)常使用的大概只有 14 種。接下來,我們就介紹一下這些具有代表性的 14 個狀態(tài)碼。
- 200 表示從客戶端發(fā)來的請求在服務(wù)器端被正常處理了。
- 204 表示請求處理成功,但沒有資源返回。
- 206 表示客戶端進(jìn)行了范圍請求,而服務(wù)器成功執(zhí)行了這部分的GET請求。
這種情況經(jīng)常發(fā)生在客戶端繼續(xù)請求一個未完成的下載的時候(比如當(dāng)客戶端加載一個體積較大的嵌入文件,比如視頻或PDF文件),或者是客戶端嘗試實現(xiàn)帶寬遏流的時候。 - 301 表示永久性重定向。該狀態(tài)碼表示請求的資源已被分配了新的URI,以后應(yīng)使用資源現(xiàn)在所指的URI。
- 302 表示臨時性重定向。該狀態(tài)碼表示請求的資源已被分配了新的URI,希望用戶(本次)能使用新的URI訪問。和301相似,但302表示的資源不是永久移動,只是臨時性的。換句話說,已移動的資源對應(yīng)的URI將來還有可能發(fā)生變化,比如,用戶把URI保存為書簽,但不會像301狀態(tài)碼出現(xiàn)時那樣更新書簽,而是仍舊保留返回302狀態(tài)碼的頁面對應(yīng)的URI。
- 303 表示由于請求對應(yīng)的資源存在著另一個URI,應(yīng)使用GET方法定向獲取請求的資源。
303和302狀態(tài)碼有著相同的功能,但是303明確表示客戶端應(yīng)當(dāng)采用get方法獲取資源,這點與302狀態(tài)碼有區(qū)別。 比如,當(dāng)使用POST方法訪問CGl程序,其執(zhí)行后的處理結(jié)果是希望客戶端能以GET方法重定向到另一個URI上去時,返回303狀態(tài)碼。
當(dāng)301、302、303響應(yīng)狀態(tài)碼返回時,幾乎所有瀏覽器都會把post改成get,并刪除請求報文內(nèi)的主體,之后請求會自動再次發(fā)送。
301、302標(biāo)準(zhǔn)是禁止將post方法改變成get方法的,但實際使用時大家都會這么做。 - 304 表示客戶端發(fā)送附帶條件的請求時(指采用GET方法的請求報文中包含if-matched,if-modified-since,if-none-match,if-range,if-unmodified-since任一個首部)服務(wù)器端允許請求訪問資源,但因發(fā)生請求未滿足條件的情況后,直接返回304Modified(服務(wù)器端資源未改變,可直接使用客戶端未過期的緩存)
- 307 表示臨時重定向。該狀態(tài)碼與302有相同的含義。盡管302標(biāo)準(zhǔn)禁止post變化get,但實際使用時大家不遵守。 307會遵照瀏覽器標(biāo)準(zhǔn),不會從post變?yōu)間et。但是對于處理響應(yīng)時的行為,各種瀏覽器有可能出現(xiàn)不同的情況。303和307是HTTP1.1新加的服務(wù)器響應(yīng)文檔的狀態(tài)碼,它們是對HTTP1.0中的302狀態(tài)碼的細(xì)化,主要用在對非GET、HEAD方法的響應(yīng)上。
- 400 表示請求報文中存在語法錯誤。當(dāng)錯誤發(fā)生時,需修改請求的內(nèi)容后再次發(fā)送請求。
- 401 表示未授權(quán)(Unauthorized),當(dāng)前請求需要用戶驗證
- 403 表示對請求資源的訪問被服務(wù)器拒絕了
- 404 表示服務(wù)器上無法找到請求的資源。除此之外,也可以在服務(wù)器端拒絕請求且不想說明理由時使
用。 - 500 表示服務(wù)器端在執(zhí)行請求時發(fā)生了錯誤。也有可能是Web應(yīng)用存在的bug或某些臨時的故障。
- 503 表示服務(wù)器暫時處于超負(fù)載或正在進(jìn)行停機(jī)維護(hù),現(xiàn)在無法處理請求。
狀態(tài)碼和狀況的不一致,不少返回的狀態(tài)碼響應(yīng)都是錯誤的,但是用戶可能察覺不到這點。比如Web應(yīng)用程序內(nèi)部發(fā)生錯誤,
狀態(tài)碼依然返回200 OK,這種情況也經(jīng)常遇到。
二、TCP連接
TCP建立一個連接,首先需要進(jìn)行三次握手。
1.三次握手
首先Client端發(fā)送連接請求報文,Server段接受連接后回復(fù)ACK報文,并為這次連接分配資源Client端接收到ACK報文后也向Server段發(fā)生ACK報文,并分配資源,這樣TCP連接就建立了。
1.客戶端發(fā)送一個帶SYN=1,Seq=X的數(shù)據(jù)包到服務(wù)器端口
2.服務(wù)器發(fā)回一個帶SYN=1, ACK=X+1, Seq=Y的響應(yīng)包以示傳達(dá)確認(rèn)信息
3.客戶端再回傳一個帶ACK=Y+1, Seq=Z的數(shù)據(jù)包,代表“握手結(jié)束”
2.為啥需要三次握手
謝希仁著《計算機(jī)網(wǎng)絡(luò)》第四版中講“三次握手”的目的是“為了防止已失效的連接請求報文段突然又傳送到了服務(wù)端,因而產(chǎn)生錯誤”。
client發(fā)出的第一個連接請求報文段并沒有丟失,而是在某個網(wǎng)絡(luò)結(jié)點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達(dá)server。本來這是一個早已失效的報文段。但server收到此失效的連接請求報文段后,就誤認(rèn)為是client再次發(fā)出的一個新的連接請求。于是就向client發(fā)出確認(rèn)報文段,同意建立連接。假設(shè)不采用“三次握手”,那么只要server發(fā)出確認(rèn),新的連接就建立了。由于現(xiàn)在client并沒有發(fā)出建立連接的請求,因此不會理睬server的確認(rèn),也不會向server發(fā)送數(shù)據(jù)。但server卻以為新的運(yùn)輸連接已經(jīng)建立,并一直等待client發(fā)來數(shù)據(jù)。這樣,server的很多資源就白白浪費掉了。采用“三次握手”的辦法可以防止上述現(xiàn)象發(fā)生。例如剛才那種情況,client不會向server的確認(rèn)發(fā)出確認(rèn)。server由于收不到確認(rèn),就知道client并沒有要求建立連接。
三、長連接
在HTTP/1.0中,默認(rèn)使用的是短連接。也就是說,瀏覽器和服務(wù)器每進(jìn)行一次HTTP操作,就建立一次連接,但任務(wù)結(jié)束就中斷連接。
從 HTTP/1.1起,默認(rèn)使用長連接,用以保持連接特性。使用長連接的HTTP協(xié)議,會在響應(yīng)頭有加入這行代碼:Connection:keep-alive,可以設(shè)置關(guān)閉時間。
由于瀏覽器的限制,同一個域下最多只能建立6個TCP連接(可以復(fù)用)。我們通常使用子域名來減少所有資源在只有一個連接時的產(chǎn)生的排隊延遲。
HTTP/2.0 時代擁有了“多路復(fù)用”功能,意思是:在一條連接上,可以同時發(fā)起無數(shù)個請求,并且響應(yīng)可以同時返回。HTTP2.0通信都在一個連接上完成,這個連接可以承載任意數(shù)量的雙向數(shù)據(jù)流。
對一個域名,只需要開啟一條 TCP 連接,請求都在這條 TCP 連接上干活。
長連接的意義在于可以減少TCP三次握手的開銷,即減少了TCP連接的重復(fù)建立和斷開所造成的額外開銷,使 HTTP 請求和響應(yīng)能夠更早地結(jié)束,這樣 Web 頁面的顯示速度也就相應(yīng)提高了。然而在 HTTP/1.1 協(xié)議中客戶端在同一時間,針對同一域名下的請求有一定數(shù)量限制。超過限制數(shù)目的請求會被阻塞。HTTP/2 通過讓所有數(shù)據(jù)流共用同一個連接,可以更有效地使用 TCP 連接,讓高帶寬也能真正的服務(wù)于 HTTP 的性能提升。所以說HTTP/2時未來發(fā)展的趨勢。
四、重定向
URL 重定向,也稱為 URL 轉(zhuǎn)發(fā),是一種當(dāng)實際資源,如單個頁面、表單或者整個 Web 應(yīng)用被遷移到新的 URL 下的時候,保持(原有)鏈接可用的技術(shù)。
進(jìn)行URL重定向時,服務(wù)器只在響應(yīng)信息的HTTP頭信息中設(shè)置了HTTP狀態(tài)碼和Location頭信息。
當(dāng)狀態(tài)碼為301或302時(301-永久重定向、302-臨時重定向),表示資源位置發(fā)生了改變,需要進(jìn)行重定向。
Location頭信息表示了資源的改變的位置,即:要跳重定向的URL。
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
response.writeHead(302, { // or 301 如果寫成狀態(tài)碼200,頁面將無內(nèi)容
'Location': '/new'
})
response.end("")
}
if (request.url === '/new') {
response.writeHead(200, {
'Content-Type': 'text/html',
})
response.end('<div>this is content</div>')
}
}).listen(8888)
console.log('server listening on 8888')
當(dāng)我們在地址欄輸入localhost:8888后,直接跳轉(zhuǎn)到localhost:8888/new,并展示出內(nèi)容
五、CORS跨域請求資源
1.跨域資源共享CORS簡介
CORS是一個W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
CORS需要瀏覽器和服務(wù)器同時支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
因此,實現(xiàn)CORS通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實現(xiàn)了CORS接口,就可以跨源通信。
2.如何實現(xiàn)
瀏覽器將CORS請求分成兩類:簡單請求和非簡單請求。
接下來我們舉一個簡單請求的例子,來說明CORS如何實現(xiàn)跨域請求:
http://127.0.0.1:8888向http://127.0.0.1:8887請求資源
//server.js文件 http://127.0.0.1:8888 node server.js 啟動服務(wù)器
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response) {
console.log('request come', request.url)
const html = fs.readFileSync('test.html', 'utf8')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}).listen(8888)
console.log('server listening on 8888')
//test.html 文件 向http://127.0.0.1:8887請求數(shù)據(jù)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
</body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8887/')
xhr.send()
</script>
</html>
//server.js文件 http://127.0.0.1:8887 node server2.js 啟動服務(wù)器
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888'//表示接受8888端口的請求
})
response.end('123')
}).listen(8887)
console.log('server listening on 8887')
最后得到如下結(jié)果:
六、Content Security Policy(CSP)
1.CSP簡介
跨域腳本攻擊 XSS 是最常見、危害最大的網(wǎng)頁安全漏洞。為了防止它們,要采取很多編程措施,非常麻煩。很多人提出,能不能根本上解決問題,瀏覽器自動禁止外部注入惡意腳本?
CSP 的實質(zhì)就是白名單制度,開發(fā)者明確告訴客戶端,哪些外部資源可以加載和執(zhí)行,等同于提供白名單。它的實現(xiàn)和執(zhí)行全部由瀏覽器完成,開發(fā)者只需提供配置。
CSP 大大增強(qiáng)了網(wǎng)頁的安全性。攻擊者即使發(fā)現(xiàn)了漏洞,也沒法注入腳本,除非還控制了一臺列入了白名單的可信主機(jī)。
2.如何實現(xiàn)
如何啟用CSP,可以通過 HTTP 頭信息的Content-Security-Policy的字段
接下來我們看一個例子:如何限制加載內(nèi)嵌的script
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>This is content</div>
<script>
console.log('inline js')//禁止這段script代碼執(zhí)行
</script>
</body>
</html>
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response) {
console.log('request come', request.url)
const html = fs.readFileSync('test.html', 'utf8')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Security-Policy': "default-src http:https:"http://限制所有的外部資源,都只能從http/https加載。
})
response.end(html)
}).listen(8888)
console.log('server listening on 8888')
最后控制臺提示如下錯誤:
如果覺得文章對你有些許幫助,歡迎在我的GitHub博客點贊和關(guān)注,感激不盡!
參考文章
《圖解HTTP》[日] 上野宣 著
《計算機(jī)網(wǎng)絡(luò)》謝希仁著