Rumah > hujung hadapan web > tutorial js > Tutorial untuk melaksanakan pemecahan kandungan HTTP 206 menggunakan Node.js_node.js

Tutorial untuk melaksanakan pemecahan kandungan HTTP 206 menggunakan Node.js_node.js

WBOY
Lepaskan: 2016-05-16 15:53:35
asal
1352 orang telah melayarinya

Pengenalan

Dalam artikel ini, saya akan menerangkan konsep asas sub-bahagian status HTTP 206 dan melaksanakannya langkah demi langkah menggunakan Node.js Kami juga akan menguji kod dengan contoh berdasarkan senario paling biasa penggunaannya. a Halaman HTML5 yang mula memainkan fail video pada bila-bila masa
Pengenalan ringkas kepada Kandungan Separa

Kod status 206 Kandungan Separa HTTP dan pengepala mesej yang berkaitan menyediakan mekanisme yang membolehkan penyemak imbas dan ejen pengguna lain menerima sebahagian daripada kandungan daripada pelayan dan bukannya keseluruhan kandungan Mekanisme ini digunakan secara meluas dalam pemindahan besar fail video disokong oleh kebanyakan penyemak imbas dan pemain seperti Windows Media Player dan VLC Player.

Proses asas boleh diterangkan dalam langkah berikut:

  • Penyemak imbas meminta kandungan.
  • Pelayan memberitahu penyemak imbas bahawa kandungan boleh diminta dalam bahagian menggunakan pengepala Accept-Ranges.
  • Penyemak imbas menghantar semula permintaan dan menggunakan pengepala Julat untuk memberitahu pelayan julat kandungan yang diperlukan.

Pelayan akan bertindak balas kepada permintaan penyemak imbas dalam dua situasi berikut:

  • Jika julat adalah munasabah, pelayan akan mengembalikan kandungan separa yang diminta dengan kod status Kandungan Separa 206 Julat kandungan semasa akan diisytiharkan dalam pengepala Julat Kandungan
  • Jika julat tidak tersedia (contohnya, lebih besar daripada jumlah bilangan bait kandungan), pelayan akan mengembalikan kod status 416 Julat Diminta Tidak Memuaskan Julat yang tersedia juga akan diisytiharkan dalam pengepala Julat Kandungan .

Mari kita lihat setiap pengepala utama dalam langkah ini.

Julat Terima: bait

Ini ialah pengepala bait yang akan dihantar oleh pelayan, menunjukkan kandungan yang boleh dihantar ke penyemak imbas dalam bahagian Nilai ini mengisytiharkan julat yang diterima untuk setiap permintaan, dalam kebanyakan kes bilangan bait.


Julat: Bilangan bait (bait) = (mula)-(akhir)

Ini ialah pengepala mesej yang dimaklumkan oleh penyemak imbas kepada pelayan julat kandungan separa yang diperlukan. Sila ambil perhatian bahawa kedudukan mula dan akhir disertakan dan bermula dari 0. Pengepala mesej ini tidak perlu menghantar kedua-dua kedudukan itu seperti berikut:

  • Jika kedudukan tamat dialih keluar, pelayan akan mengembalikan bait terakhir yang tersedia bagi kandungan daripada kedudukan mula yang diisytiharkan ke kedudukan akhir keseluruhan kandungan
  • Jika kedudukan mula dialih keluar, parameter kedudukan akhir boleh diterangkan sebagai bilangan bait yang boleh dikembalikan oleh pelayan bermula dari bait terakhir yang tersedia

Julat Kandungan: Bilangan bait (bait) = (mula)-(akhir)/(jumlah)

Pengepala ini akan muncul dengan kod status HTTP 206. Nilai mula dan akhir menunjukkan julat kandungan semasa Seperti pengepala Julat, kedua-dua nilai adalah inklusif dan bermula dari sifar jumlah bilangan bait yang tersedia.



Julat Kandungan: */(jumlah nombor)

Pengepala ini sama seperti yang sebelumnya, tetapi dalam format yang berbeza, dan hanya dihantar apabila kod status HTTP 416 dikembalikan. Jumlah bilangan mewakili jumlah bilangan bait yang tersedia untuk teks.

