成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Node.js中實現HTTP 206內容分片

開發 前端
在本文中,我會闡述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. GET /dota2/techies.mp4 HTTP/1.1  
  2. Host: localhost:8000  
  3. Range: bytes=0-1023 

服務器返回:

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

沒有終點位置的請求

瀏覽器發送:

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

服務器返回:

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

注意:服務器并不需要在單個響應中返回所有剩下的字節,特別是當正文太長或者有其他性能的考慮。所以下面的兩個例子在這種情況下也是可接受的:

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

服務器僅返回剩余正文的一半。下一次請求的范圍將從第1536個字節開始。

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

服務器僅返回剩余正文的256個字節。下一次請求的范圍將從第1280個字節開始。

 

服務器返回:

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

請求不可用的范圍:

瀏覽器發送:

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

服務器返回:

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

理解了工作流和頭部信息后,現在我們可以用Node.js去實現這個機制。

#p#

第一步:創建一個簡單的HTTP服務器

我們將像下面的例子那樣,從一個基本的HTTP服務器開始。這已經可以基本足夠處理大多數的瀏覽器請求了。首先,我們初始化我們需要用到的對象,并且用initFolder來代表文件的位置。為了生成Content-Type頭部,我們列出文件擴展名和它們相對應的MIME名稱來構成一個字典。在回調函數httpListener()中,我們將僅允許GET可用。如果出現其他方法,服務器將返回405 Method Not Allowed,在文件不存在于initFolder,服務器將返回404 Not Found

  1. // 初始化需要的對象  
  2. var http = require("http");  
  3. var fs = require("fs");  
  4. var path = require("path");  
  5. var url = require("url");  
  6.    
  7. // 初始的目錄,隨時可以改成你希望的目錄  
  8. var initFolder = "C:\\Users\\User\\Videos";  
  9.    
  10. // 將我們需要的文件擴展名和MIME名稱列出一個字典  
  11. var mimeNames = {  
  12.     ".css""text/css",  
  13.     ".html""text/html",  
  14.     ".js""application/javascript",  
  15.     ".mp3""audio/mpeg",  
  16.     ".mp4""video/mp4",  
  17.     ".ogg""application/ogg",   
  18.     ".ogv""video/ogg",   
  19.     ".oga""audio/ogg",  
  20.     ".txt""text/plain",  
  21.     ".wav""audio/x-wav",  
  22.     ".webm""video/webm";  
  23. };  
  24.    
  25. http.createServer(httpListener).listen(8000);  
  26.    
  27. function httpListener (request, response) {  
  28.     // 我們將只接受GET請求,否則返回405 'Method Not Allowed'  
  29.     if (request.method != "GET") {   
  30.         sendResponse(response, 405, {"Allow" : "GET"}, null);  
  31.         return null;  
  32.     }  
  33.    
  34.     var filename =   
  35.         initFolder + url.parse(request.url, truetrue).pathname.split('/').join(path.sep);  
  36.    
  37.     var responseHeaders = {};  
  38.     var stat = fs.statSync(filename);  
  39.     // 檢查文件是否存在,不存在就返回404 Not Found  
  40.     if (!fs.existsSync(filename)) {  
  41.         sendResponse(response, 404, nullnull);  
  42.         return null;  
  43.     }  
  44.     responseHeaders["Content-Type"] = getMimeNameFromExt(path.extname(filename));  
  45.     responseHeaders["Content-Length"] = stat.size; // 文件大小  
  46.            
  47.     sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));  
  48. }  
  49.    
  50. function sendResponse(response, responseStatus, responseHeaders, readable) {  
  51.     response.writeHead(responseStatus, responseHeaders);  
  52.    
  53.     if (readable == null)  
  54.         response.end();  
  55.     else 
  56.         readable.on("open"function () {  
  57.             readable.pipe(response);  
  58.         });  
  59.    
  60.     return null;  
  61. }  
  62.    
  63. function getMimeNameFromExt(ext) {  
  64.     var result = mimeNames[ext.toLowerCase()];  
  65.        
  66.     // 最好給一個默認值  
  67.     if (result == null)  
  68.         result = "application/octet-stream";  
  69.        
  70.     return result;  

 

步驟 2 - 使用正則表達式捕獲Range消息頭

有了這個HTTP服務器做基礎,我們現在就可以用如下代碼處理Range消息頭了. 我們使用正則表達式將消息頭分割,以獲取開始和結束字符串。然后使用 parseInt() 方法將它們轉換成整形數. 如果返回值是 NaN (非數字not a number), 那么這個字符串就是沒有在這個消息頭中的. 參數totalLength展示了當前文件的總字節數. 我們將使用它計算開始和結束位置. 

  1. function readRangeHeader(range, totalLength) {  
  2.         /*  
  3.          * Example of the method 'split' with regular expression.  
  4.          *   
  5.          * Input: bytes=100-200  
  6.          * Output: [null, 100, 200, null]  
  7.          *   
  8.          * Input: bytes=-200  
  9.          * Output: [null, null, 200, null]  
  10.          */ 
  11.    
  12.     if (range == null || range.length == 0)  
  13.         return null;  
  14.    
  15.     var array = range.split(/bytes=([0-9]*)-([0-9]*)/);  
  16.     var start = parseInt(array[1]);  
  17.     var end = parseInt(array[2]);  
  18.     var result = {  
  19.         Start: isNaN(start) ? 0 : start,  
  20.         End: isNaN(end) ? (totalLength - 1) : end  
  21.     };  
  22.        
  23.     if (!isNaN(start) && isNaN(end)) {  
  24.         result.Start = start;  
  25.         result.End = totalLength - 1;  
  26.     }  
  27.    
  28.     if (isNaN(start) && !isNaN(end)) {  
  29.         result.Start = totalLength - end;  
  30.         result.End = totalLength - 1;  
  31.     }  
  32.    
  33.     return result;  

步驟 3 - 檢查數據范圍是否合理

回到函數 httpListener(), 在HTTP方法通過之后,現在我們來檢查請求的數據范圍是否可用. 如果瀏覽器沒有發送 Range 消息頭過來, 請求就會直接被當做一般的請求對待. 服務器會返回整個文件,HTTP狀態將會是 200 OK. 另外我們還會看看開始和結束位置是否比文件長度更大或者相等. 只要有一個是這種情況,請求的數據范圍就是不能被滿足的. 返回的狀態就將會是 416 Requested Range Not Satisfiable 而 Content-Range 也會被發送. 

  1. var responseHeaders = {};  
  2.     var stat = fs.statSync(filename);  
  3.     var rangeRequest = readRangeHeader(request.headers['range'], stat.size);  
  4.       
  5.     // If 'Range' header exists, we will parse it with Regular Expression.  
  6.     if (rangeRequest == null) {  
  7.         responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));  
  8.         responseHeaders['Content-Length'] = stat.size;  // File size.  
  9.         responseHeaders['Accept-Ranges'] = 'bytes';  
  10.            
  11.         //  If not, will return file directly.  
  12.         sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));  
  13.         return null;  
  14.     }  
  15.    
  16.     var start = rangeRequest.Start;  
  17.     var end = rangeRequest.End;  
  18.    
  19.     // If the range can't be fulfilled.   
  20.     if (start >= stat.size || end >= stat.size) {  
  21.         // Indicate the acceptable range.  
  22.         responseHeaders['Content-Range'] = 'bytes */' + stat.size; // File size.  
  23.    
  24.         // Return the 416 'Requested Range Not Satisfiable'.  
  25.         sendResponse(response, 416, responseHeaders, null);  
  26.         return null;  
  27.     } 

