HTTP口試指南

媒介

也許你在面試時遇到過如許的題目:從輸入URL到瀏覽器顯現頁面發作了什麼?
簡樸的回覆就是:

  1. DNS剖析
  2. TCP豎立銜接
  3. 發送HTTP請求
  4. 效勞器處置懲罰請求

    • 假如有緩存直接讀緩存
    • 沒有緩存返回相應內容
  5. TCP斷開銜接
  6. 瀏覽器剖析襯着頁面

假如你以為如許回覆過於簡樸,不如來深切相識一下吧。

收集基本

在此之前,先相識一下TCP/IP基本學問。

TCP/IP參考模子

《HTTP口試指南》

  • 初期的TCP/IP模子是一個四層構造,從下往上依次是收集接口層、互聯網層、傳輸層和應用層,厥後將收集接口層分別為了物理層和數據鏈路層

    • 應用層(Application)供應收集與用戶應用軟件之間的接口效勞
    • 傳輸層(Transimission)供應豎立、保護和作廢傳輸銜接功用,擔任可靠地傳輸數據(PC)
      傳輸層有兩個性子差別的協定:TCP(傳輸掌握協定)和UDP(用戶數據報協定)
    • 收集層(Network)處置懲罰收集間路由,確保數據實時傳送(路由器)
    • 數據鏈路層(DataLink)擔任無錯傳輸數據,確認幀、發錯重傳等(交換機)
    • 物理層(Physics)供應機器、電氣、功用和歷程特徵(網卡、網線、雙絞線、同軸電纜、中繼器)

各層經常運用協定

《HTTP口試指南》

這裡能夠看到HTTP協定是構建於TCP之上,屬於應用層協定

詳細歷程

1. DNS剖析

DNS效勞是和HTTP協定一樣位於應用層的協定,供應域名到IP地點的剖析效勞。

獲得IP地點后就能夠豎立銜接了,這裏另有兩個學問須要相識:

耐久銜接

耐久銜接(也稱為HTTP keep-alive)的特點是,只需恣意一段沒有提出斷開銜接,就堅持TCP銜接狀況。

《HTTP口試指南》

管線化

耐久銜接豎立后就能夠運用管線化發送了,能夠同時併發多個請求,不必守候一個接一個的相應。(在這裏我想到了流的pipe要領。)
《HTTP口試指南》

2. TCP銜接與斷開

2.1 TCP報文格式

《HTTP口試指南》
大抵說一下:

  1. 計算機經由過程端口號辨認接見哪一個效勞,比方http;源端口號舉行隨機端口,目標端口決議哪一個遞次舉行吸收
  2. 數據序號和確認序號用於保證傳輸數據的完整性和遞次
  3. 須要注重的是TCP的銜接、傳輸和斷開都受六個掌握位的批示(比方三次握手和四次揮手)

    • PSH(push迫切位)緩存區將滿,馬上速率傳輸
    • RST(reset重置位)銜接斷了從新銜接
    • URG(urgent緊要位)緊要信號
    • ACK(acknowlegement確認)為1就示意確認號
    • SYN(synchronous豎立聯機)同步序號位 TCP豎立銜接時將這個值設為1
  4. 用戶數據存儲了應用層天生的HTTP報文

相識了這些,那末最先講重點

2.2 TCP三次握手和四次揮手

三次握手
《HTTP口試指南》

  1. 客戶端先發送一個帶SYN標誌的數據包給效勞器端
  2. 效勞器收到后,回傳一個帶有SYN/ACK標誌的數據包示意確認收到
  3. 客戶端再發送一個帶SYN/ACK標誌的數據包,代表握手完畢

四次揮手
《HTTP口試指南》

  1. 客戶端向效勞器發出了FIN報文段
  2. 效勞器收到后,復興一個ACK應對
  3. 效勞器也向客戶端發送一個FIN報文段,隨後封閉了效勞器端的銜接
  4. 客戶端收到以後,又向效勞器復興一個ACK應對,過了一段計時守候,客戶端也封閉了銜接(計時守候是為了確認效勞器端已一般封閉)

