读取静态文件MIME类型支持缓存支持/控制支持gzip压缩Range支持,断点续传发布为可执行命令,可以后台运行,可以通过npminstall-g安装首先建立好项目目录,项目目录如下:project|---bin命令行实现放置脚本||---public静态文件服务器默认静态文件夹||---src实现相关功能代码||||__template模板文件文件夹||||__app.js主函数文件(主文件)||__config.js配置文件||---package.josn(初始化)启动一个服务器,我们需要知道启动时的端口号服务器启动,在config.js中配置:letconfig={host:'localhost'//提示符,port:8080//服务器启动时的默认端口号,path:path.resolve(__dirname,'..','test-dir')//静态服务器启动时的默认工作目录}在读取静态文件之前,ser必须先启动ver,然后所有的方法都在类Server方法中//handlebar编译模板,得到一个渲染方法,然后传入实际的data数据即可得到渲染后的HTMLfunctionlist(){lettmpl=fs.readFileSync(path.resolve(__dirname,'template','list.html'),'utf8');returnhandlebars.compile(tmpl);//compile,***render}classServer{constructor(argv){this.list=list();this.config=Object.assign({},this.config,argv);}start(){letserver=http.createServer();//创建服务器//当客户端向服务器发送数据时,会触发请求事件server.on('request',this.request.bind(this));server.listen(this.config.port,()=>{//监听端口号leturl=`http://${this.config.host}:${this.config.port}`;debug(`serverstartedat${chalk.green(url)}`);});}//发送错误信息,sendError(err,req,res){res.statusCode=500;res.end(`${err.toString()}`);}}module.exports=Server;读取静态文件的设计思路第一次输入url的时候,可能对应的是服务器上的一个文件。或者对应一个目录,查看是文件还是目录。如果文件不存在,则返回404状态码,并将未找到页面发送给客户端。如果文件存在:打开文件读取并设置响应头。将文件发送给客户端。如果是目录,打开目录列表asyncrequest(req,res){//先获取客户端想要的文件或文件夹路径let{pathname}=url.parse(req.url);//获取文件信息ofthepathletfilepath=path.join(this.config.root,pathname);//服务器上对应的服务器物理路径try{letstatObj=awaitstat(filepath);//获取该路径的文件信息if(statObj.isDirectory()){//如果是目录,应该显示目录下的文件列表letfiles=awaitreaddir(filepath);//读取文件列表files=files.map(file=>({//把每个字符串转成一个对象名:file,url:path.join(pathname,file)}));//handlebar编译模板lethtml=this.list({title:pathname,files});res.setHeader('内容类型','文本/html');setrequestheaderres.end(html);}else{this.sendFile(req,res,filepath,statObj);//读取文件}}catch(e){//没有访问则发送错误信息debug(inspect(e));//inspect将一个对象转换成一个字符this.sendError(e,req,res);}}缓存支持/控制设计思路缓存分为强制缓存和比较缓存:两种缓存规则可以同时存在,强制缓存的优先级高于比较缓存,也就是说,当执行强制使用缓存规则时,如果缓存生效,则直接使用缓存,不再执行比较缓存规则。强制缓存生效则无需与服务器交互,对比缓存无论是否生效都需要与服务器交互***首次访问服务器时,服务器返回标识资源和缓存,客户端会将资源缓存在本地缓存数据库中。客户端第二次需要这个数据,需要获取缓存的标识,然后向服务器询问Aremyresourcessecure。如果是最好的,就直接使用缓存的数据。如果不是,则服务端返回新的资源和缓存规则,客户端根据缓存规则缓存新的数据。通过最近修改时间判断缓存是否可用Last-Modified:响应时告诉客户端这个资源的最后修改时间If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),如果发现资源有Last-Modified语句,再次请求服务器时会带上If-Modified-Since头。服务器收到请求后,发现有一个headerIf-Modified-Since,将其与请求资源的最新修改时间进行比较。如果***的修改时间较新,说明该资源又被修改过,则响应***的资源内容,返回200状态码;如果***的修改时间和If-Modified-Since一样,说明资源没有被修改过,那么Response304表示没有更新,告诉浏览器继续使用保存的缓存文件。ETag是一个资源标签。如果资源不改变,它就不会改变。如果客户端要判断缓存是否可用,可以先获取缓存中文档的ETag,然后通过If-None-Match向Web服务器发送请求,询问缓存是否可用。服务器收到请求,将服务器中这个文件的ETag与请求头中的If-None-Match进行比较,如果值相同,则说明缓存仍然有效,web服务器将发送一个304NotModified响应码,向客户端表明缓存没有被修改,可以使用。如果没有,Web服务器会将最新版本的文档发送到浏览器客户端标头['不匹配'];res.setHeader('Cache-Control','private,max-age=30');//max-age=30缓存内容30秒后失效res.setHeader('Expires',newDate(Date.now()+30*1000).toGMTString());letetag=statObj.size;letlastModified=statObj.ctime.toGMTString();res.setHeader('ETag',etag);//GetETagres.setHeader('Last-Modified',lastModified);//服务端文件的最新修改时间//如果任何一个比较缓存头不匹配,则缓存不走if(isNoneMatch&&isNoneMatch!=etag){//缓存过期returnfasle;}if(ifModifiedSince&&ifModifiedSince!=lastModified){//缓存过期returnfasle;}//当请求中有比较缓存头时,返回304,否则不去缓存if(isNoneMatch||ifModifiedSince){//Cacheisvalidres.writeHead(304);res.end();returntrue;}else{returnfalse;}}支持gzip压缩设计思路浏览器会自带支持的压缩类型,最常用的两种是gzip和放气。根据请求头Accept-Encoding,返回不同的压缩格式。getEncoding(req,res){letacceptEncoding=req.headers['accept-encoding'];//获取客户端发送的压缩请求头信息if(/\bgzip\b/.test(acceptEncoding)){//如果是gzip格式res.setHeader('Content-Encoding','gzip');returnzlib.createGzip();}elseif(/\bdeflate\b/.test(acceptEncoding)){//如果是deflate的格式res.setHeader('Content-Encoding','deflate');returnzlib.createDeflate();}else{returnnull;//不压缩}}范围支持,breakpointresume设计思路该选项指定下载的字节范围,常用于分块下载文件。服务器告诉客户端它可以使用范围response.setHeader('Accept-Ranges','bytes')并且服务器在请求标头中传递Range:bytes=0。-xxx判断是否是Range请求。如果该值存在且有效,则只返回请求的部分文件内容,响应的状态码变为206。如果无效,则返回416状态码,表示RequestgetStream(req,res,filepath,statObj){letstart=0;//可读流的起始位置letend=statObj.size-1;//可读流的结束位置letrange=req.headers['range'];//获取客户端的range请求头信息,if(range){//res.setHeader('Accept-Range','bytes');res.statusCode=206;//返回全部内容的一部分letresult=range.match(/bytes=(\d*)-(\d*)/);//断点续传的内容不能有小数,网络传输的最小单位是一个字节if(result){start=isNaN(result[1])?开始:parseInt(结果[1]);end=isNaN(result[2])?end:parseInt(result[2])-1;}}returnfs.createReadStream(filepath,{start,end});}先在package.json中发布为可执行命令Configure"bin":{"http-static":"bin/www"}#!/usr/bin/envnode//这段代码一定要写在开头,为了兼容各种电脑平台的差异//-d--root静态文件目录-o--host主机-p--port端口号letyargs=require('yargs');letServer=require('../src/app.js');letargv=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-olocalhost','在本机监听客户端在9090端口的请求').help('h').argv;//argv={d,root,o,host,p,port}letserver=newServer(argv);//启动服务server.start();这样就可以直接在命令行输入http-static启动静态文件服务器,实现命令行调用的功能。***使用npmpublish发布,发布到npm,我们可以通过npminstall-g进行全局安装
