前端必须懂的计算机网络知识—(HTTP)

2,473 阅读7分钟

计算机网络在IT行业的重要性

IT即互联网技术,从事的工作和网络有很大的关系,前端要负责和后台(服务器)进行交互,其必然得经过网络,所以懂点网络知识有很大的帮助。

前端必须懂的计算机网络知识系列文章:

网络模型数据处理过程

模型数据处理过程

报文

请求报文的组成:

请求报文的组成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 分发的时间不同 可能也会导致缓存失效

强制缓存+关键字对比缓存:

  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即互联网技术,从事的工作和网络有很大的关系,前端要负责和后台(服务器)进行交互,其必然得经过网络,所以懂点网络知识有很大的帮助。接下来会介绍:

  • HTTPS

本文参考:

  1. 计算机网络
  2. 图解http