nodejs入门-静态文件服务器

本文展示是基于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!
尚欠缺的功能:日志记录、断点、容错等~~以后有时间再加啦

posted on 2013-11-19 16:15  刀锋诚心  阅读(608)  评论(0编辑  收藏  举报