Web前端開發工程師必須瞭解的HTTP知識

前言

官方解釋,HTTP是一個應用層協議,由請求和響應構成,是一個標準的客戶端服務器模型。HTTP是一個無狀態的協議。通俗來講,就是規定服務端和客戶端通訊的一種規則。更多的是基於瀏覽器環境下使用,那麼從你瀏覽器輸入地址開始到最終頁面的呈現,到底通過了哪些過程呢?廢話很少說,先貼一張圖,以下: javascript

如上圖,就是http請求發起到返回的完證過程,本覺得本身對http的瞭解還算能夠,可是乍一看圖仍是很蒙的,好比說若是讓你優化http過程,你該從何下手呢?我想大部分仍是比較關心requestresponse,以及數據返回以後的DOMContentLoadload,至於http中的一些配置並非很清楚,其實經過優化配置,一樣可以加速網頁的打開速度,所以,我大體總結一些關於http中會常用的配置項,以及這些使用通常會在哪些場景中使用到。html

注意:上圖中的domContentLoadedEventEnd表明DOMContentLoaded事件完成的時間節點,也就是jQuery中的domready時間。load表明的是onload事件觸發和結束的時間節點。前端

HTTP之跨域相關內容

基本內容

這裏主要介紹JSONPCORS跨域,現實場景中,以上兩種使用居多,因此其餘跨域方案不作詳細介紹。形成跨域的主要緣由主要是瀏覽器自己的同源策略引發的。java

JSONP可以實現跨域主要是由於瀏覽器上容許標籤上經過src/href加載外鏈路徑,可是JSONP只支持GET請求,同時由於瀏覽器中url長度的限制,所以JSONP能傳輸的數據大小也有必定的限制。node

CORS跨域可以支持的全部ajax的方法,固然,目前是支持ie9+,低版本暫時不支持,隨時互聯網的發展,相信低版本的瀏覽器會逐漸被淘汰。在只用CORS只須要服務端可以開啓容許跨域的頭設置便可,也就是Access-Control-Allow-Origingit

跨域大體的流程圖以下:github

注意:JSONP中的數據限制並非GET請求自己的限制,而是瀏覽器中url自己有長度限制,GET方法是沒有任何長度限制的;無論是JSONP仍是CORS跨域,其實服務器均可以接收來自客戶端的數據請求,而且也都成功返回了,只是瀏覽器自己有同源策略的限制,纔會進一步判斷返回的數據是否符合瀏覽器的限制。web

這裏有個題外話,Access-Control-Allow-Origin這個配置項默認支持配置單個域名或者*,爲了安全起見,不建議配置*,那麼如何配置才能支持多個域名跨域呢?有一個簡易的方法能夠解決,主要思路是經過服務端定義可支持的跨域域名集合,經過循環判斷當前請求是否支持便可,片斷代碼以下:ajax

// 服務端代碼,如下是node服務作測試
const http = require('http')
const allowDomains = [
    'http://www.a.com',
    'http://www.b.com',
    'http://www.c.com'
]
const server = http.createServer((req, res) => {
    let acao = ''
    for(let i = 0, l = allowDomains.length; i < l; i++) {
        if(allowDomains[i].indexOf(req.headers.host) > -1) {
            acao = allowDomains[i]
            break
        }
    }
    res.writeHead(200, 
        {
            'Access-Control-Allow-Origin': acao
        }
    )
    res.end('Hello World\n')
}).listen(3001)
console.log('server listen 3001')
複製代碼

CORS跨域限制

  • 默認容許的方法有GETHEADPOST
  • 默認容許的Content-Type有text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  • HTTP的頭信息包含AcceptAccept-LanguageContent-LanguageLast-Event-ID

在請求包含以上內容的時候,其實就是簡單請求,在跨域的狀況下,瀏覽器默認是直接經過的,其他剩下的稱之爲複雜請求,瀏覽器會默認發送一次預請求做爲驗證,若是驗證經過則表明請求成功。express

所以須要對上圖增長限制的修改,最終以下:

其實就是對於複雜請求作了一次校驗,大體能夠這樣解釋,若是在發送請求時,例如額外帶了headers的配置項,若是須要驗證經過就必須在服務端也要配置容許該headers的返回,這樣預請求的驗證纔會經過。也能夠經過代碼作一下驗證,基本以下:

