搞起node.js静态服务器并实战前端缓存

40次阅读

共计 6630 个字符,预计需要花费 17 分钟才能阅读完成。

要实现的内容大概是这样的

MIME 类型支持,当本地存在资源时响应 200 状态码,不存在响应 404 状态吗,默认 UTF- 8 编码
客户端过期时间设置为 1 年
静态资源在服务器存放的根目录是 /home
实现 304 状态码响应逻辑,etag 签名
开启 Gzip 压缩文件
尽可能提高响应性能,以提高服务器吞吐能力
注意安全问题,防止 /../../index.html 这种相对路径请求访问到其他系统文件

第一步: 实现一个最简单的静态服务器:
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 状态码机制了
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,内容如下。
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 文件下增加如下配置,设置过期时间
exports.Expires = {
maxAge: 60*60*24*365
};
增加如下代码:
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() 方法主要代码如下:
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,增加代码:
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 zlib = require(‘zlib’);
使用流的方式读取文件

修改代码如下:
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 实现静态服务器,抄抄改改哈哈【滑稽】

正文完
 0