前端必須懂的計算機網路知識—(HTTP)
阿新 • • 發佈:2018-12-17
計算機網路在IT行業的重要性
IT即網際網路技術,從事的工作和網路有很大的關係,前端要負責和後臺(伺服器)進行互動,其必然得經過網路,所以懂點網路知識有很大的幫助。
前端必須懂的計算機網路知識系列文章:
網路模型資料處理過程
報文
請求報文的組成:
- 請求行
- 方法:
- GET 獲取資源
- POST 向伺服器端傳送資料,傳輸實體主體
- PUT 傳輸檔案
- HEAD 獲取報文首部
- DELETE 刪除檔案
- OPTIONS 詢問支援的方法
- TRACE 追蹤路徑
- trace
- 協議/版本號
- URL(username:[email protected]
- 協議 (HTTP)
- 登入資訊(username:password)
- 埠號 (80)
- 路徑 (/a.html)
- 查詢引數 (limit=1)
- hahs值(hash,伺服器收不到hash值,一般為前端的路由跳轉)
- 方法:
- 請求頭
- 通用首部(General Header)
- 請求首部(Request Header)
- 實體首部(Entity Header Fields)
- 請求體
響應報文的組成:
- 響應行
- 協議/版本號
- 狀態碼:
- 1XX Informational(資訊性狀態碼)
- 2XX Success(成功狀態碼)
- 200(OK 客戶端發過來的資料被正常處理
- 204(Not Content 正常響應,沒有實體
- 206(Partial Content範圍請求,返回部分資料,響應報文中由content-Range指定實體內容)
- 3XX Redirection(重定向)
- 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)
- 307(Temporary Redirect) 臨時重定向,不該改變請求方法
- 4XX Client Error(客戶端錯誤狀態碼)
- 400(Bad Request) 請求報文語法錯誤
- 401 (unauthorized) 需要認證
- 403(Forbidden) 伺服器拒絕訪問對應的資源
- 404(Not Found) 伺服器上無法找到資源
- 5XX Server Error(伺服器錯誤狀態嗎)
- 500(Internal Server Error)伺服器故障
- 503(Service Unavailable)伺服器處於超負載或正在停機維護
- 狀態碼原因短語
- 響應頭
- 通用首部(General Header)
- 響應首部(Response Header)
- 實體首部(Entity Header Fields)
- 響應體
報文首部
通用首部(通訊管理)
- Cache-Control 控制快取行為
- Connection 連結的管理
- Date 報文日期
- Pragma 報文指令
- Trailer 報文尾部的首部
- Trasfer-Encoding 指定報文主體的傳輸編碼方式
- Upgrade 升級為其他協議
- Via 代理伺服器資訊
- Warning 錯誤通知
複製程式碼
請求首部(請求資源的範圍、限制和處理)
- Accept 使用者代理可處理的媒體型別
- Accept-Charset 優先的字符集
- Accept-Encoding 優先的編碼
- Accept-Langulage 優先的語言
- Authorization Web 認證資訊
- 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-Agent HTTP 客戶端程式的信
複製程式碼
node分析請求報文
const http = require('http');
http.createServer(function (req,res) {
console.log(req.httpVersion)//列印請求行的協議版本號
console.log(req.url);//列印請求行的資源路徑
console.log(req.method)//列印請求行方法
console.log(req.headers);//列印求頭
//列印請求體,如果沒有請求體不會觸發data事件,一般用於POST、PUT等
req.on('data',function (data) {
console.log(data)
});
req.on('end',function (data) {//每次一定會觸發'end'
console.log(data);
})
}).listen(3000);
複製程式碼
響應首部(響應的資源資訊)
- Accept-Ranges 是否接受位元組範圍,用於206範圍請求
- 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 資源的最後修改時間,用於快取
複製程式碼
報文首部的應用:
- 範圍請求,範圍請求在傳送大的媒體檔案,或者與檔案下載的斷點續傳功能搭配使用時非常有用:
//客戶端請求:curl -v --header 'Range:bytes=0-3' localhost:4000
//download.txt => 1234567890\r\n1234567890\r\n1234567890\r\n
const http = require('http');
const fs = require('fs');
const size = fs.statSync('./download.txt').size;
http.createServer(function (req,res) {
let head = req.headers['range'];
if(head){
let [,start,end] = head.match(/(\d*)-(\d*)/);
start = start?Number(start):0;
end = end ? Number(end) : size-1;
console.log(start,end)
res.statusCode = 206;//狀態碼為206
res.setHeader('Accept-Ranges','bytes'); //伺服器表明這是一個範圍請求,範圍請求的單位為位元組
res.setHeader('Content-Length',end-start+1);//響應體的位元組
res.setHeader('Content-Range', `bytes ${start}-${end}/${size}`)//響應體在響應報文中的位置
fs.createReadStream('./download.txt',{start,end}).pipe(res);
}else{
fs.createReadStream('./download.txt').pipe(res);
}
}).listen(4000);
複製程式碼
- 圖片防盜
// Referer表示這個資源被哪個網站引用了
// 如果當前請求的referer和當前伺服器的域名不一樣表示圖片被盜了
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const {promisify} = require('util');
const stat = promisify(fs.stat);
let whiteList = ['localhost']
http.createServer(async function (req, res) {
let { pathname } = url.parse(req.url);
let realPath = path.join(__dirname, 'static', pathname);
let s = await stat(realPath);
if(s.isDirectory()){
fs.createReadStream(path.join(realPath, '/index.html')).pipe(res);
}else{
let refer = req.headers['referer'] || req.headers['referred'];
if (refer){ // 當前引用的資源有沒有refer
let host = req.headers.host.split(':')[0];
let r = url.parse(req.headers['referer']).hostname;
if (whiteList.includes(r) || r === host ){ //當前的請求網頁host和主機名refer相同說明有訪問許可權
fs.createReadStream(realPath).pipe(res);
}else{ // 沒有許可權就返回裂圖
fs.createReadStream(path.join(__dirname,'static/1.jpg')).pipe(res);
}
}else{
fs.createReadStream(realPath).pipe(res);
}
}
}).listen(5000);
複製程式碼
- 多語言提示資訊
//客戶端請求頭:Accept-Language: en,zh;q=0.8,ja;q=0.9
const messages = {
en: 'hello world',
ja: 'こんにちは、世界',
zh: '你好世界',
}
let defaultEncoding = 'zh'
const http = require('http');
let server = http.createServer(function (req,res) {
let lan = req.headers['accept-language'];
if (lan){
//en,zh;q=0.8,ja;q=0.9 => [en, zh;q=0.8 ,ja;q=0.9]
//並且按照權重排序
let lans = lan.split(',').map(l=>{
let q = l.split(';')[1] ? Number(l.split(';')[1].split('=')[1]):1;
return {
name: l.split(';')[0],q
}
}).sort((a,b)=>b.q-a.q);
let l = null
for (let key in lans){
if (messages[lans[key].name]){
l = messages[lans[key].name];
break;
}
}
console.log(l);
if(l){
res.setHeader('Content-Type', 'text/html;charset=utf8;')
res.end(l);
}else{
res.setHeader('Content-Type', 'text/html;charset=utf8;')
res.end(messages[defaultEncoding]);
}
}else{
res.setHeader('Content-Type','text/html;charset=utf8;')
res.end(messages[defaultEncoding])
}
}).listen(5000);
複製程式碼
- 快取304
- 強制快取 ,強制快取就是伺服器和和客戶端說,多久之內不要再發請求,直接找快取
- 協商(對比)快取,每次發請求,但是伺服器可以對比一下請求的內容,如果有快取直接返回304,否則返回新的內容
強制快取+最後修改時間對比快取:
- 強制快取cache-control: no - cache、Expires(瀏覽器處理欄位)
- 時間對比快取Last-Modified(伺服器傳送給瀏覽器) + if-modifed-since(伺服器從瀏覽器獲取)
const http = require('http');
const path = require('path');
const fs = require('fs');
const url = require('url');
const {promisify} = require('util');
const mime = require('mime');
const stat = promisify(fs.stat);
const static = path.join(__dirname,'static')
http.createServer(async function (req,res) {
let {pathname} = url.parse(req.url,true);
if(pathname === '/favicon.ico') return res.end();
let realPath = path.join(static,pathname);
let statObj = await stat(realPath);//請求的資源資訊
//強制請求
res.setHeader('Cache-Control','no-cache'); // 10s內不會再次發起請求
let time = req.headers['if-modified-since'];// 瀏覽器再次到來時 會帶上一個頭 if-modified-since的欄位
if(statObj.isFile()){
//對比快取,把當前檔案的最後修改時間傳送告訴給客戶端
res.setHeader('Last-Modified', statObj.ctime.toGMTString());
res.setHeader('Content-Type',mime.getType(realPath)+';chatset=utf8');
if (time === statObj.ctime.toGMTString()) { // 如果相等那就走快取吧
res.statusCode = 304;
res.end();
}else{
fs.createReadStream(realPath).pipe(res);
}
}
}).listen(3000);
// 最後修改時間 一般會有些誤差 時間可能不會那麼精確(一秒內改了多次)
// CDN 分發的時間不同 可能也會導致快取失效
複製程式碼
強制快取+關鍵字對比快取:
- 強制快取cache-control: no - cache、Expires(瀏覽器處理欄位)
- 關鍵字對比快取:Etag(伺服器傳送給瀏覽器) + if-none-match(伺服器從瀏覽器獲取)
const http = require('http');
const path = require('path');
const fs = require('fs');
const url = require('url');
const {promisify} = require('util');
const mime = require('mime');
const stat = promisify(fs.stat);
const crypto = require('crypto');
cosnt static = path.join(__dirname,'static');
http.createServer(async function (req,res) {
let {pathname} = url.parse(req.url,true);
if(pathname === '/favicon.ico') return res.end();
let realPath = path.join(static,pathname);
let statObj = await stat(realPath);
//強制快取
res.setHeader('Cache-Control','no-cache'); // 10s內不會再次發起請求
if(statObj.isFile()){
//對比快取:Etag內容為請求資源經過MD5加密之後
let rs = fs.createReadStream(realPath);
let md5 = crypto.createHash('md5');
rs.on('data',function (data) {
md5.update(data);
});
rs.on('end',function () {
let r = md5.digest('base64');
res.setHeader('Etag',r );
if (req.headers['if-none-match'] === r ){
res.statusCode = 304;
res.end();
}else{
res.setHeader('Content-Type', mime.getType(realPath) + ';chatset=utf8');
fs.createReadStream(realPath).pipe(res);
}
})
}
}).listen(3000);
// 如果檔案非常大,需要讀取檔案的內容比對,影響效能
複製程式碼
- 壓縮
//獲取瀏覽器支援的壓縮方式Accept-Encoding: gzip, deflate, br
//壓縮並且通知瀏覽器使用的壓縮方式Content-Encoding: gzip
const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
http.createServer(function (req,res) {
let encoding = req.headers['accept-encoding'];
if(encoding){
if (encoding.match(/\bgzip\b/)){
res.setHeader('Content-Encoding','gzip');
fs.createReadStream('./static/index.html').pipe(zlib.createGzip()).pipe(res);
} else if (encoding.match(/\bdeflate\b/)){
res.setHeader('Content-Encoding', 'deflate');
fs.createReadStream('./static/index.html').pipe(zlib.createDeflate()).pipe(res);
}else{
fs.createReadStream('./static/index.html').pipe(res);
}
}else{
fs.createReadStream('./static/index.html').pipe(res);
}
}).listen(3000);
複製程式碼
- cookie
//
const http = require('http');
const server = http.createServer(function (req,res) {
if(req.url === '/visit'){
if(req.headers['cookie']){
res.setHeader('Content-Type', 'text/html;charset=utf-8');
let queryObj=require('querystring').parse(req.headers['cookie');
queryObj.visit++;
res.setHeader('Set-Cookie', `visit=${queryObj.visit}; httpOnly`);
res.end('你是第' + queryObj.visit+ '次訪問')
}else{
res.setHeader('Content-Type','text/html;charset=utf8');
res.setHeader('Set-Cookie','visit=1; httpOnly');
res.end('你是第一次訪問')
}
}
}).listen(6000);
複製程式碼
- session
const http = require('http');
const uuid = require('uuid/v4');
const SESSION_ID = 'connet.sid'; // 卡號:110 = {m:1000}
cosnt session = {}
// csrf 加驗證碼 refer
let server = http.createServer(function (req, res) {
if (req.url === '/go') {
let cookies = require('querystring').parse(req.headers['cookie'],'; ','=');
if (cookies[SESSION_ID] && session[cookies[SESSION_ID]]) {
session[cookies[SESSION_ID]].m -= 200;
res.setHeader('Content-Type', 'text/html;charset=utf8');
res.end('你的卡有' + session[cookies[SESSION_ID]].m + '元');
} else {
let cardId = uuid();
session[cardId] = { m: 1000 };
res.setHeader('Set-Cookie', `${SESSION_ID}=${cardId}`);
res.setHeader('Content-Type', 'text/html;charset=utf8');
console.log(session)
res.end('你的卡有' + session[cardId].m + '元');
}
}
}).listen(8888);
複製程式碼
- 代理
// www.self1.cn 代理 localhost:3001
// www.self2.cn 代理 localhost:3002
const map = {
'www.self1.cn': 'http://localhost:3001',
'www.self2.cn': 'http://localhost:3002',
}
const httpProxy = require('http-proxy');
const http = require('http');
const proxy = httpProxy.createProxyServer();
http.createServer(function (req, res) {
let head = req.headers['host'];
proxy.web(req, res, {
target: map[head]
});
}).listen(80);
複製程式碼
- 跨域cros
const http = require('http')
http.createServer(function (req, res) {
res.header("Access-Control-Allow-Origin", "*");//允許的域名( * 所有域)
res.header("Access-Control-Allow-Headers", "X-Requested-With");/伺服器支援的頭資訊
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");//允許的方法
}).listen(9000, function () {
console.log('server is runing at 9000')
})
複製程式碼
HTTP和TCP的前端應用
- 客戶端會自動發一個/favicon.icon給服務端,所以在資源目錄下會放置一個/favicon.icon的檔案
- http能夠最多同時併發6個請求在同一個伺服器,所以我們要把資源分開放在不同的伺服器上
- 每次請求的資源不能過大也不能過小,因為請求的資源是分段傳送的,並且有一定的大小規定,所以過少造成浪費頻寬,過多會造成擁塞,所以才會有壓縮和資源合併(雪碧圖)
- 非同步載入的時候需要進行控制,避免頻繁煩的網路請求
- 前端資原始檔帶有hash值是為了避免強制快取的不良影響,同時也是為了版本控制
結語
IT即網際網路技術,從事的工作和網路有很大的關係,前端要負責和後臺(伺服器)進行互動,其必然得經過網路,所以懂點網路知識有很大的幫助。接下來會介紹:
- HTTPS
本文參考:
- 計算機網路
- 圖解http
作者:夢想攻城獅 連結:https://juejin.im/post/5ba9d5075188255c652d4208 來源:掘金 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。