Tutorial on using Node.js to implement HTTP 206 content fragmentation _node.js

Source: Internet
Author: User
Tags numeric memory usage ranges readable require chrome developer

Introduced

In this article, I'll explain the basics of the HTTP State 206-part content and implement it step-by-step using Node.js. We'll also test the code with an example of the most common scenario based on its usage: a HTML5 page that can start playing video files at any point in time.
A brief introduction to Partial Content

The HTTP 206 Partial content status code and its associated message headers provide a mechanism for browsers and other user agents to receive portions of content rather than all of the content from the server. This mechanism is widely used in the transmission of video files supported by most browsers and players such as Windows Media Player and VLC player.

The underlying process can be described in the following steps:

    • The browser requests the content.
    • The server tells the browser that the content can be used for partial requests using the Accept-ranges message header.
    • The browser resend the request, using the range message header to tell the server what scope it needs.

The server will respond to the browser's request in two different situations:

    • If the scope is reasonable, the server returns part of the request and takes the 206 Partial content status code. The scope of the current content is stated in the Content-range message header.
    • If the range is not available (for example, larger than the total number of bytes in the content), the server returns a 416 request range requested range not satisfiable status code. The available scopes are also declared in the Content-range message header.

Let's take a look at each of the key message headers in these steps.

Accept-ranges: bytes (bytes)

This is the byte header that will be sent by the server, showing the content that can be distributed to the browser by the division. This value declares each range request that can be accepted, in most cases the number of bytes bytes.


Range: Number of bytes (bytes) = (start)-(end)

This is the message header that the browser tells the server to be in a partial range of content. Note that the start and end positions are included, and are starting at 0. The message header can also not send two locations, meaning the following:

    • If the end position is removed, the server returns the last available byte of content from the beginning of the declaration to the end of the entire content.
    • If the start position is removed, the end position parameter can be described as the number of bytes that can be returned by the server from the last available byte.

Content-range: Number of bytes (bytes) = (start)-(end)/(total)

This header will be followed by the HTTP status code 2,061. The start and end values show the scope of the current content. Like the Range message header, two values are included and are zero-based. Total This value declares the total number of available bytes.

Content-range: * * (total)

This header information is the same as the one above, but is in another format and is sent only when the HTTP status code 416 is returned. The total number represents the total number of bytes available in the body.

Here are a couple of examples of 2048 byte files. Note that the difference between the starting point and the emphasis is omitted.

1024 bytes of Request start

Browser send:

Get/dota2/techies.mp4 http/1.1
host:localhost:8000
range:bytes=0-1023

Server return:

http/1.1 206 Partial Content
Date:mon, Sep 2014 22:19:34 GMT content-type:video/mp4
content-range:bytes
0-1023/2048
content-length:1024
 
(Content ...)

A request without an end position

Browser send:

Get/dota2/techies.mp4 http/1.1
host:localhost:8000
range:bytes=1024-

Server return:

http/1.1 206 Partial Content
Date:mon, Sep 2014 22:19:34 GMT content-type:video/mp4
content-range:by
TES 1024-2047/2048
content-length:1024
 
(Content ...)


Note: The server does not need to return all the remaining bytes in a single response, especially if the body is too long or has other performance considerations. So the following two examples are also acceptable in this case:

Content-range:bytes 1024-1535/2048
content-length:512

The server returns only half of the remainder of the body. The scope of the next request will begin with the 1536th byte.


Content-range:bytes 1024-1279/2048
content-length:256

The server returns only 256 bytes of the remaining body. The scope of the next request will begin with the 1280th byte.


request last 512 bytes

Browser send:

Get/dota2/techies.mp4 http/1.1
host:localhost:8000
range:bytes=-512

Server return:

http/1.1 206 Partial Content
Date:mon, Sep 2014 22:19:34 GMT content-type:video/mp4
content-range:by
TES 1536-2047/2048
content-length:512
 
(Content ...)

The scope of the request is not available:

Browser send:

Get/dota2/techies.mp4 http/1.1
host:localhost:8000
range:bytes=1024-4096

Server return:

http/1.1 416 requested Range not satisfiable
Date:mon, Sep 2014 22:19:34 GMT content-range:bytes
*/2048

After understanding the workflow and header information, we can now use Node.js to implement this mechanism.

Start with Node.js implementation

The first step: Create a simple HTTP server

We will start with a basic HTTP server, as in the following example. This can be done largely enough to handle most browser requests. First, we initialize the object we need to use, and use Initfolder to represent the location of the file. To generate the Content-type header, we list the file name extensions and their corresponding MIME names to form a dictionary. In the callback function HttpListener (), we will only allow get to be available. If there are other methods, the server returns 405 method not allowed, and the server returns 404 Not Found If the file does not exist in Initfolder.

Initialize the desired object var http = require ("http");
var fs = require ("FS");
var path = require ("path");
 
var url = require ("url");
 