// 後端服務代碼
const http = require('http')
const server = http.createServer((req, res) => {
    res.writeHead(200, 
        {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Headers': 'aa' // 經過設置了這個,才能使得預請求驗證經過
        }
    )
    res.end('Hello World\n')
}).listen(3001)
console.log('server listen 3001')

// 前端服務代碼
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const html = fs.readFileSync('index.html', 'utf8')
    res.writeHead(200, 
        {
            'Content-Type': 'text/html' 
        }
    )
    res.end(html)
}).listen(3000)
console.log('server listen 3000')

// index.html主要代碼以下
fetch('http://localhost:3001', {
    method: 'post',
    headers: {
        aa:'123'
    }
}) 
複製代碼

以上測試代碼主要是在發起post請求的時候額外攜帶了一個headers參數,只有在服務端配置了容許該headers傳輸才能使得瀏覽器預請求驗證經過,反之則會失敗。你們能夠根據以上測試代碼在本身的本機測試就能明白了。

注意:經過設置Access-Control-Request-Method能夠配置其餘的方法,例如PUTDELETE等。

細心的同窗可能會發現,根據以上代碼的確能夠經過預請求,可是若是再次刷新網頁,會發現仍然還會存在預請求,對於第一次預請求已經經過了,爲何一樣的請求還會再發送一次呢?其實這裏能夠作一個優化,減小預請求的發送。

經過設置Access-Control-Max-Age來肯定預請求的有效時間,只要在有效時間內,就不會再次發送預請求了。

HTTP之Cache-Control

Cache-Control包含不少特性,其中no-cache這個配置項確定最熟悉,官方解釋是在釋放緩存副本以前,強制高速緩存將請求提交給原始服務器進行驗證,其實就是表明沒有緩存。可是其實它依然有不少特性,通過資料查詢,大體分爲如下幾類,

介紹下通常經常使用的配置參數的文字解釋:

  • public表明http從請求到返回的整個路徑上的均可以被緩存,例如客戶端瀏覽器,通過的代理服務器等等。

  • private指發起的瀏覽器這一端才能進行緩存,也就是代理服務器是不能緩存的。

  • no-cache 是否使用緩存須要經過服務器驗證後才能判斷。

  • max-age=<seconds> 最大能緩存多少秒,過時以後,請求會再次發送到服務端,對於返回的數據會再次被緩存。

  • s-maxage=<seconds> 會覆蓋max-age或者Expires頭,應用於共享(如:代理服務器)緩存,而且在代理服務器生效,客戶端不生效。

  • max-stale[=<seconds>] 代表客戶端願意接收一個已通過期的資源。即便max-age已通過期,一樣會使用本地的過時緩存。

  • must-revalidate 若是max-age過時,必須經過服務端來驗證返回的數據是否真的過時。

  • proxy-revalidate 主要使用在代理服務器端,對於過時的數據必須向服務端從新請求一遍。

  • no-store 本地和代理服務器都不容許存緩存。

  • no-transform 不得對資源進行轉換或轉變,主要使用在代理服務器上。

具體每一個配置的官方解釋參考具體說明

資源驗證

  • Last-Modified代表請求的資源上次的修改時間,主要配合If-Modified-Since(客戶端保留的資源上次的修改時間)進行使用,主要是在發送請求的時候帶上。經過對比上次修改時間以驗證資源是否更新。
  • Etag資源的內容標識。(不惟一,一般爲文件的md5或者一段hash值,只要保證寫入和驗證時的方法一致便可),配合If-Match或者If-None-Match進行使用,對比資源的內容標識來判斷是否使用緩存。

其實服務器能夠經過Etag來區分返回哪些數據,具體能夠參考下面的示例:

// server.js
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const url = req.url
const html = fs.readFileSync('index.html', 'utf8')
const etag = req.headers['if-none-match']
if(url === '/') {
res.writeHead(200, 
    {
    'Content-Type': 'text/html',
    'Cache-Control': 'max-age=2000, no-cache',
    'Last-Modified': '123',
    'Etag': '444'
    }
)
res.end(html)
}
if(url === '/aa.js') {
if(etag === '444') {
    res.writeHead(304, 
    {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000, no-cache',
        'Last-Modified': '123',
        'Etag': '444'
    }
    )
    res.end('888888')
} else {
    res.writeHead(200, 
    {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000, no-cache',
        'Last-Modified': '123',
        'Etag': '444'
    }
    )
    res.end('1111111111')
}
}
}).listen(3000)
console.log('server listen 3000')
複製代碼
<!-- html文件 -->
<!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>
<script src="/aa.js"></script>
</body>
</html>
複製代碼

