本文展示是基于node.js的静态文件服务器,代码参考自这里,主要是练习node http、文件模块的使用,另外,对理解http协议也很有帮助
除了实现了基本的路由控制,还实现了MIME类型、304缓存、gzip压缩、目录读取
首先是配置文件,setting.js
var setting = { webroot : '/xxx/xxx/webroot', viewdir : false, index : 'index.html', //只有当viewdir为false时,此设置才有用 expires : { filematch : /^(gif|png|jpg|js|css)$/ig, maxAge: 60 * 60 //默认为一个月 }, compress : { match : /css|js|html/ig } }; module.exports = setting; |
MIME映射,mime.js
var mime = { "html": "text/html", "ico": "image/x-icon", "css": "text/css", "gif": "image/gif", "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" }; module.exports = mime; |
然后是主程序,server.js
var http = require('http'); var setting = require('./setting.js'); var mime = require('./mime'); var url = require('url'); var util = require('util'); var path = require('path'); var fs = require('fs'); var zlib = require('zlib'); //访问统计 var number = 0; var accesses = {}; http.createServer(function(req, res){ res.number = ++number; accesses[res.number] = {startTime : new Date().getTime()}; var pathname = url.parse(req.url).pathname; //安全问题,禁止父路径 pathname = pathname.replace(/\.\./g, ''); var realPath = setting.webroot + pathname; accesses[res.number].path = pathname; readPath(req, res, realPath, pathname); }).listen(8000); console.log('http server start at parth 8000\n\n\n'); //判断文件是否存在 function readPath(req, res, realPath, pathname){ //首先判断所请求的资源是否存在 path.exists(realPath, function(ex){ console.log('path.exists--%s', ex); if(!ex){ responseWrite(res, 404, {'Content-Type' : 'text/plain'}, 'This request URL ' + pathname + ' was not found on this server.'); }else{ //文件类型 fs.stat(realPath, function(err, stat){ if(err){ responseWrite(res, 500, err); }else{ //目录 if(stat.isDirectory()){ //是否读取目录 if(setting.viewdir){ fs.readdir(realPath, function(err, files){ if(err){ responseWrite(res, 500, err); }else{ var htm = '<html><head><title>' + pathname + '</title></head><body>' + pathname + '<hr>'; for(var i = 0; i < files.length; i++){ htm += '<br><a href="' + pathname + (pathname.slice(-1) != '/' ? '/' : '') + files[i] + '">' + files[i] + '</a>', 'utf8'; } responseWrite(res, 200, {'Content-Type' : 'text/html'}, htm); } }); }else if(setting.index && realPath.indexOf(setting.index) < 0){ readPath(req, res, path.join(realPath, '/', setting.index), path.join(pathname, '/', setting.index)); }else{ responseWrite(res, 404, {'Content-Type' : 'text/plain'}, 'This request URL ' + pathname + ' was not found on this server.'); } }else{ var type = path.extname(realPath); type = type ? type.slice(1) : 'nuknown'; var header = {'Content-Type' : mime[type] || 'text/plain'}; //缓存支持 if(setting.expires && setting.expires.filematch && type.match(setting.expires.filematch)){ var expires = new Date(), maxAge = setting.expires.maxAge || 3600 * 30; expires.setTime(expires.getTime() + maxAge * 1000); header['Expires'] = expires.toUTCString(); header['Cache-Control'] = 'max-age=' + maxAge; var lastModified = stat.mtime.toUTCString(); header['Last-Modified'] = lastModified; //判断是否304 if(req.headers['if-modified-since'] && lastModified == req.headers['if-modified-since']){ responseWrite(res, 304, 'Not Modified'); }else{ readFile(req, res, realPath, header, type); } }else{ readFile(req, res, realPath, header, type); } } } }); } }); } //读文件/压缩/输出 function readFile(req, res, realPath, header, type){ var raw = fs.createReadStream(realPath), cFun; //是否gzip if(setting.compress && setting.compress.match && type.match(setting.compress.match) && req.headers['accept-encoding']){ if(req.headers['accept-encoding'].match(/\bgzip\b/)){ header['Content-Encoding'] = 'gzip'; cFun = 'createGzip'; }else if(req.headers['accept-encoding'].match(/\bdeflate\b/)){ header['Content-Encoding'] = 'deflate'; cFun = 'createDeflate'; } } res.writeHead(200, header); if(cFun){ raw.pipe(zlib[cFun]()).pipe(res); }else{ raw.pipe(res); } } //普通输出 function responseWrite(res, starus, header, output, encoding){ encoding = encoding || 'utf8'; res.writeHead(starus, header); if(output){ res.write(output, encoding); } res.end(); accesses[res.number].endTime = new Date().getTime(); //日志输出 console.log('access[%s]--%s--%s--%s--%s\n\n', res.number, accesses[res.number].path, (accesses[res.number].endTime - accesses[res.number].startTime), starus, (output ? output.length : 0)); delete accesses[res.number]; } |
over!
尚欠缺的功能:日志记录、断点、容错等~~以后有时间再加啦