使用node手动配置一个静态资源服务器主要实现功能
读取静态文件
序列号 | CPU | RAM | HDD | 带宽 | 售价(美元) | 免费试用 |
---|---|---|---|---|---|---|
香港服务器1 | E5-2620 | 32G | 1T HDD | 50M/无限流量 | $196.00 | 立即申请 |
香港服务器2 | E5-2650 | 32G | 1T HDD | 50M/无限流量 | $256.00 | 立即申请 |
香港服务器3 | E5-2680 | 32G | 1T HDD | 50M/无限流量 | $316.00 | 立即申请 |
香港服务器4 | E5-2690 | 32G | 1T HDD | 50M/无限流量 | $336.00 | 立即申请 |
香港服务器5 | E5-2697 | 32G | 1T HDD | 50M/无限流量 | $376.00 | 立即申请 |
香港服务器6 | E5-2620*2 | 32G | 1T HDD | 50M/无限流量 | $376.00 | 立即申请 |
香港服务器7 | E5-2650*2 | 32G | 1T HDD | 50M/无限流量 | $436.00 | 立即申请 |
香港服务器8 | E5-2680*2 | 32G | 1T HDD | 50M/无限流量 | $476.00 | 立即申请 |
香港服务器9 | E5-2690*2 | 32G | 1T HDD | 50M/无限流量 | $556.00 | 立即申请 |
香港服务器10 | E5-2697*2 | 32G | 1T HDD | 50M/无限流量 | $596.00 | 立即申请 |
香港服务器11 | E5-2680v4*2 | 32G | 1T HDD | 50M/无限流量 | $696.00 | 立即申请 |
香港服务器12 | E5-2698v4*2 | 32G | 1T HDD | 50M/无限流量 | $796.00 | 立即申请 |
静态资源缓存
资源压缩
MIME类型支持
断点续传
发布为可执行命令并可以后台运行,可以通过npm install -g安装
Useage
//install$npmist-server-g//forhelp$st-server-h//start$st-server//orwithport$st-server-p8800//orwithhostname$st-server-olocalhost-p8888//orwithfolder$st-server-d///fullparameters$st-server-d/-p9900-olocalhost
其中可以配置三个参数,-d代表你要访问的根目录,-p代表端口号(目前暂不支持多次开启用同一个端口号,需要手动杀死之前的进程),-o代表hostname。
源码分析
全部代码基于一个StaticServer类进行实现,在构造函数中首先引入所有的配置,argv是通过命令行敲入传进来的参数,然后在获取需要编译的模板,该模板是简单的显示一个文件夹下所有文件的列表。基于handlebars实现。然后开启服务,监听请求,由this.request()处理
classStaticServer{constructor(argv){this.config=Object.assign({},config,argv);this.compileTpl=compileTpl();}startServer(){letserver=http.createServer();server.on('request',this.request.bind(this));server.listen(this.config.port,()=>{letserverUrl=`http://${this.config.host}:${this.config.port}`;debug(`服务已开启,地址为${chalk.green(serverUrl)}`);})}}
主线就是读取想要搭建静态服务的地址,如果是文件夹,则查找该文件夹下是否有index.html文件,有则显示,没有则列出所有的文件;如果是文件的话,则直接显示该文件内容。大前提在显示具体的文件之前,要判断有没有缓存,有直接获取缓存,没有的话再请求服务器。
asyncrequest(req,res){let{pathname}=url.parse(req.url);if(pathname=='/favicon.ico'){returnthis.sendError('NOTFOUND',req,res);}//获取需要读的文件目录letfilePath=path.join(this.config.root,pathname);letstatObj=awaitfsStat(filePath);if(statObj.isDirectory()){//如果是一个目录的话列出目录下面的内容letfiles=awaitreadDir(filePath);letisHasIndexHtml=false;files=files.map(file=>{if(file.indexOf('index.html')>-1){isHasIndexHtml=true;}return{name:file,url:path.join(pathname,file)}})if(isHasIndexHtml){letstatObjN=awaitfsStat(filePath+'/index.html');returnthis.sendFile(req,res,filePath+'/index.html',statObjN);}letresHtml=this.compileTpl({title:filePath,files})res.setHeader('Content-Type','text/html');res.end(resHtml);}else{this.sendFile(req,res,filePath,statObj);}}sendFile(req,res,filePath,statObj){//判断是否走缓存if(this.getFileFromCache(req,res,statObj))return;//如果走缓存,则直接返回res.setHeader('Content-Type',mime.getType(filePath)+';charset=utf-8');letencoding=this.getEncoding(req,res);//常见一个可读流letrs=this.getPartStream(req,res,filePath,statObj);if(encoding){rs.pipe(encoding).pipe(res);}else{rs.pipe(res);}}
sendFile方法就是向浏览器输出内容的方法,主要包括以下几个重要的点:
缓存处理
getFileFromCache(req,res,statObj){letifModifiedSince=req.headers['if-modified-since'];letisNoneMatch=req.headers['if-none-match'];res.setHeader('Cache-Control','private,max-age=60');res.setHeader('Expires',newDate(Date.now()+60*1000).toUTCString());letetag=crypto.createHash('sha1').update(statObj.ctime.toUTCString()+statObj.size).digest('hex');letlastModified=statObj.ctime.toGMTString();res.setHeader('ETag'