第一次刷新和第二次刷新截圖以下: 根據測試代碼能得出,在服務端經過etag === '444'作了判斷,可是最終返回的依然是1111111111,這是因爲服務端選取了第一次的緩存數據做爲返回。雖然發生了一次請求,但請求內容長度減小了,節省了帶寬。

總結

其實以上全部的概念基本能夠用一個圖來展現,大體分爲2個主要部分:

一、本地緩存,其實也就是本地資源的緩存。 二、服務器緩存,包含驗證緩存和非驗證緩存。

HTTP之cookie

  • 經過Set-Cookie來設置cookie,下次請求的時候,會自動帶上以前設置的cookie(同域狀況下),取值類型是String/Array。
  • 經過max-ageExpires設置過時時間。
  • 經過Secure來設置只能在https的時候發送。
  • 經過HttpOnly來設置沒法經過document.cookie訪問。
  • 經過domain=來設置該cookie是否在同域下共享。

以上內容基本能彙總成一張圖解釋,以下:

HTTP之keep-alive

目前開發web網頁,全部請求默認都是Connection: keep-alive,除非服務端手動去關閉配置(Connection: close),使用keep-alive能夠複用以前的請求的信道,這樣減小tcp三次握手的時間(同域狀況下才能生效)。

HTTP之數據協商

web服務請求會攜帶如下信息內容:

  • Accept 想要的數據類型
  • Accept-Encoding 限制服務端數據的壓縮方式(gzip、deflate、br)
  • Accept-Language 服務端返回數據的語言類型
  • User-Agent瀏覽器頭信息內容

服務端返回數據會攜帶如下信息內容:

  • Content-type 返回的數據類型
  • Content-Encoding 對應的是 Accept-Encoding 表明數據壓縮類型
  • Content-Language 對應的是 Accept-Language 表明數據語言類型

固然,web請求也能夠自定義Content-type來傳輸數據,通常在form表單中比較經常使用,例如上傳文件,會指定Content-type:multipart/form-data,這樣服務端就能接收上傳的文件信息內容。

HTTP之重定向

瀏覽器能識別的重定向code碼有兩種,分別是301302,二者在使用上會有必定的區別,大體以下:

  • 301重定向是永久重定向,用戶在訪問資源的時候,瀏覽器默認是從緩存中獲取以前指定的跳轉信息;
  • 302重定向能夠隨時取消,也就是用戶在訪問資源的時候,每次都會通過服務端而且在服務端經過跳轉邏輯進行跳轉;

可使用一段代碼來描述以上的不一樣之處,基本代碼以下:

// 302跳轉測試代碼
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const url = req.url
const html = fs.readFileSync('index.html', 'utf8')
console.log(url)
if(url === '/') {
res.writeHead(302, 
    {
        Location: '/wq'
    }
)
res.end('')
}
if(url === '/wq') {
res.writeHead(200, 
    {
        'Content-Type': 'text/html',
    }
)
res.end(html)
}
    
}).listen(3000)
console.log('server listen 3000')
複製代碼

301測試代碼跟上面基本同樣,只要將302改爲301便可,根據以上代碼測試,你會發現,每次在刷新頁面的時候,若是是302跳轉,那麼console.log(url)每次都會打印/ 和 /wq,若是是301的話,只會打印/wq,這就說明,302的跳轉是從服務端指定跳轉的,而301的跳轉則是永久性的,除非清楚本地瀏覽器的緩存,要麼沒法改變。

HTTP以內容安全策略

配置內容安全策略涉及到添加Content-Security-Policy,HTTP頭部到一個頁面,並配置相應的值,以控制用戶代理(瀏覽器等)能夠爲該頁面獲取哪些資源。更多詳細參考具體說明

經常使用示例以下:

// 一個網站管理者想要全部內容均來自站點的同一個源 (不包括其子域名)
'Content-Security-Policy': default-src 'self'

// 一個網站管理者容許內容來自信任的域名及其子域名 (域名沒必要須與CSP設置所在的域名相同)
'Content-Security-Policy': default-src 'self' *.trusted.com

// 該服務器僅容許經過HTTPS方式並僅從onlinebanking.jumbobank.com域名來訪問文檔
'Content-Security-Policy': default-src https://onlinebanking.jumbobank.com