步驟 4 - 滿足請求

最后使人迷惑的一塊來了。對于狀態 216 Partial Content, 我們有另外一種格式的 Content-Range 消息頭,包括開始,結束位置以及當前文件的總字節數. 我們也還有 Content-Length 消息頭,其值就等于開始和結束位置之間的差。在最后一句代碼中,我們調用了 createReadStream() 并將開始和結束位置的值給了第二個參數選項的對象, 這意味著返回的流將只包含從開始到結束位置的只讀數據.

  1. // Indicate the current range.   
  2.     responseHeaders['Content-Range'] = 'bytes ' + start + '-' + end + '/' + stat.size;  
  3.     responseHeaders['Content-Length'] = start == end ? 0 : (end - start + 1);  
  4.     responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));  
  5.     responseHeaders['Accept-Ranges'] = 'bytes';  
  6.     responseHeaders['Cache-Control'] = 'no-cache';  
  7.    
  8.     // Return the 206 'Partial Content'.  
  9.     sendResponse(response, 206,   
  10.         responseHeaders, fs.createReadStream(filename, { start: start, end: end })); 

下面是完整的 httpListener() 回調函數.

  1. function httpListener(request, response) {  
  2.     // We will only accept 'GET' method. Otherwise will return 405 'Method Not Allowed'.  
  3.     if (request.method != 'GET') {  
  4.         sendResponse(response, 405, { 'Allow''GET' }, null);  
  5.         return null;  
  6.     }  
  7.    
  8.     var filename =  
  9.         initFolder + url.parse(request.url, truetrue).pathname.split('/').join(path.sep);  
  10.    
  11.     // Check if file exists. If not, will return the 404 'Not Found'.   
  12.     if (!fs.existsSync(filename)) {  
  13.         sendResponse(response, 404, nullnull);  
  14.         return null;  
  15.     }  
  16.    
  17.     var responseHeaders = {};  
  18.     var stat = fs.statSync(filename);  
  19.     var rangeRequest = readRangeHeader(request.headers['range'], stat.size);  
  20.    
  21.     // If 'Range' header exists, we will parse it with Regular Expression.  
  22.     if (rangeRequest == null) {  
  23.         responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));  
  24.         responseHeaders['Content-Length'] = stat.size;  // File size.  
  25.         responseHeaders['Accept-Ranges'] = 'bytes';  
  26.    
  27.         //  If not, will return file directly.  
  28.         sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));  
  29.         return null;  
  30.     }  
  31.    
  32.     var start = rangeRequest.Start;  
  33.     var end = rangeRequest.End;  
  34.    
  35.     // If the range can't be fulfilled.   
  36.     if (start >= stat.size || end >= stat.size) {  
  37.         // Indicate the acceptable range.  
  38.         responseHeaders['Content-Range'] = 'bytes */' + stat.size; // File size.  
  39.    
  40.         // Return the 416 'Requested Range Not Satisfiable'.  
  41.         sendResponse(response, 416, responseHeaders, null);  
  42.         return null;  
  43.     }  
  44.    
  45.     // Indicate the current range.   
  46.     responseHeaders['Content-Range'] = 'bytes ' + start + '-' + end + '/' + stat.size;  
  47.     responseHeaders['Content-Length'] = start == end ? 0 : (end - start + 1);  
  48.     responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));  
  49.     responseHeaders['Accept-Ranges'] = 'bytes';  
  50.     responseHeaders['Cache-Control'] = 'no-cache';  
  51.    
  52.     // Return the 206 'Partial Content'.  
  53.     sendResponse(response, 206,   
  54.         responseHeaders, fs.createReadStream(filename, { start: start, end: end }));  

