Node.js静态文件服务器改进版
首先还是先感谢github,感谢github上提供此段源码的作者。跟昨晚的来比今天的静态文件服务器有点点复杂些,可以学到很多新的东西。
仔细会发现这次的代码多了一个fs.stat函数和ReadStream对象的pipe函数,stat这个函数是用来获取文件信息。第一个参数是传入文件路径,第二个则是回调函数,回调函数的第二个参数stats的属性为文件的基本信息。pipe函数用于将这个可读流和destination目标可写流连接起来,传入这个流中的数据将会写入到destination流中。通过在必要时暂停和恢复流,来源流和目的流得以保持同步。
该静态文件服务器的改进点在于使用了Last-Modified和If-Modified-Since报文头,可以不必要给浏览器返回它已经存在的文件。顺便可以根据浏览器请求资源的压缩方式返回给资源进行gzip或者deflate压缩。
varPORT=8000; varhttp=require("http"); varurl=require("url"); varfs=require("fs"); varpath=require("path"); varmime=require("./mime").types; varconfig=require("./config"); varzlib=require("zlib"); varserver=http.createServer(function(request,response){ response.setHeader("Server","Node/V5"); varpathname=url.parse(request.url).pathname; console.log("url="+pathname); if(pathname.slice(-1)==="/"){ pathname=pathname+config.Welcome.file; } varrealPath=__dirname+"/"+path.join("assets",path.normalize(pathname.replace(/\.\./g,""))); console.log("realPath="+realPath); varpathHandle=function(realPath){ fs.stat(realPath,function(err,stats){ if(err){ response.writeHead(404,"NotFound",{'Content-Type':'text/plain'}); response.write("stats="+stats); response.write("ThisrequestURL"+pathname+"wasnotfoundonthisserver."); response.end(); }else{ if(stats.isDirectory()){ realPath=path.join(realPath,"/",config.Welcome.file); pathHandle(realPath); }else{ varext=path.extname(realPath); ext=ext?ext.slice(1):'unknown'; varcontentType=mime[ext]||"text/plain"; response.setHeader("Content-Type",contentType); //获得文件的修改时间 varlastModified=stats.mtime.toUTCString(); varifModifiedSince="If-Modified-Since".toLowerCase(); //设置Last-Modified //服务器给浏览器返回文件最后一次修改时间Last-Modified response.setHeader("Last-Modified",lastModified); if(ext.match(config.Expires.fileMatch)){ varexpires=newDate(); expires.setTime(expires.getTime()+config.Expires.maxAge*1000); response.setHeader("Expires",expires.toUTCString()); response.setHeader("Cache-Control","max-age="+config.Expires.maxAge); } //服务器接收浏览器发送过来的If-Modified-Since报文头 //日期相同表示该资源没有变化则返回304 //告诉浏览器该资源你已经有了,不需要再请求了 if(request.headers[ifModifiedSince]&&lastModified==request.headers[ifModifiedSince]){ response.writeHead(304,"NotModified"); response.end(); }else{ varraw=fs.createReadStream(realPath); varacceptEncoding=request.headers['accept-encoding']||""; varmatched=ext.match(config.Compress.match); if(matched&&acceptEncoding.match(/\bgzip\b/)){ response.writeHead(200,"Ok",{'Content-Encoding':'gzip'}); raw.pipe(zlib.createGzip()).pipe(response); }elseif(matched&&acceptEncoding.match(/\bdeflate\b/)){ response.writeHead(200,"Ok",{'Content-Encoding':'deflate'}); raw.pipe(zlib.createDeflate()).pipe(response); }else{ response.writeHead(200,"Ok"); raw.pipe(response); } } } } }); }; pathHandle(realPath); }); server.listen(PORT); console.log("Serverruningatport:"+PORT+".");
Expires字段声明了一个网页或URL地址不再被浏览器缓存的时间,一旦超过了这个时间,浏览器都应该联系原始服务器。这里设置失效时间为1年。
exports.Expires={ fileMatch:/^(gif|png|jpg|js|css)$/ig, maxAge:60*60*24*365 }; exports.Compress={ match:/css|js|html/ig }; exports.Welcome={ file:"index.html" };
枚举各种资源的类型,可根据扩展名设置Content-Type。
exports.types={ "css":"text/css", "gif":"image/gif", "html":"text/html", "ico":"image/x-icon", "jpeg":"image/jpeg", "jpg":"image/jpeg", "js":"text/javascript", "json":"application/json", "pdf":"application/pdf", "png":"image/png", "svg":"image/svg+xml", "swf":"application/x-shockwave-flash", "tiff":"image/tiff", "txt":"text/plain", "wav":"audio/x-wav", "wma":"audio/x-ms-wma", "wmv":"video/x-ms-wmv", "xml":"text/xml" };