Berikut ialah sepasang contoh dengan fail 2048 bait. Beri perhatian kepada perbezaan antara meninggalkan titik permulaan dan titik utama.

1024 bait pertama permintaan

Pelayar menghantar:



GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=0-1023
Salin selepas log masuk
Pelayan kembali:



HTTP/1.1 206 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...)
Salin selepas log masuk

Tiada permintaan kedudukan akhir

Pelayar menghantar:



GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=1024-
Salin selepas log masuk
Pelayan kembali:



HTTP/1.1 206 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...)
Salin selepas log masuk

Nota: Pelayan tidak perlu memulangkan semua bait yang tinggal dalam satu respons, terutamanya jika kandungan terlalu panjang atau terdapat pertimbangan prestasi lain. Jadi dua contoh berikut juga boleh diterima dalam kes ini:


Content-Range: bytes 1024-1535/2048
Content-Length: 512
Salin selepas log masuk
Pelayan hanya mengembalikan separuh daripada badan yang tinggal. Julat yang diminta seterusnya akan bermula pada bait 1536.


Content-Range: bytes 1024-1279/2048
Content-Length: 256
Salin selepas log masuk
Pelayan hanya mengembalikan 256 bait badan yang tinggal. Julat yang diminta seterusnya akan bermula pada bait 1280.


Minta 512 bait terakhir

Pelayar menghantar:



GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=-512
Salin selepas log masuk
Pelayan kembali:



HTTP/1.1 206 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...)
Salin selepas log masuk

Meminta julat yang tidak tersedia:

Pelayar menghantar:



GET /dota2/techies.mp4 HTTP/1.1
Host: localhost:8000
Range: bytes=1024-4096
Salin selepas log masuk
Pelayan kembali:



HTTP/1.1 416 Requested Range Not Satisfiable
Date: Mon, 15 Sep 2014 22:19:34 GMT
Content-Range: bytes */2048
Salin selepas log masuk

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

开始用Node.js实现

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

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

// 初始化需要的对象
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, true, true).pathname.split('/').join(path.sep);
 
  var responseHeaders = {};
  var stat = fs.statSync(filename);
  // 检查文件是否存在,不存在就返回404 Not Found
  if (!fs.existsSync(filename)) {
    sendResponse(response, 404, null, null);
    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;
<strong>}
</strong>
Salin selepas log masuk

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

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


function readRangeHeader(range, totalLength) {
    /*
     * Example of the method &apos;split&apos; 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) &#63; 0 : start,
    End: isNaN(end) &#63; (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;
}
Salin selepas log masuk

步骤 3 - 检查数据范围是否合理

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

var responseHeaders = {};
  var stat = fs.statSync(filename);
  var rangeRequest = readRangeHeader(request.headers[&apos;range&apos;], stat.size);
  
  // If &apos;Range&apos; header exists, we will parse it with Regular Expression.
  if (rangeRequest == null) {
    responseHeaders[&apos;Content-Type&apos;] = getMimeNameFromExt(path.extname(filename));
    responseHeaders[&apos;Content-Length&apos;] = stat.size; // File size.
    responseHeaders[&apos;Accept-Ranges&apos;] = &apos;bytes&apos;;
     
    // If not, will return file directly.
    sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));
    return null;
  }
 
  var start = rangeRequest.Start;
  var end = rangeRequest.End;
 
  // If the range can&apos;t be fulfilled. 
  if (start >= stat.size || end >= stat.size) {
    // Indicate the acceptable range.
    responseHeaders[&apos;Content-Range&apos;] = &apos;bytes */&apos; + stat.size; // File size.
 
    // Return the 416 &apos;Requested Range Not Satisfiable&apos;.
    sendResponse(response, 416, responseHeaders, null);
    return null;
  }
Salin selepas log masuk


步骤 4 - 满足请求

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

// Indicate the current range. 
  responseHeaders['Content-Range'] = 'bytes ' + start + '-' + end + '/' + stat.size;
  responseHeaders['Content-Length'] = start == end &#63; 0 : (end - start + 1);
  responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
  responseHeaders['Accept-Ranges'] = 'bytes';
  responseHeaders['Cache-Control'] = 'no-cache';
 
  // Return the 206 'Partial Content'.
  sendResponse(response, 206, 
    responseHeaders, fs.createReadStream(filename, { start: start, end: end }));