#p#

測試實現

我們怎么來測試我們的代碼呢?就像在介紹中提到的,部分正文最常用的場景是流和播放視頻。所以我們創建了一個ID為mainPlayer并包含一個<source/>標簽的<video/>。函數onLoad()將在mainPlayer預讀取當前視頻的元數據時被觸發,這用于檢查在URL中是否有數字參數,如果有,mainPlayer將跳到指定的時間點。

  1. <!DOCTYPE html> 
  2. <html> 
  3.     <head> 
  4.         <script type="text/javascript"> 
  5.    
  6.             function onLoad() {  
  7.                 var sec = parseInt(document.location.search.substr(1));  
  8.                    
  9.                 if (!isNaN(sec))  
  10.                     mainPlayer.currentTime = sec;  
  11.             }  
  12.            
  13.         </script> 
  14.         <title>Partial Content Demonstration</title> 
  15.     </head> 
  16.     <body> 
  17.         <h3>Partial Content Demonstration</h3> 
  18.         <hr /> 
  19.         <video id="mainPlayer" width="640" height="360"   
  20.             autoplay="autoplay" controls="controls" onloadedmetadata="onLoad()"> 
  21.             <source src="dota2/techies.mp4" /> 
  22.         </video> 
  23.     </body> 
  24. </html> 

現在我們把頁面保存為"player.html"并和"dota2/techies.mp4"一起放在initFolder目錄下。然后在瀏覽器中打開URL:http://localhost:8000/player.html

