Web Server之处理静态文件
2016-08-05 12:01
337 查看
Web Server之处理静态文件
标签(空格分隔): webserver细心的同学会发现,在express server的代码中,包含这么一句
app.use('/static', express.static('public'));,什么用呢?这个是用来处理静态文件,所有访问
/static及其子目录下的url均会检查静态文件,如果静态文件存在,则直接返回静态资源。
是不是很简单,但是我们还得深挖一下,看这货到底是怎么实现的。
handle static file
我们还是通过原生态的httpServer,新建一个静态文件的处理流程。var http = require("http"); var url = require("url"); var fs = require("fs"); var mimeType = require("mime-types"); http.createServer(function(req, res){ var obj = url.parse(req.url); var pathname = obj.pathname; if(pathname.indexOf("/static")===0){ var fileName = __dirname + pathname; fs.stat(fileName, function(err, is){ if(err){ console.error(err); res.writeHead(500, "Error occur."); res.end(); } else{ if(is.isFile()){ fs.readFile(fileName, function(err, buf){ if(err){ console.error(err); res.writeHead(500, "Read file error."); res.end(); } else{ var tempObject = { "Content-Type" : mimeType.lookup(pathname), "Last-Modified": is.mtime.toLocaleString(), "Content-Length": is.size }; res.writeHead(200, tempObject); res.write(buf); res.end(); } }); } else{ res.writeHead(404, "Not Found"); res.end(); } } }); } else{ res.writeHead(200); res.write("Hello!"); res.end(); } }).listen(3001);
代码解析
获取模块
http、
url、
fs、
mime-types
新建一个httpServer实例,并绑定端口3001.
定义httpServer的处理逻辑,对于/static开头的请求,均按照静态文件进行处理,如果文件存在,则将文件内容返回值Response对象,否则则返回404;
API介绍
看着代码,两眼一抹黑!这是什么鬼,怎么会如此之多,而且req、res这是啥,怎么中知道哪些方法能用,哪些属性能访问。这个我只能告诉你,平时多看书,需要时自觉上网查资料。此处我们不妨列举一下用到的对象。http.Server
通过http.createServer创建一个Server实例
方法
server.close([callback]) server不在接收新的端口
server.listen(param, [callback]) 监听端口或者一个Unix Socker path。
其他,具体参考https://nodejs.org/api/http.html#http_class_http_server
事件
checkContinue
clientError
close
connect
connection
request
upgrade
http.ServerResponse
http请求一旦建立,会产生一个request实例(IncomingMessage)和response实例(http.ServerResponse),下面介绍一下常用的方法。方法
response.end(data, [encoding], [callback]) 通过该方法告诉服务器所有的header和body均已经发送。因此每个http请求均需要调用该方法来结束处理,否则服务器将持续等待。
response.finished 在调用end方法之前,该值为false,调用之后该值为false,可以通过该方法判断response是否调用end()方法。
response.getHeader(name) 获取指定header的值
response.removeHeader(name) 删除指定header
response.setHeader(name, value)
response.writeHead(code[, codeMessage][, header]) 发送header,包括状态码,调用此方法之后headersSent将置为true。
response.headerSent 只读,表示headers是否被发送。
response.write(chuck, [encoding], [callback]) 异步发送数据。
事件
close
finish
http.IncomingMessage
request的类。方法
request.destroy([error])
request.headers 一个object对象,内部的key使用小写格式,如content-type、last-modified等
request.httpVersion http版本,如1.1/1.0
request.method http使用的方法,如GET、POST、DELETE等
request.rawHeaders 原始header,request.headers是经过滤重处理的,而request.rawHeaders未经过任何处理。
request.url, url属性,可以通过url模块处理该字段。
fs.Stats
通过fs.stat(filePath, callback)可以在callback中得到一个Stats对象,Stats对象包括的属性和方法如下:属性&方法
stats.isFile() 是否文件
stats.iDirectory() 是否文件夹
stats.isBlockDevice() 是否块设备
stats.isCharacterDevice() 是否字符设备
stats.isSymbolicLink() 是否链接
stats.isFIFO() 是否管道文件
stats.isSocket() 是否Socket文件
属性(节选)
size 文件大小,单位字节
blksize: 块大小,单位字节
atime: access time,Date对象
mtime: modified time, Date对象,上次更改时间,
ctime: change time, Date对象,上次更改时间,重命名,更改权限等均能修改该属性。
birthtime: Birth Time,创建时间。
url.parse方法
一个url通过url.parse方法会返回一个object对象,对象格式如下:{ href: "full url", protocol : "", // 协议 slashes: true | false, // host: "", // hostname + port, 如 "host.com:8080" auth: "", // 授权信息,如URL(http://)"user:pass", hostname: "", // host信息,不包括端口, port: number, // 端口信息,如http的80, ftp的21 pathname: "", // 文件地址,如abc/d/e.txt search: "", // query部分,如?query=string path: "" // pathname + search集合 }
更深一步
对于静态资源而言,一但生成,修改的可能性一般都不大,在实际操作过程,我们可以利用缓存以减少带宽的压力。一般地,浏览器会根据header中的字段,判断是否利用缓存。因此,我们可以将代码略做修改:
>
1. 获取request中的If-Modified-Since字段,如果非空,比当前文件的last modified time进行比较,如果If-Modified-Since小于last modified time,进入第3步,否则直接返回304.
2. 获取request中的If-None-Match字段,如果字段非空,且If-None-Match等于文件的MD5值,直接返回304,否则进入第3步。
3. 读取文件,设置Response的Last-Modified、Expires、Cache-Control、ETag字段。
// 处理修改时间 var lastModifiedTime = is.mtime; var ifModifiedSince = req.headers["if-modified-since"]; if(ifModifiedSince){ ifModifiedSince = new Date().strtotime(ifModifiedSince); if(!(ifModifiedSince instanceof Date)){ ifModifiedSince = null; } } if(ifModifiedSince && ifModifiedSince.getTime() >= lastModifiedTime.getTime()){ res.writeHead(304); res.end(); return ; } // 处理etag var et = eTag(is); var ifNoneMatch = req.headers["if-none-match"]; if(ifNoneMatch && et === ifNoneMatch){ res.writeHead(304); res.end(); return ; } // 如果modified和etag校验均失败,返回静态文件内容。 var tempObject = { "Expires": new Date().strtotime("30 days").format("ddd, dd mmm yyyy HH:MM:ss Z"), "Content-Type" : mimeType.lookup(pathname), "Last-Modified": lastModifiedTime.format('ddd, dd mmm yyyy HH:MM:ss Z'), "eTag": et, "Content-Length": is.size, "Cache-Control": "public,max-age=2592000000" }; res.writeHead(200, tempObject); res.write(buf); res.end();
代码分析
获取request header中的If-Modified-Since值,并与当前文件的Last Mofidified Time比较;如果If-Modified-Since大于等于Last Mofidified Time,则表示服务器文件并未更新,直接返回304;
通过etag包的方法当前文件的etag值,并获取request header中If-None-Match值,两者对比;
上述比配均失败时,获取文件内容,并在header中写入Expires、Last-Modified、Etag。
再深一步
我们可以通过浏览器验证一下,执行如下代码:var loadJS = function(src){ var script = document.createElement("script"); script.type = "text/javascript"; script.src = src; document.body.appendChild(script); }
然后执行
loadJS("http://localhost:3001/static/js/index.js");10次。监控网络状况,可以发现如下情况:
浏览器只发送了一次请求!!!
此时我们再使用浏览器打开
http://localhost:3001/static/js/index.js,发现网络请求如下:
没有意外,直接返回304。使用调试工具,查看网络请求,如下图:
我们可以看出,在request请求中包含了两个header
* If-Modified-Since
* If-None-Match
那么问题来了,浏览器为什么会只发送一个请求:
当浏览器首次获取资源时,资源返回内容并附带Expires,Cache-Control,Last-Modified,ETag;当浏览器再次请求时,浏览器根据Expires、Cache-Control,判断是否从cache中返回,此例中,直接从cache中返回,http状态码为200;此后无论发送多少次请求,只要Cache没有过期,均从cache中获取。
附:浏览器缓存操作:
参考文献
Node.js API: https://nodejs.org/dist/latest-v4.x/docs/api/浏览器缓存: http://www.alloyteam.com/2012/03/web-cache-2-browser-cache/
相关文章推荐
- javaweb中静态文件的常用处理方法汇总
- Spring Boot Web 静态文件缓存处理的方法
- web服务器与应用服务器静态文件处理
- ASP.NET Core应用针对静态文件请求的处理[1]: 以Web的形式发布静态文件
- Golang Web 框架 Beego 静态文件处理 - 07
- 新浪SAE python web静态文件处理
- SAE web.py-处理根目录下(或者其他非static目录下的)的静态文件
- JavaWeb项目中springmvc和tomcat对静态文件的处理
- java 的 web 服务器 jswdk 的配置文件 webserver.xml
- Django 静态文件处理 三部曲
- Sys.WebForms.PageRequestManagerServerErrorException: 在服务器上处理请求时出现未知错误解决办法
- webrequest请求JAVA servlet,webresponse获得返回的压缩后的XML文件,然后再解压处理
- web.config文件中的特殊字符处理
- 指定web.config让httphandler处理某目录及子目录下所有文件
- 从GridView中直接导出数据到Excel文件 处理导出乱码 类型“GridView”的控件“XXXX”必须放在具有 runat=server 的窗体标记内。”的异常
- Django中,图片显示问题--Django中静态文件处理方法指南
- Visual Studio 在根目录下运行文件系统站点 [ Visual Studio | 文件系统 | WebDev.WebServer.EXE ]
- 访问web.inf文件.....关于访问html静态文件的tomcat原理解析.....
- java web应用文件下载(包括中文文件名乱码处理)