http1.1带宽优化以及网络连接的使用

249 阅读4分钟

“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”

http1.1在带宽优化以及网络连接的使用有了很大的进步,引入range字段,可以让我们实现断点续传的功能,这个#对于在校生真的是太爽了,原来某度网盘在下载的时候慢就不说了,在下载到一半校园网没了,再次下载就要重头开始。

HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。

实践需求:对于视频的播放,如果上一次用户已经看了一部分的视频,那么下次再次访问的时候前面的视频自然是不想继续观看的了,如果每次请求还是从头开始,那就非常的下头了。

基本流程

  • 浏览器发送请求请求对应的资源。

      Accept-Ranges:告知浏览器,该资源可以接受部分请求,并带上范围,单位是字节数。
    
  • 服务器告知浏览器,对应的资源允许接受部分请求。

      Range:开始-结束告知服务器,我所要请请求的资源范围,这个范围是左闭右闭区间的,如果缺失一边有以下
      含义:
      1.例子:-结束  开始位置消失,表示从文件的开始位置到我声明的结束位置。
      2.例子:开始-  结束位置消失,表示从开始位置,一直到文件的结尾。
    
  • 浏览器发送请求告知服务器所要请求资源的范围。

      Content-Range:*/(文件总大小) 返回文件的可用的字节数(文件的开始位置默认是0开始的)
    
  • 浏览器对该范围进行判定,如果在合理的范围中,就返回该部分内容,如果不合理那么就通知浏览器,并带上可用的范围。

测试结构

image.png

客户端代码

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript">
 
      function onLoad() {
        var sec = parseInt(document.location.search.substr(1));
         
        if (!isNaN(sec))
          mainPlayer.currentTime = sec;
      }
     
    </script>
    <title>部分文件的上传测试</title>
  </head>
  <body>
    <h3>部分文件的上传测试</h3>
    <hr />
    <video id="mainPlayer" width="640" height="360" 
      autoplay="autoplay" controls="controls" onloadedmetadata="onLoad()">
      <source src="./test.mp4" />
    </video>
  </body>
</html>

这里我们的客户端是一个视频的请求网页,如果在请求的路径后面存在数字,那么表示我们要进行部分的请求,以此来模拟用户拖拽播放条请求部分资源的需求。

服务端代码

// 初始化需要的对象
var http = require("http");
var fs = require("fs");
var path = require("path");
var url = require("url");
 
// 初始的目录,随时可以改成你希望的目录
var initFolder = "D:\\vscode-code\\HTML\\range\\initFile";
 
// 将我们需要的文件扩展名和MIME名称列出一个字典
var mimeNames = {
  ".css": "text/css",
  ".html": "text/html",
  ".js": "application/javascript",
  ".mp3": "audio/mpeg",
  ".mp4": "video/mp4",
  ".ogg": "application/ogg", 
  ".ogv": "video/ogg", 
  ".oga": "audio/ogg",
  ".txt": "text/plain",
  ".wav": "audio/x-wav",
  ".webm": "video/webm"
};
 
http.createServer(httpListener).listen(8000);
 
function httpListener(request, response) {
    // 我们只对GET进行处理
    if (request.method != 'GET') {
      sendResponse(response, 405, { 'Allow': 'GET' }, null);
      return null;
    }
   
    var filename =
      initFolder + url.parse(request.url, true, true).pathname.split('/').join(path.sep);
   
    // 检查资源是否存在,不存在就返回404
    if (!fs.existsSync(filename)) {
      sendResponse(response, 404, null, null);
      return null;
    }
   
    var responseHeaders = {};
    var stat = fs.statSync(filename);
    var rangeRequest = readRangeHeader(request.headers['range'], stat.size);
   
    // 是否存在range头
    if (rangeRequest == null) {
      responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
      responseHeaders['Content-Length'] = stat.size; // File size.
      responseHeaders['Accept-Ranges'] = 'bytes';
   
      // 直接返回文件
      sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));
      return null;
    }
   
    var start = rangeRequest.Start;
    var end = rangeRequest.End;
   
    // 范围是否合理
    if (start >= stat.size || end >= stat.size) {
      // 声明合理的范围
      responseHeaders['Content-Range'] = 'bytes */' + stat.size; // File size.
   
      // 返回416表示无法满足该范围
      sendResponse(response, 416, responseHeaders, null);
      return null;
    }
   
    // 声明当前的range位置
    responseHeaders['Content-Range'] = 'bytes ' + start + '-' + end + '/' + stat.size;
    responseHeaders['Content-Length'] = start == end ? 0 : (end - start + 1);
    responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
    responseHeaders['Accept-Ranges'] = 'bytes';
    responseHeaders['Cache-Control'] = 'no-cache';
   
    //返回206和部分文件
    sendResponse(response, 206, 
      responseHeaders, fs.createReadStream(filename, { start: start, end: end }));
  }
  
 
function sendResponse(response, responseStatus, responseHeaders, readable) {
  response.writeHead(responseStatus, responseHeaders);
 
  if (readable == null)
    response.end();
  else
    readable.on("open", function () {
      readable.pipe(response);
    });
 
  return null;
}
 
function getMimeNameFromExt(ext) {
  var result = mimeNames[ext.toLowerCase()];
   
  // 最好给一个默认值
  if (result == null)
    result = "application/octet-stream";
   
  return result;
}
function readRangeHeader(range, totalLength) {
    /*
     * Example of the method ';split'; with regular expression.
     * 
     * Input: bytes=100-200
     * Output: [null, 100, 200, null]
     * 
     * Input: bytes=-200
     * Output: [null, null, 200, null]
     */
 
  if (range == null || range.length == 0)
    return null;
 
  var array = range.split(/bytes=([0-9]*)-([0-9]*)/);
  var start = parseInt(array[1]);
  var end = parseInt(array[2]);
  var result = {
    Start: isNaN(start) ? 0 : start,
    End: isNaN(end) ? (totalLength - 1) : end
  };
   
  if (!isNaN(start) && isNaN(end)) {
    result.Start = start;
    result.End = totalLength - 1;
  }
 
  if (isNaN(start) && !isNaN(end)) {
    result.Start = totalLength - end;
    result.End = totalLength - 1;
  }
 
  return result;
}

测试

输入http://localhost:8000/index.html进行正常请求。
输入http://localhost:8000/player.html?80进行部分请求。

打开浏览器的网络模块我们可以看到range字段生效了,实现了文件的部分请求。

未带数字从0开始请求

image.png

带了数子80从4816896开始请求

image.png