宝哥软件园

使用Node.js实现HTTP 206内容分片教程

编辑:宝哥软件园 来源:互联网 时间:2021-09-09

介绍

在本文中,我将解释HTTP status 206的划分内容的基本概念,并使用Node.js逐步实现它我们还将基于其最常见的使用场景用一个例子来测试代码:HTML5页面简介。可以随时开始播放视频文件的部分内容。

HTTP的206 Partial Content状态代码及其相关标头为浏览器和其他用户代理提供了一种机制,可以从服务器接收部分内容,但不能接收全部内容。这种机制广泛应用于Windows Media Player、VLC Player等大多数浏览器和播放器支持的视频文件传输中。

基本过程可以描述为以下步骤:

浏览器请求内容。服务器告诉浏览器,可以使用接受范围标题来部分请求内容。浏览器重新发送请求,并通过使用范围头告诉服务器所需的内容范围。服务器将在以下两种情况下响应浏览器请求:

如果该范围是合理的,服务器将返回具有206部分内容状态代码的部分请求内容。当前内容范围将在内容范围标题中声明。如果范围不可用(例如,它大于内容的总字节数),服务器将返回416请求范围不可满足的状态代码,这是不合理的。可用范围也将在内容范围标题中声明。让我们看看这些步骤中的每个关键标题。

接受-范围:字节(字节)

这是服务器发送的字节头,显示了可以按节分发给浏览器的内容。该值声明了每个可以接受的范围请求,在大多数情况下,它是字节数。

范围:字节)=(开始)-(结束)

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

如果删除了结束位置,服务器将从声明的开始位置返回最后一个可用字节到整个内容的结束位置。如果删除开始位置,结束位置参数可以描述为服务器从最后一个可用字节返回的字节数。内容-范围:字节)=(开始)-(结束)/(总数)

该消息头将与HTTP状态代码206一起出现。开始和结束值显示当前内容的范围。与范围标题一样,两个值都包含在内,并且从零开始。total值声明可用字节的总数。内容-范围: */(总数)

该报头信息与上述相同,但采用另一种格式,并且仅在返回HTTP状态代码416时发送。其中,总数表示正文可用的字节总数。

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

请求前1024个字节

浏览器发送:

get/dota 2/techies . MP4 http/1.1 host : localhost :8000范围:字节=0-1023服务器返回:

Http/1.1 206部分内容日期: mon,2014年9月15日22:19336034 GMT内容类型:视频/MP4内容-范围:字节0-1023/2048内容-长度3360 1024(内容.)没有结束位置的请求

浏览器发送:

get/dota 2/techies . MP4 http/1.1 host : localhost :8000范围:字节=1024-服务器返回:

HTTP/1.1 206 Partial content date : Mon,2014年9月15日22:19336034 GMT内容类型:视频/MP4内容-范围:字节1024-2047/2048内容-长度3360 1024(内容.)注意:服务器不需要在单个响应中返回所有剩余的字节,尤其是当文本太长或其他情况时。所以下面两个例子在这种情况下也是可以接受的:

内容范围:字节1024-1535/2048内容长度: 512服务器只返回剩余文本的一半。下一个请求的范围将从第1536个字节开始。

内容-范围:字节1024-1279/2048内容-长度: 256服务器仅返回剩余正文的256个字节。下一次请求的范围将从第1280个字节开始。

请求最后512个字节

浏览器发送:

GET/dota 2/技术人员。MP4 HTTP/1.1主机:本地主机33608000范围:字节=-512服务器返回:

HTTP/1.1 206部分内容日期: 2014年9月15日星期一22:19:34格林尼治标准时间内容类型:视频/MP4内容范围:字节1536-2047/2048内容长度: 512(内容.)请求不可用的范围:

浏览器发送:

GET/dota 2/技术人员。MP4 HTTP/1.1主机:本地主机33608000范围:字节=1024-4096服务器返回:

HTTP/1.1 416请求范围未满足: 2014年9月15日星期一22:19:34 GMT内容-范围:字节*/2048理解了工作流和头部信息后,现在我们可以用Node.js去实现这个机制。

开始用Node.js实现

第一步:创建一个简单的超文本传送协议服务器

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