Salin selepas log masuk

下面是完整的 httpListener() 回调函数.


function httpListener(request, response) {
  // We will only accept 'GET' method. Otherwise will return 405 'Method Not Allowed'.
  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);
 
  // Check if file exists. If not, will return the 404 'Not Found'. 
  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);
 
  // If 'Range' header exists, we will parse it with Regular Expression.
  if (rangeRequest == null) {
    responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
    responseHeaders['Content-Length'] = stat.size; // File size.
    responseHeaders['Accept-Ranges'] = 'bytes';
 
    // If not, will return file directly.
    sendResponse(response, 200, responseHeaders, fs.createReadStream(filename));
    return null;
  }
 
  var start = rangeRequest.Start;
  var end = rangeRequest.End;
 
  // If the range can't be fulfilled. 
  if (start >= stat.size || end >= stat.size) {
    // Indicate the acceptable range.
    responseHeaders['Content-Range'] = 'bytes */' + stat.size; // File size.
 
    // Return the 416 'Requested Range Not Satisfiable'.
    sendResponse(response, 416, responseHeaders, null);
    return null;
  }
 
  // Indicate the current range. 
  responseHeaders['Content-Range'] = 'bytes ' + start + '-' + end + '/' + stat.size;
  responseHeaders['Content-Length'] = start == end &#63; 0 : (end - start + 1);
  responseHeaders['Content-Type'] = getMimeNameFromExt(path.extname(filename));
  responseHeaders['Accept-Ranges'] = 'bytes';
  responseHeaders['Cache-Control'] = 'no-cache';
 
  // Return the 206 'Partial Content'.
  sendResponse(response, 206, 
    responseHeaders, fs.createReadStream(filename, { start: start, end: end }));
}
Salin selepas log masuk


测试实现

我们怎么来测试我们的代码呢?就像在介绍中提到的,部分正文最常用的场景是流和播放视频。所以我们创建了一个ID为mainPlayer并包含一个标签的


<!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>Partial Content Demonstration</title>
  </head>
  <body>
    <h3>Partial Content Demonstration</h3>
    <hr />
    <video id="mainPlayer" width="640" height="360" 
      autoplay="autoplay" controls="controls" onloadedmetadata="onLoad()">
      <source src="dota2/techies.mp4" />
    </video>
  </body>
</html>
Salin selepas log masuk

现在我们把页面保存为"player.html"并和"dota2/techies.mp4"一起放在initFolder目录下。然后在浏览器中打开URL:http://localhost:8000/player.html

在Chrome中看起来像这样:

2015623105803917.png (680×535)

因为在URL中没有任何参数,文件将从最开始出播放。

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

2015623105918021.png (680×535)

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

Range:bytes=225084502-
Salin selepas log masuk

Lawak kan? Apabila fungsi onLoad() menukar sifat currentTime, penyemak imbas mengira kedudukan bait pada 60 saat ke dalam video. Oleh kerana mainPlayer telah dipramuat dengan metadata, termasuk format, kadar bit dan maklumat asas lain, kedudukan permulaan ini diperoleh serta-merta. Penyemak imbas kemudiannya boleh memuat turun dan memainkan video tanpa meminta 60 saat pertama. Berjaya!

Kesimpulan

Kami telah menggunakan Node.js untuk melaksanakan pelayan HTTP yang menyokong teks separa. Kami juga menguji dengan halaman HTML5. Tetapi ini hanya permulaan. Jika anda mempunyai pemahaman yang menyeluruh tentang maklumat pengepala dan aliran kerja, anda boleh cuba melaksanakannya menggunakan rangka kerja lain seperti perkhidmatan ASP.NET MVC atau WCF. Tetapi jangan lupa untuk melancarkan Pengurus Tugas untuk melihat penggunaan CPU dan memori. Seperti yang kita bincangkan sebelum ini, pelayan tidak mengembalikan baki bait yang digunakan dalam satu respons. Mencari keseimbangan prestasi akan menjadi tugas penting.

Label berkaitan:
sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan