Previously wrote an article that briefly describes a node. JS-based static file server. It was only personal interest at that time. Recently there are new requirements on the server, I would like to take some time to do a good study. So take the previous code out of refactoring, the overall code has become much cleaner.
First of all, the latest node. JS is support generator, the so-called generator, is JavaScript in the Association (Semi-association), but the function is slightly weak, just to solve the JS in the name of the callback Hell and born. Here I do not use generator, but use promise (rice to eat a mouthful, first understand promise to learn generator). Promise is not a new grammar, but a way of writing. The most famous implementation is Q.
The learning materials about Q can be seen here, very clear.
Server.js's Code:
' Use strict '; var CONFIG = {' host ': ' 127.0.0.1 ', ' Port ': 9527, ' site_base ': './site ', ' File_expiry_time ': 0,//HTTP cache Exp Iry time, minutes ' directory_listing ': true};var mime_types = {'. txt ': ' Text/plain ', '. MD ': ' Text/plain ', ': ' Text/plain ' , '. html ': ' text/html ', '. css ': ' Text/css ', '. js ': ' Application/javascript ', '. JSON ': ' Application/json ', '. jpg ': ' Image /jpeg ', '. png ': ' Image/png ', '. gif ': ' Image/gif ', '. zip ': ' Text/plain ', '. Cfg ': ' text/plain '};var expiry_time = ( Config.file_expiry_time *). ToString (), var http = require (' http '), var Path = require (' path '), var Crypto = require (' Cryp To ') var custard = require ('./custard/custard '); var Q = require (' q '); var fs = require ('./filesystem ')//an object Represen Ting a server responsefunction responseobject (metadata) {this.status = Metadata.status | | 200;this.data = Metadata.data | | False;this.type = Metadata.type | | false;} ResponseObject.prototype.getEtag = function () {var hash = Crypto.createhash (' MD5 '); Hash.update (this.data); return hash . digeSt (' Hex ');}; function getfilelist (files, URL, callback, error) {var template = new Custard;var Full_path = config.site_base + Url;var I = 0;template.addtagset (' h ', require ('./custard/templates/tags/html ')); Template.addtagset (' C ', {' title ': ' Index of ' + URL, ' file_list ': function (h) {var items = [];var stats;for (i = 0; i < files.length; i + = 1) {stats = Fs.statsync (full _path + files[i]); if (Stats.isdirectory ()) {Files[i] + = '/';} Items.push (H.el (' Li ', [H.el (' a ', {' href ': URL + files[i]}, Files[i])));} return items;}}); Q.nbind (Template.render, template) ("H.doctype (' HTML5 '), h.html ([H.head (' H.el (' title ', C.title),]), H.body ([H.el (' H1 ', C.title), H.el (' ul ', C.file_list (h))])]). Then (callback, error);} Filter server requests by Typefunction HandleRequest (URL) {//hack fix version does not have an extension but is a text file]var url = Url;var Deferre D = Q.defer (); if (path.extname (url) = = = ' && url.indexof (' version ') = =-1) {//path var Full_path = Config.site_ Base + url;if (! config.directory_listing) {//FORbiddendeferred.resolve (New Responseobject ({' Status ': 403})) return deferred. Promise;} Fs.exists (Full_path). Then (function () {return Fs.readdir (Full_path)}, function () {Deferred.resolve (new Responseobject ({' status ': 404})}). Then (function (files) {getfilelist (files, URL, function (HTML) {deferred.resolve ( New Responseobject ({' Data ': New Buffer (HTML), ' type ': ' text/html ')}), function (Error) {Deferred.resolve (new Responseobject ({' Data ': error.stack, ' status ': ')}}, function (Error) {//Internal errordeferred.resolve (new Responseobject ({' Data ': error.stack, ' status ': 500});})} else {//file var path = config.site_base + url;fs.exists (path). Then (function () {return fs.readfile (path)}, function () {Defe Rred.resolve (New Responseobject ({' status ': 404});}). Then (data) {Deferred.resolve (new Responseobject ({' Data ': New Buffer (data), ' type ': Mime_types[path.extname ( Path)}), function (Error) {Deferred.resolve (new Responseobject ({' Data ': error.stack, ' status ': ')})})}return Deferred.prOmise;;} function Parserange (str, size) {if (Str.indexof (",")! =-1) {return; } str = str.replace ("bytes=", "" "); var range = Str.split ("-"), start = parseint (Range[0], ten), end = parseint (range[1], 10); Case: -100 if (IsNaN (start)) {start = Size-end; end = Size-1; case:100-} else if (IsNaN (end)) {end = Size-1; }//Invalid if (IsNaN (start) | | IsNaN (END) | | Start > End | | end > Size) {return; } return {start:start, end:end};}; var compresshandle = function (raw, matched, StatusCode, reasonphrase) {var stream = raw; var acceptencoding = request.headers[' accept-encoding ' | | ""; if (matched && acceptencoding.match (/\bgzip\b/)) {Response.setheader ("content-encoding", "gzip"); stream = Raw.pipe (Zlib.creategzip ()); } else if (matched && acceptencoding.match (/\bdeflate\b/)) {Response.setheader ("Content-encoDing "," deflate "); stream = Raw.pipe (Zlib.createdeflate ()); } response.writehead (StatusCode, reasonphrase); Stream.pipe (response);};/ /Start Serverhttp.createserver (function (request, response) {var Headers;var etag;if (Request.method = = = ' GET ') {//get re Sponse objecthandlerequest (Request.url). Then (function (Response_object) {if (!response_object | |! response_ Object.data | | Response_object.data.length <= 0) {//No file Contents Response.writehead (response_object.status); Response.End (); return;} ETag = Response_object.getetag (); if (Request.headers.hasOwnProperty (' If-none-match ') && request.headers[' If-none-match '] = = = ETag) {//not modifiedresponse.writehead (304); Response.End (); return;} var filefullsize = response_object.data.length;if (request.headers["range"]) {//If there is rangevar range = Parserange ( request.headers["Range"], filefullsize), if (range) {var raw = Fs.createreadstream (Config.site_base + request.url, {" Start ": Range.Start," End ": Range.End});//console.log (raw); heAders = {' accept-ranges ': ' bytes ', ' content-type ': Response_object.type, ' content-length ': (Range.end-range.start + 1), ' Cache-control ': ' max-age= ' + expiry_time, ' content-range ': ' bytes ' + range.start + '-' + range.end + '/' + filefullsize , ' ETag ': Etag};console.log ("range" + Range.Start + "-" + Range.End); Response.writehead (Response_object.status, headers );//response.end (Response_object.data); Raw.pipe (response);//raw = "";//response.end (raw);} else {console.log ("range format error");//Range Format error Response.removeheader ("Content-length"); Response.writehead (416 , "Request Range not Satisfiable"); Response.End ();}} else {//no range, full file headers = {' accept-ranges ': ' bytes ', ' content-type ': Response_object.type, ' content-length ': Response_object.data.length, ' Cache-control ': ' max-age= ' + expiry_time, ' ETag ': Etag};response.writehead (response_ Object.status, headers); Response.End (Response_object.data);}} );} else if (Request.method = = ' HEAD ') {handlerequest (Request.url). Then (function (Response_object) {if (!response_object | |!response_object.data | | response_object.data.length <= 0) { Response.writehead (Response_object.status); Response.End (); return;} ETag = Response_object.getetag (); if (Request.headers.hasOwnProperty (' If-none-match ') && request.headers[' If-none-match '] = = = ETag) {//Not Modifiedresponse.writehead (304); Response.End (); return;} headers = {' Content-type ': Response_object.type, ' content-length ': response_object.data.length, ' Cache-control ': ' Max-age= ' + expiry_time, ' ETag ': Etag};response.writehead (response_object.status, headers); Response.End ();} );} else {//Forbiddenresponse.writehead (403); Response.End ();}} ). Listen (Config.port, config.host); Console.log (' Site online:http://' + config.host + ': ' + CONFIG.port.toString () + ' /' );
The Filesystem.js code (which is useful in server.js, just uses Q to simply encapsulate the asynchronous file operation under node. js):
var q = require (' q ') var fs = require (' FS ')///File operation function using the Q package callback form var fs_readfile = Q.nfbind (fs.readfile); var fs_readdir = q.nf Bind (Fs.readdir), var fs_stat = Q.nfbind (Fs.stat), function exists (path) {var defer = Q.defer (); Fs.exists (path, function (exists) {if (exists) {defer.resolve (exists); } else {defer.reject (exists); } }); return defer.promise;} function ReadFile (path) {return fs_readfile (path);} function Readdir (path) {return fs_readdir (path);} function Stat (path) {return Fs_stat (path)}//the synchronization function, calling function Statsync (path) {return Fs.statsync (path) directly;} function Readfilesync (path) {return Fs.readfilesync (path)}module.exports.exists = Exists;module.exports.readfile = re Adfile;module.exports.readfilesync = Readfilesync;module.exports.readdir = Readdir;module.exports.stat = stat; Module.exports.statSync = Statsync;
To be honest, even the refactored code is not very concise. The most important thing to do with Q is to solve the problem of multi-layer callbacks. In the above code, in fact there is not too many multi-layer calls, so the embodiment is not very obvious, but the project is large, there will be significant effect.
Even after the use of Q, debugging is still troublesome, because we cannot clearly know who this function is, when it is called, that is, many times we do not get the call stack (can be obtained, but the information obtained is almost meaningless, because the function is called in the Q Task).
For the Q Package of node. js Async Functions, here's a look at the exists () function in Filesystem.js. What Q.nfbind do is actually what exists does. Function The first argument must be error, the second parameter must be data, such a function can directly use the Q.nfbind package, like the Fs.exits function, only one parameter, so the Q.nfbind package cannot be used.
node. js-based file server (using Q refactoring Code)