//初始化需要的对象var http=require(' http ');var fs=require(' fs ');var path=require(' path ');var URL=必选(' URL ');//初始的目录,随时可以改成你希望的目录var initFolder=' C: 用户 用户 视频;//将我们需要的文件扩展名和哑剧名称列出一个字典var mimeNames={ ' .CSS“:”文本/css ",html': '文本/html ',' .js': '应用程序/javascript ',mp3': '音频/mpeg ',mp4': '视频/mp4 ',' .ogg': '应用程序/ogg ',' .ogv': 'video/ogg ',' .oga': 'audio/ogg ',' .txt': '文本/纯文本','。wav': 'audio/x-wav ',' .webm': '视频/webm ';};http.createServer(httpListener).听(8000);函数httpListener(请求,响应){ //我们将只接受得到请求,否则返回405"不允许方法“如果(request.method!='GET') { sendResponse(response,405,{'Allow' : 'GET'},null);返回null} var filename=initFolder URL。解析(请求。URL,true,true).pathname.split('/').join(路径。sep);var响应头={ };var stat=fs.statSync(文件名);//检查文件是否存在,不存在就返回404未找到如果(!fs。existssync(filename)){发送响应(响应,404,null,null);返回null } responseHeaders[' Content-Type ']=从ext(路径)获取mimename。扩展名(文件名));响应头['内容-长度']=stat。大小;//文件大小sendResponse(响应,200,responseHeaders,fs.createReadStream(文件名));}函数发送响应(响应状态,响应头,可读){回应。写头(响应状态、响应头);如果(可读==null)响应。end();else可读. on('open ',function(){可读。管道(响应);});返回null}函数getmimenamelfromtext(ext){ var result=mimeNames[ext。tolowercase()];//最好给一个默认值if(result==null)result=' application/octet-stream ';返回结果;}步骤2 - 使用正则表达式捕获范围消息头

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

函数readRangeHeader(range,TotalEngth){/* *使用正则表达式"拆分"方法的示例* *输入:字节=100-200 *输出: [null,100,200,null]* *输入3360字节=-200 * Output: [null,null,200,null]*/if(range==null | | range。长度==0)返回nullvar数组=范围。split(/bytes=([0-9]*)-([0-9]*)/);var start=parseInt(数组[1]);var end=parseInt(数组[2]);定义变量结果={ Start: isNaN(start)?0 :开始,End: isNaN(结束)?(合计长度-1): end };if(!isNaN(开始)isNaN(结束)){结果。开始=开始;结果结束=合计长度-1;} if (isNaN(start)!isNaN(end)) {结果.开始=总计-结束;结果结束=合计长度-1;}返回结果;}步骤3 - 检查数据范围是否合理

回到函数httpListener(),在超文本传送协议方法通过之后,现在我们来检查请求的数据范围是否可用。如果浏览器没有发送范围消息头过来,请求就会直接被当做一般的请求对待。服务器会返回整个文件,HTTP状态将会是200好的。另外我们还会看看开始和结束位置是否比文件长度更大或者相等。只要有一个是这种情况,请求的数据范围就是不能被满足的。返回的状态就将会是416请求的范围无法满足而内容-范围也会被发送。

var响应头={ };var stat=fs.statSync(文件名);var范围请求=readrange标头(请求。标题['范围'],stat。大小);//如果范围头存在,我们用正则表达式解析if(范围请求==null){ response headers[' Content-Type ']=从ext(路径)获取mimename。extname(文件名));响应头['内容-长度']=stat。大小;//文件大小。响应头["接受范围"]="字节";//如果没有,将直接返回文件sendResponse(response,200,responseHeaders,fs.createReadStream(文件名));返回null} var start=rangeRequest .开始;var end=rangeRequest .结束;//如果范围不能满足if(start=stat。size | | end=stat。大小){//指出可接受的范围响应头['内容-范围']='字节*/' stat。大小;//文件大小。//返回416"请求的范围不可满足"。发送响应(响应,416,响应头,空);返回null}步骤4 - 满足请求

最后使人迷惑的一块来了。对于状态216部分内容,我们有另外一种格式的内容-范围消息头,包括开始,结束位置以及当前文件的总字节数。我们也还有内容-长度消息头,其值就等于开始和结束位置之间的差。在最后一句代码中,我们调用了createReadStream()并将开始和结束位置的值给了第二个参数选项的对象,这意味着返回的流将只包含从开始到结束位置的只读数据。

