设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

Node.js中实现HTTP 206内容分片

2014-9-12 10:02| 发布者: joejoe0332| 查看: 3233| 评论: 0|原作者: LeoG0816, LeoXu|来自: 开源中国社区

摘要: 在本文中,我会阐述HTTP状态206 分部分内容 的基础概念,并使用Node.js一步步地实现它. 我们还将用一个基于它用法最常见场景的示例来测试代码:一个能够在任何时间点开始播放视频文件的HTML5页面. ...

内容表

介绍

在本文中,我会阐述HTTP状态206 分部分内容 的基础概念,并使用Node.js一步步地实现它. 我们还将用一个基于它用法最常见场景的示例来测试代码:一个能够在任何时间点开始播放视频文件的HTML5页面. 

Partial Content 的简要介绍

HTTP 的 206 Partial Content 状态码和其相关的消息头提供了让浏览器以及其他用户代理从服务器接收部分内容而不是全部内容,这样一种机制. 这一机制被广泛使用在一个被大多数浏览器和诸如Windows Media Player和VLC Player这样的播放器所支持视频文件的传输上.

基础的流程可以用下面这几步描述:

  1. 浏览器请求内容.

  2. 服务器告诉浏览器,该内容可以使用 Accept-Ranges 消息头进行分部分请求.

  3. 浏览器重新发送请求,用 Range 消息头告诉服务器需要的内容范围.

  4. 服务器会分如下两种情况响应浏览器的请求: 

    • 如果范围是合理的,服务器会返回所请求的部分内容,并带上 206 Partial Content 状态码. 当前内容的范围会在 Content-Range 消息头中申明.

    • 如果范围是不可用的(例如,比内容的总字节数大), 服务器会返回 416 请求范围不合理 Requested Range Not Satisfiable 状态码. 可用的范围也会在 Content-Range 消息头中声明.

让我们来看看这几个步骤中的每一个关键消息头.

Accept-Ranges: 字节(bytes)

这是会有服务器发送的字节头,展示可以被分部分发送给浏览器的内容. 这个值声明了可被接受的每一个范围请求, 大多数情况下是字节数 bytes.

Range: 字节数(bytes)=(开始)-(结束)

这是浏览器告知服务器所需分部分内容范围的消息头. 注意开始和结束位置是都包括在内的,而且是从0开始的. 这个消息头也可以不发送两个位置,其含义如下: 

  • 如果结束位置被去掉了,服务器会返回从声明的开始位置到整个内容的结束位置内容的最后一个可用字节.

  • 如果开始位置被去掉了,结束位置参数可以被描述成从最后一个可用的字节算起可以被服务器返回的字节数.

Content-Range:字节数(bytes)=(开始)-(结束)/(总数)

这个消息头将会跟随 HTTP 状态码 206 一起出现. 开始和结束的值展示了当前内容的范围. 跟 Range 消息头一样, 两个值都是包含在内的,并且也是从零开始的. 总数这个值声明了可用字节的总数.


Content-Range: */(总数)

这个头信息和上面一个是一样的,不过是用另一种格式,并且仅在返回HTTP状态码416时被发送。其中总数代表了正文总共可用的字节数。

这里有一对有2048个字节文件的例子。注意省略起点和重点的区别。

请求开始的1024个字节

浏览器发送:

1
2
3
GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=0-1023

服务器返回:

1
2
3
4
5
6
7
HTTP/1.1 216 Partial Content
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Type: video/mp4
Content-Range: bytes 0-1023/2048
Content-Length: 1024
 
(Content...)

没有终点位置的请求

浏览器发送:

1
2
3
GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=1024-

服务器返回:

1
2
3
4
5
6
7
HTTP/1.1 216 Partial Content
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Type: video/mp4
Content-Range: bytes 1024-2047/2048
Content-Length: 1024
 
(Content...)

注意:服务器并不需要在单个响应中返回所有剩下的字节,特别是当正文太长或者有其他性能的考虑。所以下面的两个例子在这种情况下也是可接受的:

1
2
Content-Range: bytes 1024-1535/2048
Content-Length: 512

服务器仅返回剩余正文的一半。下一次请求的范围将从第1536个字节开始。

1
2
Content-Range: bytes 1024-1279/2048
Content-Length: 256

服务器仅返回剩余正文的256个字节。下一次请求的范围将从第1280个字节开始。


请求最后512个字节

浏览器发送:

1
2
3
GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=-512

服务器返回:

1
2
3
4
5
6
7
HTTP/1.1 216 Partial Content
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Type: video/mp4
Content-Range: bytes 1536-2047/2048
Content-Length: 512
 
(Content...)

请求不可用的范围:

浏览器发送:

1
2
3
GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=1024-4096

服务器返回:

1
2
3
HTTP/1.1 416 Requested Range Not Satisfiable
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Range: bytes */2048

理解了工作流和头部信息后,现在我们可以用Node.js去实现这个机制。


开始用Node.js实现

第一步:创建一个简单的HTTP服务器

我们将像下面的例子那样,从一个基本的HTTP服务器开始。这已经可以基本足够处理大多数的浏览器请求了。首先,我们初始化我们需要用到的对象,并且用initFolder来代表文件的位置。为了生成Content-Type头部,我们列出文件扩展名和它们相对应的MIME名称来构成一个字典。在回调函数httpListener()中,我们将仅允许GET可用。如果出现其他方法,服务器将返回405 Method Not Allowed,在文件不存在于initFolder,服务器将返回404 Not Found

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 初始化需要的对象
var http = require("http");
var fs = require("fs");
var path = require("path");
var url = require("url");
 
// 初始的目录,随时可以改成你希望的目录
var initFolder = "C:\\Users\\User\\Videos";
 
// 将我们需要的文件扩展名和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请求,否则返回405 'Method Not Allowed'
    if (request.method != "GET") { 
        sendResponse(response, 405, {"Allow" "GET"}, null);
        return null;
    }
 
    var filename = 
        initFolder + url.parse(request.url, truetrue).pathname.split('/').join(path.sep);
 
    var responseHeaders = {};
    var stat = fs.statSync(filename);
    // 检查文件是否存在,不存在就返回404 Not Found
    if (!fs.existsSync(filename)) {
        sendResponse(response, 404, nullnull);
        return null;
    }
    responseHeaders["Content-Type"] = getMimeNameFromExt(path.extname(filename));
    responseHeaders["Content-Length"] = stat.size; // 文件大小
         
    sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));
}
 
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;
}


步骤 2 - 使用正则表达式捕获Range消息头

有了这个HTTP服务器做基础,我们现在就可以用如下代码处理Range消息头了. 我们使用正则表达式将消息头分割,以获取开始和结束字符串。然后使用 parseInt() 方法将它们转换成整形数. 如果返回值是 NaN (非数字not a number), 那么这个字符串就是没有在这个消息头中的. 参数totalLength展示了当前文件的总字节数. 我们将使用它计算开始和结束位置. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
}



酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部