官方解釋,HTTP是一個應用層協議,由請求和響應構成,是一個標準的客戶端服務器模型。HTTP是一個無狀態的協議。通俗來講,就是規定服務端和客戶端通訊的一種規則。更多的是基於瀏覽器環境下使用,那麼從你瀏覽器輸入地址開始到最終頁面的呈現,到底通過了哪些過程呢?廢話很少說,先貼一張圖,以下: javascript
如上圖,就是http請求發起到返回的完證過程,本覺得本身對http
的瞭解還算能夠,可是乍一看圖仍是很蒙的,好比說若是讓你優化http過程,你該從何下手呢?我想大部分仍是比較關心request
和response
,以及數據返回以後的DOMContentLoad
和load
,至於http
中的一些配置並非很清楚,其實經過優化配置,一樣可以加速網頁的打開速度,所以,我大體總結一些關於http
中會常用的配置項,以及這些使用通常會在哪些場景中使用到。html
注意:上圖中的
domContentLoadedEventEnd
表明DOMContentLoaded事件完成的時間節點,也就是jQuery中的domready時間。load
表明的是onload
事件觸發和結束的時間節點。前端
這裏主要介紹JSONP
和CORS
跨域,現實場景中,以上兩種使用居多,因此其餘跨域方案不作詳細介紹。形成跨域的主要緣由主要是瀏覽器自己的同源策略
引發的。java
JSONP
可以實現跨域主要是由於瀏覽器上容許標籤上經過src/href加載外鏈路徑
,可是JSONP
只支持GET
請求,同時由於瀏覽器中url
長度的限制,所以JSONP
能傳輸的數據大小也有必定的限制。node
CORS
跨域可以支持的全部ajax的方法,固然,目前是支持ie9+,低版本暫時不支持,隨時互聯網的發展,相信低版本的瀏覽器會逐漸被淘汰。在只用CORS
只須要服務端可以開啓容許跨域的頭設置便可,也就是Access-Control-Allow-Origin
。git
跨域大體的流程圖以下: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')
複製代碼
GET
、HEAD
、POST
。text/plain
、multipart/form-data
、application/x-www-form-urlencoded
。Accept
、Accept-Language
、Content-Language
、Last-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
能夠配置其餘的方法,例如PUT
、DELETE
等。
細心的同窗可能會發現,根據以上代碼的確能夠經過預請求,可是若是再次刷新網頁,會發現仍然還會存在預請求,對於第一次預請求已經經過了,爲何一樣的請求還會再發送一次呢?其實這裏能夠作一個優化,減小預請求的發送。
經過設置Access-Control-Max-Age
來肯定預請求的有效時間,只要在有效時間內,就不會再次發送預請求了。
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個主要部分:
一、本地緩存,其實也就是本地資源的緩存。 二、服務器緩存,包含驗證緩存和非驗證緩存。
Set-Cookie
來設置cookie
,下次請求的時候,會自動帶上以前設置的cookie
(同域狀況下),取值類型是String/Array。max-age
和Expires
設置過時時間。Secure
來設置只能在https
的時候發送。HttpOnly
來設置沒法經過document.cookie
訪問。domain=
來設置該cookie
是否在同域下共享。以上內容基本能彙總成一張圖解釋,以下:
目前開發web網頁,全部請求默認都是Connection: keep-alive
,除非服務端手動去關閉配置(Connection: close
),使用keep-alive
能夠複用以前的請求的信道,這樣減小tcp
三次握手的時間(同域狀況下才能生效)。
web服務請求會攜帶如下信息內容:
服務端返回數據會攜帶如下信息內容:
Accept-Encoding
表明數據壓縮類型Accept-Language
表明數據語言類型固然,web請求也能夠自定義Content-type
來傳輸數據,通常在form
表單中比較經常使用,例如上傳文件,會指定Content-type:multipart/form-data
,這樣服務端就能接收上傳的文件信息內容。
瀏覽器能識別的重定向code
碼有兩種,分別是301
和302
,二者在使用上會有必定的區別,大體以下:
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
的跳轉則是永久性的,除非清楚本地瀏覽器的緩存,要麼沒法改變。
配置內容安全策略涉及到添加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/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正式發表。
大體總結有如下特性:
貼一張簡圖,能夠更好的體現,更多詳情能夠參考:
爲了能更好的體驗實際場景中的效果,作了簡單的測試,使用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
。