前端必須懂的計算機網絡知識—(HTTP)

計算機網絡在IT行業的重要性

IT即互聯網技術,從事的工做和網絡有很大的關係,前端要負責和後臺(服務器)進行交互,其必然得通過網絡,因此懂點網絡知識有很大的幫助。html

前端必須懂的計算機網絡知識系列文章:

網絡模型數據處理過程

模型數據處理過程

報文

請求報文的組成:

請求報文的組成1
請求報文的組成2

  1. 請求行
    • 方法:
      • GET 獲取資源
      • POST 向服務器端發送數據,傳輸實體主體
      • PUT 傳輸文件
      • HEAD 獲取報文首部
      • DELETE 刪除文件
      • OPTIONS 詢問支持的方法
      • TRACE 追蹤路徑
      • trace
    • 協議/版本號
    • URL(username:password@www.baidu.com:80/a.html?limi…
      1. 協議 (HTTP)
      2. 登陸信息(username:password)
      3. 主機名(www.baidu.com
      4. 端口號 (80)
      5. 路徑 (/a.html)
      6. 查詢參數 (limit=1)
      7. hahs值(hash,服務器收不到hash值,通常爲前端的路由跳轉)
  2. 請求頭
    • 通用首部(General Header)
    • 請求首部(Request Header)
    • 實體首部(Entity Header Fields)
  3. 請求體

響應報文的組成:

響應報文的組成1
響應報文的組成2

  1. 響應行
    • 協議/版本號
    • 狀態碼:
      • 1XX Informational(信息性狀態碼)
      • 2XX Success(成功狀態碼)
        1. 200(OK 客戶端發過來的數據被正常處理
        2. 204(Not Content 正常響應,沒有實體
        3. 206(Partial Content範圍請求,返回部分數據,響應報文中由content-Range指定實體內容)
      • 3XX Redirection(重定向)
        1. 301(Moved Permanently) 永久重定向
        2. 302(Found)臨時重定向,規範要求,方法名不變,可是都會改變
        3. 303(See Other) 和302相似,但必須用GET方法
        4. 304(Not Modified)狀態未改變,配合(If-Match、If-Modified-Since、If-None_Match、If-Range、If-Unmodified-Since)
        5. 307(Temporary Redirect) 臨時重定向,不應改變請求方法
      • 4XX Client Error(客戶端錯誤狀態碼)
        1. 400(Bad Request) 請求報文語法錯誤
        2. 401 (unauthorized) 須要認證
        3. 403(Forbidden) 服務器拒絕訪問對應的資源
        4. 404(Not Found) 服務器上沒法找到資源
      • 5XX Server Error(服務器錯誤狀態嗎)
        1. 500(Internal Server Error)服務器故障
        2. 503(Service Unavailable)服務器處於超負載或正在停機維護
    • 狀態碼緣由短語
  2. 響應頭
    • 通用首部(General Header)
    • 響應首部(Response Header)
    • 實體首部(Entity Header Fields)
  3. 響應體

報文首部

通用首部(通訊管理)

- 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	        資源的最後修改時間,用於緩存
複製代碼

報文首部的應用:

  1. 範圍請求,範圍請求在傳送大的媒體文件,或者與文件下載的斷點續傳功能搭配使用時很是有用:
//客戶端請求: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);
複製代碼
  1. 圖片防盜
// 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);
複製代碼
  1. 多語言提示信息
//客戶端請求頭: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);
複製代碼
  1. 緩存304
  • 強制緩存 ,強制緩存就是服務器和和客戶端說,多久以內不要再發請求,直接找緩存
  • 協商(對比)緩存,每次發請求,可是服務器能夠對比一下請求的內容,若是有緩存直接返回304,不然返回新的內容

強制緩存+最後修改時間對比緩存:前端

  1. 強制緩存cache-control: no - cache、Expires(瀏覽器處理字段)
  2. 時間對比緩存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 分發的時間不一樣 可能也會致使緩存失效
複製代碼

強制緩存+關鍵字對比緩存:node

  1. 強制緩存cache-control: no - cache、Expires(瀏覽器處理字段)
  2. 關鍵字對比緩存: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);
// 若是文件很是大,須要讀取文件的內容比對,影響性能
複製代碼
  1. 壓縮
//獲取瀏覽器支持的壓縮方式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);
複製代碼
  1. 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);

複製代碼
  1. 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);
複製代碼
  1. 代理
// 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);
複製代碼
  1. 跨域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的前端應用

  1. 客戶端會自動發一個/favicon.icon給服務端,因此在資源目錄下會放置一個/favicon.icon的文件
  2. http可以最多同時併發6個請求在同一個服務器,因此咱們要把資源分開放在不一樣的服務器上
  3. 每次請求的資源不能過大也不能太小,由於請求的資源是分段發送的,而且有必定的大小規定,因此過少形成浪費帶寬,過多會形成擁塞,因此纔會有壓縮和資源合併(雪碧圖)
  4. 異步加載的時候須要進行控制,避免頻繁煩的網絡請求
  5. 前端資源文件帶有hash值是爲了不強制緩存的不良影響,同時也是爲了版本控制

結語

IT即互聯網技術,從事的工做和網絡有很大的關係,前端要負責和後臺(服務器)進行交互,其必然得通過網絡,因此懂點網絡知識有很大的幫助。接下來會介紹:web

  • HTTPS

本文參考:跨域

  1. 計算機網絡
  2. 圖解http
相關文章
相關標籤/搜索