四次揮手並非必定的,當效勞器已沒有內容發給客戶端了,就直接發送FIN報文段,如許就變成了三次揮手。

3. HTTP請求/相應

3.1 HTTP報文

HTTP報文大抵可分為報文首部和報文主體兩塊,兩者由空行(就相當於用了兩個換行符rnrn)來分別。報文主體並非一定要有的。

《HTTP口試指南》

3.1.1 請求報文

《HTTP口試指南》

經常運用請求行要領:

  • GET 獵取資本
  • POST 向效勞器端發送數據,傳輸實體主體
  • PUT 傳輸文件
  • HEAD 獵取報文首部
  • DELETE 刪除文件
  • OPTIONS 訊問支撐的要領
  • TRACE 追蹤途徑

3.1.2 相應報文

《HTTP口試指南》

說到相應報文,就必要談到狀況碼:

  • 2XX 勝利

    • 200(OK) 客戶端發過來的數據被一般處置懲罰
    • 204(Not Content) 一般相應,沒有實體
    • 206(Partial Content) 局限請求,返回部份數據,相應報文中由Content-Range指定實體內容
  • 3XX 重定向

    • 301(Moved Permanently) 永遠重定向
    • 302(Found) 暫時重定向,範例請求要領名穩定,然則都邑轉變
    • 303(See Other) 和302相似,但必須用GET要領
    • 304(Not Modified) 狀況未轉變 合營(If-Match、If-Modified-Since、If-None_Match、If-Range、If-Unmodified-Since) (一般緩存會返回304狀況碼)
  • 4XX 客戶端毛病

    • 400(Bad Request) 請求報文語法毛病
    • 401 (unauthorized) 須要認證
    • 403(Forbidden) 效勞器謝絕接見對應的資本
    • 404(Not Found) 效勞器上沒法找到資本
  • 5XX 效勞器端毛病

    • 500(Internal Server Error) 效勞器毛病
    • 503(Service Unavailable) 效勞器處於超負載或正在停機保護

3.1.3 首部

通用首部

首部字段名申明
Cache-Control掌握緩存行動
Connection銜接的治理
Date報文日期
Pragma報文指令
Trailer報文尾部的首部
Trasfer-Encoding指定報文主體的傳輸編碼體式格局
Upgrade晉級為其他協定
Via代辦效勞器信息
Warning毛病關照

請求首部

首部字段名申明
Accept用戶代辦可處置懲罰的媒體範例
Accept-Charset優先的字符集
Accept-Encoding優先的編碼
Accept-Langulage優先的言語
AuthorizationWeb認證信息
Expect期待效勞器的特定行動
From用戶的电子郵箱地點
Host請求資本地點的效勞器
If-Match比較實體標記
If-Modified-Since比較資本的更新時刻
If-None-Match比較實體標記
If-Range資本未更新時發送實體Byte的局限請求
If-Unmodified-Since比較資本的更新時刻(和If-Modified-Since相反)
Max-Forwards最大傳輸條數
Proxy-Authorization代辦效勞器須要客戶端認證
Range實體字節局限請求
Referer請求中的URI的原始獵取方
TE傳輸編碼的優先級
User-AgentHTTP客戶端遞次的信息

相應首部

首部字段名申明
Accept-Ranges是不是接收字節局限
Age資本的建立時刻
ETag資本的婚配信息
Location客戶端重定向至指定的URI
Proxy-Authenticate代辦效勞器對客戶端的認證信息
Retry-After再次發送請求的機遇
Server效勞器的信息
Vary代辦效勞器緩存的治理信息
www-Authenticate效勞器對客戶端的認證

實體首部

