学习服务端知识,入门就是要把文件挂载到服务器上,咱们才能去访问相应的文件。本地开发的时候,咱们也会常常把文件放在服务器上去访问,以便达到在同一个局域网内,经过同一个服务器地址访问相同的文件,好比咱们会用xampp,会用sulime的插件sublime-server等等。本篇文章就经过node,手写一个静态资源服务器,以达到你能够随意定义任何一个文件夹为根目录,去访问相应的文件,达到anywhere is your static-server。html
//install
$ npm i st-server -g
//forhelp
$ st-server -h
//start
$ st-server
// or with port
$ st-server -p 8800
// or with hostname
$ st-server -o localhost -p 8888
// or with folder
$ st-server -d /
// full parameters
$ st-server -d / -p 9900 -o localhost
复制代码
其中能够配置三个参数,-d表明你要访问的根目录,-p表明端口号(目前暂不支持屡次开启用同一个端口号,须要手动杀死以前的进程),-o表明hostname。 全部源代码已经上传至github。node
class StaticServer{
constructor(argv){
this.config = Object.assign({},config,argv);
this.compileTpl = compileTpl();
}
startServer(){
let server = http.createServer();
server.on('request',this.request.bind(this));
server.listen(this.config.port,()=>{
let serverUrl = `http://${this.config.host}:${this.config.port}`;
debug(`服务已开启,地址为${chalk.green(serverUrl)}`);
})
}
}
复制代码
async request(req,res){
let {pathname} = url.parse(req.url);
if(pathname == '/favicon.ico'){
return this.sendError('NOT FOUND',req,res);
}
//获取须要读的文件目录
let filePath = path.join(this.config.root,pathname);
let statObj = await fsStat(filePath);
if(statObj.isDirectory()){//若是是一个目录的话 列出目录下面的内容
let files = await readDir(filePath);
let isHasIndexHtml = false;
files = files.map(file=>{
if(file.indexOf('index.html')>-1){
isHasIndexHtml = true;
}
return {
name:file,
url:path.join(pathname,file)
}
})
if(isHasIndexHtml){
let statObjN = await fsStat(filePath+'/index.html');
return this.sendFile(req,res,filePath+'/index.html',statObjN);
}
let resHtml = 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');
let encoding = this.getEncoding(req,res);
//常见一个可读流
let rs = this.getPartStream(req,res,filePath,statObj);
if(encoding){
rs.pipe(encoding).pipe(res);
}else{
rs.pipe(res);
}
}
复制代码
sendFile方法就是向浏览器输出内容的方法,主要包括如下几个重要的点:git
getFileFromCache(req,res,statObj){
let ifModifiedSince = req.headers['if-modified-since'];
let isNoneMatch = req.headers['if-none-match'];
res.setHeader('Cache-Control','private,max-age=60');
res.setHeader('Expires',new Date(Date.now() + 60*1000).toUTCString());
let etag = crypto.createHash('sha1').update(statObj.ctime.toUTCString() + statObj.size).digest('hex');
let lastModified = statObj.ctime.toGMTString();
res.setHeader('ETag', etag);
res.setHeader('Last-Modified', lastModified);
if (isNoneMatch && isNoneMatch != etag) {
return false;
}
if (ifModifiedSince && ifModifiedSince != lastModified) {
return false;
}
if (isNoneMatch || ifModifiedSince) {
res.statusCode = 304;
res.end('');
return true;
} else {
return false;
}
}
复制代码
这里咱们经过Last-Modified,ETag实现协商缓存,Cache-Control,Expires实现强制缓存,当全部缓存条件成立时才会生效。Last-Modified原理是经过文件的修改时间,判断文件是否修改过,ETag经过文件内容的加密判断是否修改过。Cache-Control,Expire经过时间进行强缓。 2. 对文件进行压缩,压缩文件之后能够减小体积,加快传输速度和节约带宽 ,这里支持gzip和deflate两种方式,用node自己的模块zlib进行处理。github
getEncoding(req,res){
let acceptEncoding = req.headers['accept-encoding'];
if(acceptEncoding.match(/\bgzip\b/)){
res.setHeader('Content-Encoding','gzip');
return zlib.createGzip();
}else if(acceptEncoding.match(/\bdeflate\b/)){
res.setHeader('Conetnt-Encoding','deflate');
return zlib.createDeflate();
}else{
return null;
}
}
复制代码
getPartStream(req,res,filePath,statObj){
let start = 0;
let end = statObj.size -1;
let range = req.headers['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
})
}
复制代码
#! /usr/bin/env node
let yargs = require('yargs');
let argv = yargs.option('d', {
alias: 'root',
demand: 'false',
type: 'string',
default: process.cwd(),
description: '静态文件根目录'
}).option('o', {
alias: 'host',
demand: 'false',
default: 'localhost',
type: 'string',
description: '请配置监听的主机'
}).option('p', {
alias: 'port',
demand: 'false',
type: 'number',
default: 8800,
description: '请配置端口号'
})
.usage('st-server [options]')
.example(
'st-server -d / -p 9900 -o localhost', '在本机的9900端口上监听客户端的请求'
).help('h').argv;
let path = require('path');
let {
spawn
} = require('child_process');
let p1 = spawn('node', ['www.js', JSON.stringify(argv)], {
cwd: __dirname
});
p1.unref();
process.exit(0);
复制代码
anywherenpm