在Chrome中看起來像這樣:

因為在URL中沒有任何參數,文件將從最開始出播放。

接下來就是有趣的部分了。讓我們試著打開這個然后看看發生了什么:http://localhost:8000/player.html?60

如果你按F12來打開Chrome的開發者工具,切換到網絡標簽頁,然后點擊查看最近一次日志的詳細信息。你會發現范圍的頭信息(Range)被你的瀏覽器發送了:

  1. Range:bytes=225084502

很有趣,對吧?當函數onLoad()改變currentTime屬性的時候,瀏覽器計算這部視頻60秒處的字節位置。因為mainPlayer已經預加載了元數據,包括格式、比特率和其他基本信息,這個起始位置立刻就被得到了。之后,瀏覽器就可以下載并播放視頻而不需要請求開頭的60秒了。成功了!

我們已經用Node.js來實現支持部分正文的HTTP服務器端了。我們也用HTML5頁面測試了。但這只是一個開始。如果你對頭部信息和工作流這些都已經理解透徹了,你可以試著用其他像ASP.NET MVC或者WCF服務這類框架來實現它。但是不要忘記啟動任務管理器來查看CPU和內存的使用。像我們在之前討論到的,服務器沒有在單個響應中返回所用剩余的字節。要找到性能的平衡點將是一項重要的任務。

英文原文:HTTP 206 Partial Content In Node.js

 

譯文出自:http://www.oschina.net/translate/http-partial-content-in-node-js

結論

開始用Node.js實現

請求最后512個字節

瀏覽器發送:

  1. GET /dota2/techies.mp4 HTTP/1.1  
  2. Host: localhost:8000  
  3. Range: bytes=-512 
責任編輯:林師授 來源: 開源中國社區 編譯
相關推薦

2017-04-24 08:31:26

Node.jsExpress.jsHTTP

2011-09-08 14:16:12

Node.js

2023-06-30 23:25:46

HTTP模塊內存

2015-03-10 10:59:18

Node.js開發指南基礎介紹

2013-11-01 09:34:56

Node.js技術

2017-03-19 16:40:28

漏洞Node.js內存泄漏

2017-03-20 13:43:51

Node.js內存泄漏

2021-10-03 15:02:50

HTTPNodejs

2017-08-17 13:56:30

JavascriptNode.jsHttp

2021-03-09 08:03:21

Node.js 線程JavaScript

2021-07-16 04:56:03

NodejsAddon

2011-11-10 08:55:00

Node.js

2012-10-24 14:56:30

IBMdw

2011-09-08 13:46:14

node.js

2011-11-01 10:30:36

Node.js

2011-09-02 14:47:48

Node

2011-09-09 14:23:13

Node.js

2024-01-05 08:49:15

Node.js異步編程

2021-05-21 09:36:42

開發技能代碼

2016-08-11 14:02:02

NodeJS前端
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品国产偷自在线观看 | 亚洲一区av在线 | av影片在线 | 国产亚洲精品久久久久动 | 亚洲国产欧美日韩 | 亚洲码欧美码一区二区三区 | 色综合久久久 | 国产精品久久久久久婷婷天堂 | 日韩免费中文字幕 | 综合一区 | 日韩精品在线观看一区二区三区 | 国产日屁 | 国产成人精品一区二区三区 | 国产欧美精品一区二区三区 | 国产91丝袜在线播放 | 欧美乱淫视频 | 亚洲成人精品 | 久久久蜜臀国产一区二区 | 中文字幕日韩一区 | 97超碰中文网 | 一级免费黄色 | 亚洲精品久久久久久一区二区 | 亚洲一在线 | 久久久久av| 成人免费在线电影 | 国产精品欧美精品日韩精品 | 九九热精品视频 | 国产视频久久 | 久久久久久久久久久高潮一区二区 | 九九久久国产精品 | 国产精品高清一区二区三区 | 国产一区二区在线免费观看 | 免费黄色a级毛片 | av影音在线| 天天天操天天天干 | 国产高清久久 | 中文字幕精品一区久久久久 | 精产国产伦理一二三区 | 中文字幕日韩av | 国外成人免费视频 | 亚洲午夜精品一区二区三区 |