The initial directory can be changed to your desired directory at any time var initfolder = "C:\\users\\user\\videos"; List the file extensions and MIME names we need for a dictionary var mimenames = {". css": "Text/css", ". html": "Text/html", ". js": "Application/javascri PT ",". 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) {//We will accept only get requests, otherwise 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);
  var responseheaders = {};
  var stat = fs.statsync (filename); Checks whether the file exists, does not exist returns 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;
File size Sendresponse (response, responseheaders, fs.createreadstream (filename)); function Sendresponse (response, ResponseStatus, responseheaders, readable) {response.writehead, responsestatus
 
  Onseheaders);
  if (readable = null) Response.End ();
    else Readable.on ("open", function () {readable.pipe (response);
 
  });
return null;
   
  function Getmimenamefromext (EXT) {var result = Mimenames[ext.tolowercase ()];
   
  It is best to give a default value if (result = = NULL) result = "Application/octet-stream";
 return result;} 

Step 2-Capturing the range message header with a regular expression

With this HTTP server as a base, we can now process the range message header with the following code. We use regular expressions to split the header of the message to get the start and end strings. Then use the parseint () method to convert them to an integer number. If the return value is NaN (Non-numeric not a number), then the string is not in the header of this message. The parameter totallength shows the total number of bytes in the current file. We'll use it to compute the start and end positions.


function Readrangeheader (range, totallength) {
    /
     * * Example of the method ' split ' with regular expression.
     * 
     input:bytes=100-200 *
     output: [NULL, MB, NULL]
     * * 
     input:bytes=-200
     * output: [NULL, NULL, MB, 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;
}

Step 3-Check that the data range is reasonable

Back to function HttpListener (), after the HTTP method is passed, we now check that the requested data range is available. If the browser does not send a range message header, the request is treated as a general request. The server returns the entire file, and the HTTP status will be OK. In addition, we'll see if the start and end positions are larger or equal than the file lengths. As long as one is in this case, the requested data range is not satisfied. The returned state will be 416 requested Range not satisfiable and Content-range will also be sent.  
 

var responseheaders = {};
  var stat = fs.statsync (filename);
  
  var rangerequest = Readrangeheader (request.headers[' range '), stat.size);
  If ' Range ' header exists, we'll 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, would return file directly.
    Sendresponse (response, 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 to the 416 ' requested Range not satisfiable '.
    Sendresponse (response, 416, responseheaders, null);
  return null; }
 


Step 4-satisfy the request

At last the confusing piece came. For state 216 Partial Content, we have another format for the Content-range message header, including the start, end position, and the total number of bytes in the current file. We also have a content-length message header whose value is equal to the difference between the start and end positions. In the last code, we call Createreadstream () and give the value of the start and end positions to the object of the second argument option, which means that the returned stream will contain only read-only data from the start to the end position.

Indicate the current 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 ';
 
  Return the 206 ' Partial Content '.
  Sendresponse (response, 206, 
    responseheaders, fs.createreadstream (filename, {start:start, end:end}));

Here is the complete httplistener () callback function.


function HttpListener (request, Response) {//We'll only have accept ' get ' method.
  Otherwise'll return 405 ' method is 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, would return to 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'll 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, would return file directly.
    Sendresponse (response, 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 to 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?
  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}));
 }


Test Implementation

How are we going to test our code? As mentioned in the introduction, some of the most common scenes in the text are streaming and playing video. So we created a <video/> with an ID of Mainplayer and contains a <source/> tag. The onload () function is triggered when Mainplayer reads the metadata for the current video, which checks for numeric parameters in the URL and, if so, Mainplayer jumps to the specified point in time.


<! DOCTYPE html>
 
 

Now we save the page as "player.html" and put it in the Initfolder directory with "Dota2/techies.mp4". Then open the url:http://localhost:8000/player.html in the browser

It looks like this in chrome:

Because there are no arguments in the URL, the file will play from the very beginning.

Then there is the interesting part. Let's try to open this and see what happened: http://localhost:8000/player.html?60

If you press F12 to open the Chrome Developer tool, switch to the Web tab, and then click to view the details of the last log. You will find that the range header information (range) is sent by your browser:

range:bytes=225084502-

It's funny, isn't it? When the function onload () changes the CurrentTime property, the browser calculates the byte position of the video at 60 seconds. Since Mainplayer has preloaded metadata, including formatting, bit rates, and other basic information, the starting position is immediately available. After that, the browser can download and play the video without requiring 60 seconds for the start of the request. It worked!

Conclusion

We have used node.js to implement the HTTP server side that supports partial body text. We also used the HTML5 page to test. But it's only a start. If you have a thorough understanding of the header information and workflow, you can try to implement it with other frameworks such as ASP.net mvc or WCF services. But don't forget to start Task Manager to see the CPU and memory usage. As we discussed earlier, the server did not return the remaining bytes in a single response. Finding the balance point of performance is an important task.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.