//指示当前范围响应头['内容-范围']='字节'开始'-'结束'/'状态。大小;响应头['内容-长度']=开始==结束?0 :(结束-开始1);response headers[' Content-Type ']=从ext(路径)获取mimename。扩展名(文件名));响应头["接受范围"]="字节";响应头['缓存-控制']='无缓存;//返回206"部分内容"。sendResponse(response,206,responseHeaders,fs.createReadStream(文件名,{ start: start,end : end });下面是完整的httpListener()回调函数。

函数httpListener(请求,响应){ //我们将只接受GET '方法。否则将返回405"不允许方法"。if (request.method!='GET') { sendResponse(response,405,{ 'Allow': 'GET' },null);返回null} var filename=initFolder URL。解析(请求。URL,true,true).pathname.split('/').join(路径。sep);//检查文件是否存在。如果没有,将返回404"未找到"。if(!fs。existssync(filename)){发送响应(响应,404,null,null);返回null } var response headers={ } var stat=fs . stat sync(文件名);var范围请求=readrange标头(请求。标题['范围'],stat。大小);//如果范围头存在,我们用正则表达式解析if(范围请求==null){ response headers[' Content-Type ']=从ext(路径)获取mimename。extname(文件名));响应头['内容-长度']=stat。大小;//文件大小。响应头["接受范围"]="字节";//如果没有,将直接返回文件sendResponse(response,200,responseHeaders,fs.createReadStream(文件名));返回null} var start=rangeRequest .开始;var end=rangeRequest .结束;//如果范围不能满足if(start=stat。size | | end=stat。大小){//指出可接受的范围响应头['内容-范围']='字节*/' stat。大小;//文件大小。//返回416"请求的范围不可满足"。发送响应(响应,416,响应头,空);返回null} //指示当前范围响应头['内容-范围']='字节'开始'-'结束'/'状态。大小;响应头['内容-长度']=开始==结束?0 :(结束-开始1);response headers[' Content-Type ']=从ext(路径)获取mimename。扩展名(文件名));响应头["接受范围"]="字节";响应头['缓存-控制']='无缓存;//返回206"部分内容"。sendResponse(response,206,responseHeaders,fs.createReadStream(文件名,{ start: start,end : end });}测试实现

我们怎么来测试我们的代码呢?就像在介绍中提到的,部分正文最常用的场景是流和播放视频。所以我们创建了一个身份为主播放器并包含一个来源/标签的视频/.函数onLoad()将在主播放器预读取当前视频的元数据时被触发,这用于检查在统一资源定位器中是否有数字参数,如果有,主播放器将跳到指定的时间点。

!DOCTYPE htmlhtml头脚本类型='text/javascript '函数OnLoad(){ var sec=ParSeint(文档。位置。搜索。substr(1));if(!isNaN(秒))mainPlayer.currentTime=秒;}/脚本标题部分内容演示/标题/标题正文h3部分内容演示/h3 hr /视频id=' main player ' width=' 640 ' height=' 360 ' autoplay=' autoplay ' controls=' controls ' on loaded metadata=' onLoad()' source src=' http : dota 2/techies。MP4 '/视频/正文/html

现在我们把页面保存为player.html并和dota2/techies.mp4 '一起放在initFolder目录下。然后在浏览器中打开网址:http://localhost :8000/播放器。超文本标记语言

在铬中看起来像这样:

2015623105803917.png  (680535)

因为在统一资源定位器中没有任何参数,文件将从最开始出播放。

接下来就是有趣的部分了。让我们试着打开这个然后看看发生了什么:http://localhost :8000/播放器。html?60

2015623105918021.png  (680535)

如果你按F12来打开铬的开发者工具,切换到网络标签页,然后点击查看最近一次日志的详细信息。你会发现范围的头信息(范围)被你的浏览器发送了:

范围:字节=225084502-很有趣,对吧?当函数onLoad()改变当前时间属性的时候,浏览器计算这部视频60秒处的字节位置。因为主播放器已经预加载了元数据,包括格式、比特率和其他基本信息,这个起始位置立刻就被得到了。之后,浏览器就可以下载并播放视频而不需要请求开头的60秒了。成功了!

结论

我们已经使用Node.js实现了支持部分文本的HTTP服务器。我们还用HTML5页面进行了测试。但这只是开始。如果你对头文件信息和工作流有透彻的理解,你可以尝试用其他框架实现它,比如ASP.NET MVC或者WCF服务。但是不要忘记启动任务管理器来检查CPU和内存的使用情况。正如我们前面讨论的,服务器没有返回单个响应中使用的剩余字节。找到性能的平衡将是一项重要的任务。

更多资讯
游戏推荐
更多+