首部字段名申明
Allow資本可支撐的HTTP要領
Content-Encoding實體的編碼體式格局
Content-Language實體的自然言語
Content-Length實體的內容大小(字節為單元)
Content-Location替換對應資本的URI
Content-MD5實體的報文擇要
Content-Range實體的位置局限
Content-Type實體主體的媒體範例
Expires實體逾期時刻
Last-Modified資本的末了修正時刻

3.2 完成客戶端接見效勞端

建立HTTP效勞端

let http = require('http');
let app = http.createServer((req, res) => {// req是可讀流/res是可寫流
    // 獵取請求報文信息
    let method = req.method;// 要領
    let httpVersion = req.httpVersion;// HTTP版本
    let url = req.url;
    let headers = req.headers;
    console.log(method, httpVersion, url, headers);
    // 獵取請求體(假如請求體的數據大於64k,data事宜會被觸發屢次)
    let buffers = [];
    req.on('data', data => {
        buffers.push(data);
    })
    req.on('end', () => {
        console.log(Buffer.concat(buffers).toString());
        res.write('hello');
        res.end('world');
    })
})
// 監聽效勞器事宜
app.on('connection', socket => {
    console.log('豎立銜接');
});
app.on('close', () => {
    console.log('效勞器封閉')
});
app.on('error', err => {
    console.log(err);
});
app.listen(3000, () => {
    console.log('server is starting on port 3000');
});

建立客戶端

let http = require('http');
let options = {
    hostname: 'localhost',
    port: 3000,
    path: '/',
    method: 'GET',
    // 設置實體首部 通知效勞端我當前要給你發什麼樣的數據
    headers: {
        'content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': 15
    }
}
let req = http.request(options);
req.on('response', res => {
    res.on('data', chunk => {
        console.log(chunk.toString());
    });
});
req.end('name=js&&age=22')

然後運用node運轉我們的客戶端

《HTTP口試指南》

說了這麼多,你能夠已大抵相識了
從輸入URL到瀏覽器顯現頁面發作了什麼,不必多說,我們再來看一下緩存

4. 緩存

4.1 緩存作用

  • 減少了冗餘的數據傳輸,節省了網費。
  • 減少了效勞器的累贅, 大大提高了網站的機能
  • 加快了客戶端加載網頁的速率

4.2 緩存分類

強迫緩存

強迫緩存:說白了就是第一次請求數據時,效勞端將數據和緩存劃定規矩一併返回,下一次請求時瀏覽器直接依據緩存劃定規矩舉行推斷,有就直接讀緩存數據庫,不必銜接效勞器;沒有,再去找效勞器。
《HTTP口試指南》

對照緩存
  • 對照緩存,望文生義,須要舉行比較推斷是不是能夠運用緩存。
  • 瀏覽器第一次請求數據時,效勞器會將緩存標識與數據一同返回給客戶端,客戶端將兩者備份至緩存數據庫中。
  • 再次請求數據時,客戶端將備份的緩存標識發送給效勞器,效勞器依據緩存標識舉行推斷,推斷勝利后,返回304狀況碼,關照* 客戶端比較勝利,能夠運用緩存數據。

《HTTP口試指南》

4.3 請求流程

第一次請求,此時沒有緩存

《HTTP口試指南》

第二次請求

《HTTP口試指南》

從上張圖我們能夠看到,推斷緩存是不是可用,有兩種體式格局

  • ETag是實體標籤的縮寫,依據實體內容天生的一段hash字符串,能夠標識資本的狀況。當資本發作轉變時,ETag也隨之發作變化。ETag是Web效勞端發生的,然後發給瀏覽器客戶端。
  • Last-Modified是此資本的末了修正時刻,

    • 假如客戶端在請求到的資本中發明實體首部里有Last-Modified聲明,再次請求就會在頭裡帶上if-Modified-Since字段
    • 效勞端收到請求后發明if-Modified-Since字段則與被請求資本的末了修正時刻舉行對照

說了這麼多,不如直接來完成一下緩存

