博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
搞起node.js静态服务器并实战前端缓存
阅读量:6847 次
发布时间:2019-06-26

本文共 8590 字,大约阅读时间需要 28 分钟。

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

  • 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');});

附上一篇不错的文章,里面还有更多的一些细节,抄抄改改哈哈【滑稽】

转载地址:http://aflul.baihongyu.com/

你可能感兴趣的文章
批量插入,更新,删除数据
查看>>
mysql 查看用户的权限
查看>>
JavaScript 函数节流和函数去抖应用场景辨析
查看>>
log4j的参数配置(转)
查看>>
[C++][基础]1_变量、常量和基本类型
查看>>
Android Service与Runnable整合并用
查看>>
Php综合手册
查看>>
[轉]javascript 的 location 各種用法
查看>>
MySQL 数据文件 说明
查看>>
测地膨胀和膨胀重建—lhMorpRDilate
查看>>
30 +最佳移动网络设计灵感的案例
查看>>
C++基础代码--20余种数据结构和算法的实现
查看>>
深入探索PowerPivot客户端和服务器端架构
查看>>
fash 3D 游戏
查看>>
Android 用户界面---广播通知(Toast Notifications)
查看>>
HDOJ 2090
查看>>
Java线程中断的本质和编程原则
查看>>
ODBC 、DAO 、ADO 、OLEDB 数据库连接方式区别及联系
查看>>
First glance in Go
查看>>
24点经典算法
查看>>