“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”
http1.1在带宽优化以及网络连接的使用有了很大的进步,引入range字段,可以让我们实现断点续传的功能,这个#对于在校生真的是太爽了,原来某度网盘在下载的时候慢就不说了,在下载到一半校园网没了,再次下载就要重头开始。
HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
实践需求:对于视频的播放,如果上一次用户已经看了一部分的视频,那么下次再次访问的时候前面的视频自然是不想继续观看的了,如果每次请求还是从头开始,那就非常的下头了。
基本流程
-
浏览器发送请求请求对应的资源。
Accept-Ranges:告知浏览器,该资源可以接受部分请求,并带上范围,单位是字节数。 -
服务器告知浏览器,对应的资源允许接受部分请求。
Range:开始-结束告知服务器,我所要请请求的资源范围,这个范围是左闭右闭区间的,如果缺失一边有以下 含义: 1.例子:-结束 开始位置消失,表示从文件的开始位置到我声明的结束位置。 2.例子:开始- 结束位置消失,表示从开始位置,一直到文件的结尾。 -
浏览器发送请求告知服务器所要请求资源的范围。
Content-Range:*/(文件总大小) 返回文件的可用的字节数(文件的开始位置默认是0开始的) -
浏览器对该范围进行判定,如果在合理的范围中,就返回该部分内容,如果不合理那么就通知浏览器,并带上可用的范围。
测试结构
客户端代码
<!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进行部分请求。