經由過程末了修正時刻來推斷緩存是不是可用

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let app = http.createServer((req, res) => {
    // 依據url獵取客戶端要請求的文件途徑
    let { parsename } = url.parse(req.url);
    let p = path.join(__dirname, 'public', '.' + pathname);
    // fs.stat()用來讀取文件信息,文件末了修正時刻就是stat.ctime
    fs.stat(p, (err, stat) => {
        if (!err) {
            let since = req.headers['if-modified-since'];//客戶端發來的文件末了修正時刻
            if (since) {
                if (since === stat.ctime.toUTCString()) {//末了修正時刻相稱,讀緩存
                    res.statusCode = 304;
                    res.end();
                } else {
                    sendFile(req, res, p, stat);//末了修正時刻不相稱,返回新內容
                }
            } else {
                sendError(res);
            }
        }
    })
})
function sendError(res) {
    res.statusCode = 404;
    res.end();
}
function sendFile(req, res, p, stat) {
    res.setHeader('Cache-Control', 'no-cache');// 設置通用首部字段 掌握緩存行動
    res.setHeader('Last-Modified', stat.ctime.toUTCString());// 實體首部字段 資本末了修正時刻
    res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
    fs.createReadStream(p).pipe(res);
}
app.listen(3000, () => {
    console.log('server is starting on port 3000');
});

末了修正時刻存在題目:

1. 某些效勞器不能準確獲得文件的末了修正時刻, 如許就沒法經由過程末了修正時刻來推斷文件是不是更新了。

2. 某些文件的修正異常頻仍,在秒以下的時刻內舉行修正. Last-Modified只能準確到秒。

3. 一些文件的末了修正時刻轉變了,然則內容並未轉變。 我們不願望客戶端以為這個文件修正了。

4. 假如一樣的一個文件位於多個CDN效勞器上的時刻內容雖然一樣,修正時刻不一樣。

經由過程ETag來推斷緩存是不是可用

ETag就是依據文件內容來推斷,說白了就是採納MD5(md5並不叫加密算法,它不可逆,應當叫擇要算法)發生信息擇要,用擇要來舉行比對。

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
// crypto是node.js中完成加密和解密的模塊 詳細詳解請自行相識
let crypto = require('crypto');
let app = http.createServer((req, res) => {
    // 依據url獵取客戶端要請求的文件途徑
    let { parsename } = url.parse(req.url);
    let p = path.join(__dirname, 'public', '.' + pathname);
    // fs.stat()用來讀取文件信息,文件末了修正時刻就是stat.ctime
    fs.stat(p, (err, stat) => {
        let md5 = crypto.createHash('md5');//建立md5對象
        let rs = fs.createReadStream(p);
        rs.on('data', function (data) {
            md5.update(data);
        });
        rs.on('end', () => {
            let r = md5.digest('hex'); // 對文件舉行md5加密
            // 下次就拿最新文件的加密值 和客戶端請求來比較
            let ifNoneMatch = req.headers['if-none-match'];
            if (ifNoneMatch) {
                if (ifNoneMatch === r) {
                    res.statusCode = 304;
                    res.end();
                } else {
                    sendFile(req, res, p, r);
                }
            } else {
                sendFile(req, res, p, r);
            }
        });
    })
});
function sendError(res) {
    res.statusCode = 404;
    res.end();
}
function sendFile(req, res, p, stat) {
    res.setHeader('Cache-Control', 'no-cache');// 設置通用首部字段 掌握緩存行動
    res.setHeader('Etag', r);// 相應首部字段 資本的婚配信息
    res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8')
    fs.createReadStream(p).pipe(res);
}
app.listen(3000, () => {
    console.log('server is starting on port 3000');
});

末了

想深切進修http的同硯,我引薦一本書《圖解HTTP》
本人程度有限,有不足之處,望人人指出糾正。

    原文作者:acdseen
    原文地址: https://segmentfault.com/a/1190000014579248
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