第一步:实现一个最简单的静态服务器:javascript
var http = require('http'); var server = http.createServer(function(req,res) { res.writeHeader(200,{'Content-Type': 'text/plain'}); res.end('hello'); }); server.listen(9030,function() { console.log('you are listening port 9030'); });
第二步:MIME类型支持,当本地存在资源时响应200状态码,不存在响应404状态吗,默认UTF-8编码
要读文件,须要引入url,fs模块,如今已经实现200,404状态码机制了css
var server = http.createServer(function(req,res) { var pathname= url.parse(req.url).pathname;//解析路径 var resourcePath = 'home' + pathname;//资源路径 if(fs.existsSync(resourcePath)) {//判断资源是否存在,存在则读取 fs.readFile(resourcePath,'binary',function(err,resource) { if(err) { res.writeHead(500,{'Content-Type': 'text/plain'}); res.end(); }else { res.writeHead(200, {'Content-Type': 'text/html'}); res.write(resource, "binary"); res.end(); } }) }else { res.writeHead(404,{'Content-Type': 'text/plain'}); res.write('No Found'); res.end(); } });
接下来就是支持MIME类型,由于服务器不可能知识存储一种类型的资源。增长一个配置文件config.js,内容以下。html
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" };
而后使用path模块的extname方法解析文件后缀命。前端
var path = require('path'); var mimeList = require('./config').types; var server = http.createServer(function(req,res) { var pathname= url.parse(req.url).pathname;//解析路径 var resourcePath = 'home' + pathname;//资源路径 var suffix = path.extname(pathname).slice(1);//获取后缀 var contentType = mimeList[suffix]; if(fs.existsSync(resourcePath)) {//判断资源是否存在,存在则读取 fs.readFile(resourcePath,'binary',function(err,resource) { if(err) { res.writeHead(500,{'Content-Type': 'text/plain'}); res.end(); }else { res.writeHead(200, {'Content-Type': contentType}); res.write(resource, "binary"); res.end(); } }) }else { res.writeHead(404,{'Content-Type': contentType}); res.write('No Found'); res.end(); } });
到如今,已经实现了一个比较完整的静态服务器了。那么接下来重点来了,也就是实现前端老生常谈的缓存。接下实现304缓存逻辑,在config文件下增长以下配置,设置过时时间java
exports.Expires = { maxAge: 60*60*24*365 };
增长以下代码:node
var Expires = require('./config').Expires; var expires = new Date(); expires.setTime(expires.getTime() + Expires.maxAge * 1000); res.writeHead(200, {'Content-Type': contentType,"Expires":expires.toUTCString(), "Cache-Control": "max-age=" + Expires.maxAge });
304状态码:在服务器上为全部请求的响应都添加Last-Modified头,当浏览器发送第二次请求时会带上If-Modified-Since字段,而后将该字段的值跟文件最后修改时间比较,若是同样则不返回内容。获取文件最后修改时间用fs.stat()方法
主要代码以下:npm
fs.stat(resourcePath,function(err,stat) { var lastModified = stat.mtime.toUTCString(); var ifModifiedSince = "If-Modified-Since".toLowerCase(); res.setHeader("Last-Modified", lastModified); var expires = new Date(); expires.setTime(expires.getTime() + Expires.maxAge * 1000); if (req.headers[ifModifiedSince] && lastModified == req.headers[ifModifiedSince]) {//实现304逻辑 res.writeHead(304, "Not Modified"); res.end(); }else { res.writeHead(200, {'Content-Type': contentType,"Expires":expires.toUTCString(), "Cache-Control": "max-age=" + Expires.maxAge }); res.write(resource, "binary"); res.end(); } });
增长etag验头,nodejs生成etag要按照etag包,npm install etag
,增长代码:json
var ifNoneMatch = req.headers['if-none-match']; if (req.headers[ifModifiedSince] && lastModified == req.headers[ifModifiedSince] || ifNoneMatch === etag(resource)) {//实现304逻辑,etag
完整代码:浏览器
var server = http.createServer(function(req,res) { var pathname= url.parse(req.url).pathname;//解析路径 var resourcePath = 'home' + pathname;//资源路径 var suffix = path.extname(pathname).slice(1);//获取后缀 var contentType = mimeList[suffix]; if(fs.existsSync(resourcePath)) {//判断资源是否存在,存在则读取 fs.readFile(resourcePath,'binary',function(err,resource) { if(err) { res.writeHead(500,{'Content-Type': 'text/plain'}); res.end(); }else { fs.stat(resourcePath,function(err,stat) { var lastModified = stat.mtime.toUTCString(); var ifModifiedSince = "If-Modified-Since".toLowerCase(); res.setHeader("Last-Modified", lastModified); var expires = new Date(); expires.setTime(expires.getTime() + Expires.maxAge * 1000); console.log(etag(resource),req.headers); var ifNoneMatch = req.headers['if-none-match']; if (req.headers[ifModifiedSince] && lastModified == req.headers[ifModifiedSince] || ifNoneMatch === etag(resource)) {//实现304逻辑 res.writeHead(304, "Not Modified"); res.end(); }else { res.writeHead(200, {'Content-Type': contentType,"Expires":expires.toUTCString(), "Cache-Control": "max-age=" + Expires.maxAge, "ETag":etag(resource) }); res.write(resource, "binary"); res.end(); } }) } }) }else { res.writeHead(404,{'Content-Type': contentType}); res.write('No Found'); res.end(); } });
开启Gzip压缩缓存
修改代码以下:
var resource = fs.createReadStream(resourcePath); var acceptEncoding = req.headers['accept-encoding']; if(acceptEncoding && acceptEncoding.indexOf('gzip') != -1) {//判断是否须要开启Gzip res.writeHead(200, "Ok", {'Content-Encoding': 'gzip'}); resource.pipe(zlib.createGzip()).pipe(res); }else { res.writeHead(200, "Ok"); resource.pipe(res); }
最后一步,解决/../../index.html这种相对路径请求访问到其余系统文件。思路:首先替换掉全部的..,而后调用path.normalize方法来处理掉不正常的/。
var resourcePath = path.join("home", path.normalize(pathname.replace(/\.\./g, "")));
到这里基本完成一个静态服务器了:
var http = require('http'); var url = require('url'); var fs = require('fs'); var path = require('path'); var mimeList = require('./config').types; var Expires = require('./config').Expires; var zlib = require('zlib'); var server = http.createServer(function(req,res) { var pathname= url.parse(req.url).pathname;//解析路径 var resourcePath = path.join("home", path.normalize(pathname.replace(/\.\./g, ""))); var suffix = path.extname(pathname).slice(1);//获取后缀 var contentType = mimeList[suffix]; if(fs.existsSync(resourcePath)) {//判断资源是否存在,存在则读取 fs.stat(resourcePath,function(err,stat) { var lastModified = stat.mtime.toUTCString(); var ifModifiedSince = "If-Modified-Since".toLowerCase(); var expires = new Date(); res.setHeader("Last-Modified", lastModified); res.setHeader('Content-Type',contentType); res.setHeader("Expires",expires.toUTCString()); res.setHeader("Cache-Control", "max-age=" + Expires.maxAge); expires.setTime(expires.getTime() + Expires.maxAge * 1000); if (req.headers[ifModifiedSince] && lastModified == req.headers[ifModifiedSince]) {//实现304逻辑 res.writeHead(304, "Not Modified"); res.end(); }else { var resource = fs.createReadStream(resourcePath); var acceptEncoding = req.headers['accept-encoding']; if(acceptEncoding && acceptEncoding.indexOf('gzip') != -1) {//判断是否须要开启Gzip res.writeHead(200, "Ok", {'Content-Encoding': 'gzip'}); resource.pipe(zlib.createGzip()).pipe(res); }else { res.writeHead(200, "Ok"); resource.pipe(res); } } }) }else { res.writeHead(404,{'Content-Type': contentType}); res.write('No Found'); res.end(); } }); server.listen(9030,function() { console.log('you are listening port 9030'); });
附上一篇不错的文章,里面还有更多的一些细节Nodejs实现静态服务器,抄抄改改哈哈【滑稽】