共计 5664 个字符,预计需要花费 15 分钟才能阅读完成。
这篇文章的内容主要围绕静态文件服务器实现的功能是什么进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随丸趣 TV 小编一起阅读吧。希望大家通过这篇文章有所收获!
首先先构建好项目目录,项目目录如下:
project |---bin 命令行实现放置脚本 | |---public 静态文件服务器默认静态文件夹 | |---src 实现功能的相关代码 | | | |__template 模板文件夹 | | | |__app.js 主要功能文件(main 文件) | |__config.js 配置文件 | |---package.josn (初始化)
要启动一个服务器,我们需要知道这个服务器的启动时的端口号, 在 config.js 配置一下:
let config = { host: localhost // 提示用 , port:8080 // 服务器启动时候的默认端口号, path:path.resolve(__dirname, .. , test-dir) // 静态服务器启动时默认的工作目录 }
读取静态文件之前首先要先启动服务器, 之后所有的方法都在 class Server 方法里
//handlebar 编译模板,得到一个渲染的方法, 然后传入实际数据数据就可以得到渲染后的 HTML 了 function list() { let tmpl = fs.readFileSync(path.resolve(__dirname, template , list.html), utf8 return handlebars.compile(tmpl);// 进行编译,*** 渲染 }class Server { constructor(argv) { this.list = list(); this.config = Object.assign({}, this.config, argv); } start() { let server = http.createServer();// 创建服务器 // 当客户端向服务端发出数据的时候,会出发 request 事件 server.on(request , this.request.bind(this)); server.listen(this.config.port, () = {// 监听端口号 let url = `http://${this.config.host}:${this.config.port}`; debug(`server started at ${chalk.green(url)}`); }); } // 发送错误信息, sendError(err, req, res) { res.statusCode = 500; res.end(`${err.toString()}`); }} module.exports = Server;
读取静态文件
设计思路
首先输入一个 url 时,可能对应服务器上的一个文件,或者对应一个目录,
检查是否文件还是目录如果文件不存在,返回 404 状态码,发送 not found 页面到客户端
如果文件存在:打开文件读取
设置 response header 发送文件到客户端
如果是目录就打开目录列表
async request(req, res) { // 先取到客户端想要的是文件或文件夹路径 let { pathname } = url.parse(req.url);// 获取路径的文件信息 let filepath = path.join(this.config.root, pathname);// 服务器上的对应服务器物理路径 try { let statObj = await stat(filepath);// 获取路径的文件信息 if (statObj.isDirectory()) {// 如果是目录的话,应该显示目录 下面的文件列表 let files = await readdir(filepath);// 读取文件的文件列表 files = files.map(file = ({// 把每个字符串变成对象 name: file, url: path.join(pathname, file) })); //handlebar 编译模板 let html = this.list({ title: pathname, files }); res.setHeader(Content-Type , text/html 设置请求头 res.end(html); } else { this.sendFile(req, res, filepath, statObj);// 读取文件 } } catch (e) {// 不存在访问内就发送错误信息 debug(inspect(e));//inspect 把一个对象转成字符 this.sendError(e, req, res); } }
缓存支持 / 控制
设计思路
缓存分为强制缓存和对比缓存:
两类缓存规则可以同时存在,强制缓存优先级高于对比缓存,也就是说,当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则.
强制缓存如果生效,不需要再和服务器发生交互,而对比缓存不管是否生效,都需要与服务端发生交互
*** 次访问服务器的时候,服务器返回资源和缓存的标识,客户端则会把此资源缓存在本地的缓存数据库中。
第二次客户端需要此数据的时候,要取得缓存的标识,然后去问一下服务器我的资源是否是 *** 的。如果是 *** 的则直接使用缓存数据,如果不是 *** 的则服务器返回新的资源和缓存规则,客户端根据缓存规则缓存新的数据。
通过 *** 修改时间来判断缓存是否可用
Last-Modified:响应时告诉客户端此资源的 *** 修改时间
If-Modified-Since:当资源过期时(使用 Cache-Control 标识的 max-age),发现资源具有 Last-Modified 声明,则再次向服务器请求时带上头 If-Modified-Since。
服务器收到请求后发现有头 If-Modified-Since 则与被请求资源的 *** 修改时间进行比对。若 *** 修改时间较新,说明资源又被改动过,则响应 *** 的资源内容并返回 200 状态码;
若 *** 修改时间和 If-Modified-Since 一样,说明资源没有修改,则响应 304 表示未更新,告知浏览器继续使用所保存的缓存文件。
ETag 是资源标签。如果资源没有变化它就不会变。
客户端想判断缓存是否可用可以先获取缓存中文档的 ETag,然后通过 If-None-Match 发送请求给 Web 服务器询问此缓存是否可用。
服务器收到请求,将服务器的中此文件的 ETag, 跟请求头中的 If-None-Match 相比较, 如果值是一样的, 说明缓存还是 *** 的,Web 服务器将发送 304 Not Modified 响应码给客户端表示缓存未修改过,可以使用。
如果不一样则 Web 服务器将发送该文档的 *** 版本给浏览器客户端
handleCache(req, res, filepath, statObj) { let ifModifiedSince = req.headers[ if-modified-since let isNoneMatch = req.headers[ is-none-match res.setHeader( Cache-Control , private,max-age=30 //max-age=30 缓存内容将在 30 秒后失效 res.setHeader( Expires , new Date(Date.now() + 30 * 1000).toGMTString()); let etag = statObj.size; let lastModified = statObj.ctime.toGMTString(); res.setHeader( ETag , etag);// 获取 ETag res.setHeader(Last-Modified , lastModified);// 服务器文件的 *** 修改时间 // 任何一个对比缓存头不匹配,则不走缓存 if (isNoneMatch isNoneMatch != etag) {// 缓存过期 return fasle; } if (ifModifiedSince ifModifiedSince != lastModified) {// 缓存过期 return fasle; } // 当请求中存在任何一个对比缓存头,则返回 304,否则不走缓存 if (isNoneMatch || ifModifiedSince) {// 缓存有效 res.writeHead(304); res.end(); return true; } else { return false; } }
支持 gzip 压缩
设计思路
浏览器都会携带自己支持的压缩类型,最常用的两种是 gzip 和 deflate。根据请求头 Accept-Encoding,返回不同的压缩格式.
getEncoding(req, res) { let acceptEncoding = req.headers[ accept-encoding // 获取客户端发送的压缩请求头的信息 if (/\bgzip\b/.test(acceptEncoding)) {// 如果是 gzip 的格式 res.setHeader( Content-Encoding , gzip return zlib.createGzip(); } else if (/\bdeflate\b/.test(acceptEncoding)) {// 如果是 deflate 的格式 res.setHeader( Content-Encoding , deflate return zlib.createDeflate(); } else { return null;// 不压缩 } }
Range 支持,断点续传
设计思路
该选项指定下载字节的范围,常应用于分块下载文件
服务器告诉客户端可以使用 range response.setHeader(Accept-Ranges , bytes)
Server 通过请求头中的 Range:bytes=0-xxx 来判断是否是做 Range 请求,如果这个值存在而且有效,则只发回请求的那部分文件内容,响应的状态码变成 206, 如果无效,则返回 416 状态码,表明 Request
getStream(req, res, filepath, statObj) { let start = 0;// 可读流起始位置 let end = statObj.size - 1;// 可读流结束位置 let range = req.headers[ range // 获取客户端的 range 请求头信息, if (range) {// 断点续传 res.setHeader( Accept-Range , bytes res.statusCode = 206;// 返回整个内容的一块 let result = range.match(/bytes=(\d*)-(\d*)/);// 断点续传的分段内容不能有小数,网络传输的最小单位为一个字节 if (result) { start = isNaN(result[1]) ? start : parseInt(result[1]); end = isNaN(result[2]) ? end : parseInt(result[2]) - 1; } } return fs.createReadStream(filepath, { start, end }); }
发布为可执行命令
首先在 package.json 配置一下 bin : {http-static : bin/www}
#! /usr/bin/env node // 这段代码一定要写在开头,为了兼容各个电脑平台的差异性 // -d --root 静态文件目录 -o --host 主机 -p --port 端口号 let yargs = require(yargs let Server = require( ../src/app.js let argv = yargs.option( d ,{ alias: root , demand: false , type: string , default:process.cwd(), description: 静态文件跟目录 }) .option(o ,{ alias: host , demand: localhost , type: string , description: 请配置监听的主机}) .option(p ,{ alias: root , demand: false , type: number , default:8080, description: 请配置端口号}) .usage(http-static [options] ).example( http-static -d / 8080 -o localhost , 在本机的 9090 端口上监听客户端的请求 ).help(h).argv; // argv = {d,root,o,host,p,port}let server = new Server(argv);// 启动服务 server.start();
这样命令行当中通过输入 http-static 来直接启动静态文件服务器了,那么命令行调用的功能也就实现了,最后用 npm publish 发布一下,发布到 npm 上面去了,我们就可以通过 npm install - g 来进行全局安装了。
感谢你的阅读,相信你对“静态文件服务器实现的功能是什么”这一问题有一定的了解,快去动手实践吧,如果想了解更多相关知识点,可以关注丸趣 TV 网站!丸趣 TV 小编会继续为大家带来更好的文章!