// 限制向百度請求
'Content-Security-Policy': connect-src http://baidu.com

// 經過設定report-uri來指定上報服務器地址
'Content-Security-Policy': default-src 'self'; report-uri http://reportcollector.example.com/collector.cgi
複製代碼

HTTP之HTTP2

HTTP/2是HTTP協議自1999年HTTP1.1發佈後的首個更新,主要基於SPDY協議。它由互聯網工程任務組(IETF)的Hypertext Transfer Protocol Bis(httpbis)工做小組進行開發。該組織於2014年12月將HTTP/2標準提議遞交至IESG進行討論,於2015年2月17日被批准。HTTP/2標準於2015年5月以RFC 7540正式發表。

大體總結有如下特性:

  • 二進制分幀
  • 多路複用:同域名下全部通訊都在單個鏈接上完成;單個鏈接能夠承載任意數量的雙向數據流;數據流以消息的形式發送,而消息又由一個或多個幀組成,多個幀之間能夠亂序發送,由於根據幀首部的流標識能夠從新組裝;
  • 服務器推送:服務端能夠在發送頁面HTML時主動推送其它資源,而不用等到瀏覽器解析到相應位置,發起請求再響應。
  • 頭部壓縮:HTTP/2對消息頭採用HPACK(專爲http2頭部設計的壓縮格式)進行壓縮傳輸,可以節省消息頭佔用的網絡的流量。而HTTP/1.x每次請求,都會攜帶大量冗餘頭信息,浪費了不少帶寬資源。

貼一張簡圖,能夠更好的體現,更多詳情能夠參考

爲了能更好的體驗實際場景中的效果,作了簡單的測試,使用express配合node開啓http2來測試具體效果,在這以前,須要生成一個SSL,生成代碼以下:

// 直接在cmd中運行
openssl req -x509 -nodes -newkey rsa:2048 -keyout example.com.key -out example.com.crt
複製代碼

編寫基礎的server.js代碼,基本以下:

const port = 3000
const spdy = require('spdy')
const express = require('express')
const path = require('path')
const fs = require('fs')
const resolve = file => path.resolve(__dirname, file)
const app = express()

const serve = (path, cache) => express.static(resolve(path), {
  maxAge: cache ? 1000 * 60 * 60 * 24 * 30 : 0
})
app.use('/html', serve('./html', true))

app.get('/', (req, res) => {
    res.status(200)
    res.send('hello world')
})

app.get('/timg.jpeg', (req, res) => {
    const img = fs.readFileSync('/html/timg.jpeg')
    res.writeHead(200,
        {"Content-Type": 'image/jpeg'
    });
    res.send(img)
})

const options = {
    key: fs.readFileSync(__dirname + '/example.com.key'),
    cert:  fs.readFileSync(__dirname + '/example.com.crt')
}
spdy
    .createServer(options, app)
    .listen(port, (error) => {
    if (error) {
        console.error(error)
        return process.exit(1)
    } else {
        console.log('Listening on port: ' + port + '.')
    }
    })
複製代碼

根目錄的靜態文件定義fetch方法來請求100張圖片,這樣來測試請求的信道以及加載時長,基本代碼以下:

<!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>http2測試</title>
</head>
<body>
    <script> for(let i = 0; i < 100; i++) { fetch('//localhost:3000/html/timg.jpeg') } </script>
</body>
</html>
複製代碼

按照以上代碼運行,而且對比HTTP/1.1的效果以下(圖1是HTTP/1.1,圖2是HTTP2):

從圖1和圖2就能看出,圖2中Connection ID只有一個,而圖1中Connection ID會隨着請求的增長而增長,每增長一個信道,就會建立一次TCP連接,也就是須要通過三次握手,而且還受限瀏覽器自己的併發數(其中谷歌瀏覽器的最大併發數是6個),因此纔會出現等待的狀況;從Size那一列能看出,HTTP2中的請求數據也會小(由於自己數據就小,因此不明顯),這樣可以減小帶寬;從最終的Finsh時間能看出,一樣是100張圖片的請求,HTTP2耗時更少。

總結

以上內容主要是和HTTP相關的內容,因爲比較簡單,就不提供測試代碼壓縮包,基本從示例中直接複製粘貼就能本地測試運行,若是有什麼不正確的地方,歡迎提Issues

相關文章
相關標籤/搜索