女神镇楼css
咱们都知道在本地起个服务直接一个
http-server -p 3000
一个端口为3000的服务就起来了,咱们能够直接在浏览器访问3000端口,就能拿到咱们须要的页面,那么若是想本身实现一个这样的工具怎么作呢?不要急,看我慢慢分析写出来html
写以前咱们先要搞清楚要作什么:用本身写的包,起一个服务,访问3000端口回车,应该显示出public下的目录列表,后面加/index.html,就应该显示index.html的内容来node
index.css
body{
background: red
}
复制代码
index.html
ios
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
我很美
<link rel="stylesheet" href="/index.css">
</body>
</html>
复制代码
config.js
git
let path = require('path')
//启动服务的配置项
let config = {
hostname:'localhost',
port:3000,
dir:path.join(__dirname,'..','public')
}
module.exports = config
复制代码
app.js
这里用到了debug(用法请看这里)github
// set DEBUG=static:app (win32 // export DEBUG=static:app (iosweb
let config = require('./config')
let path = require('path')
let fs = require('fs')
let mime = require('mime')
let chalk = require('chalk')
let util = require('util')
let url = require('url')
let http = require('http')
let stat = util.promisify(fs.stat)
//debug 能够后面放参数,能够根据后面的参数决定是否打印
let debug = require('debug')('static:app')
//console.log(chalk.green('hello'));
//debug('app')
class Server { //首先写一个Server类
constructor(){
this.config = config
}
handleRequest(){
return (req,res)=>{
}
}
start(){ //实例上的start方法
let {port,hostname} = this.config
let server = http.createServer(this.handleRequest())
//用http启动一个服务,回调里执行handleRequest方法
let url = `http://${hostname}:${chalk.green(port)}`
debug(url);
server.listen(port, hostname);
}
}
let server = new Server()
server.start()
复制代码
node执行app.js,(在执行以前要先执行set DEBUG=static:app)获得下图 chrome
handleRequest(){
return async(req,res)=>{
//处理路径
let {pathname} = url.parse(req.url,true)
//由于拿到的pathname会是/index,这样会直接指向c盘,加./的话就变成当前
let p = path.join(this.config.dir,'.'+pathname)
try{
let statObj = await stat(p)//判断p路径对不对
if(statObj.isDirectory()){
}else{
//是文件就直接读了
res.setHeader('Content-Type',mime.getType(p)+';charset=utf8')
fs.createReadStream(p).pipe(res)
}
}catch(e){
res.statusCode = 404;
res.end()
}
}
}
复制代码
这时候访问 http://localhost:3000/index.html,就能出页面信息了npm
sendFile(req,res,p){
//是文件就直接读了
res.setHeader('Content-Type',mime.getType(p)+';charset=utf8')
fs.createReadStream(p).pipe(res)
}
sendError(req,res,e){
debug(util.inspect(e).toString())
res.statusCode = 404;
res.end()
}
复制代码
src/tmpl.ejs
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>staticServer</title>
</head>
<body>
<!-- 碰到js就 <% %>包起来,求值用= -->
<%dirs.forEach(dir=>{%>
<li><a href="<%=dir.path%>"><%=dir.name%></a></li>
<% })%>
</body>
</html>
复制代码
那在app.js中就要放进去数组
let ejs = require('ejs')
let tmpl = fs.readFileSync(path.join(__dirname,'tmpl.ejs'),'utf8')
let readDir = util.promisify(fs.readdir)//读取目录用的方法
复制代码
git上各类模板在这里
再把tmpl挂在this上 app.js那么若是是目录的话这个代码就这么写
if(statObj.isDirectory()){
//若是是目录的话就应该把目录放出去
//用模板引擎写 handlebal ejs underscore jade
let dirs = await readDir(p)
debug(dirs)//返回的是个数组[index.css,index.html]
dirs = dirs.map(dir => ({
path: path.join(pathname, dir),
name: dir
}))
let content = ejs.render(this.tmpl,{dirs})
res.setHeader('Content-Type','text/html;charset=utf8')
res.end(content)
}else{
this,this.sendFile(req,res,p)
}
复制代码
缓存
第一次请求:
1,客户端发起 HTTP GET 请求一个文件。
2,服务器处理请求,返回文件内容以及相应的 Header,其中包括 Etag(例如:627-4d648041f6b80)(假设服务器支持 Etag 生成并已开启了 Etag)状态码为 200。
复制代码
第二次请求(断点续传):
1,客户端发起 HTTP GET 请求一个文件,同时发送 If-Range(该头的内容就是第一次请求时服务器返回的 Etag:627-4d648041f6b80)。
2,服务器判断接收到的 Etag 和计算出来的 Etag 是否匹配,若是匹配,那么响应的状态码为 206;不然,状态码为 200。
复制代码
cache(req,res,statObj){
//etag if-none-match
//Last-Modified if-modified-since
//Cache-Control
//ifNoneMatch通常是内容的md5戳 => ctime+size
let ifNoneMatch = req.headers['if-none-match']
//ifModifiedSince文件的最新修改时间
let ifModifiedSince = req.headers['if-modified-since']
let since = statObj.ctime.toUTCString();//最新修改时间
//表明的是服务器文件的一个描述
let etag = new Date(since).getTime() +'-'+statObj.size
res.setHeader('Cache-Control','max-age=10')
//10秒以内强制缓存
res.setHeader('Etag',etag)
res.setHeader('Last-Modified',since) //请求头带着
//再访问的时候对比,若是相等,就走缓存
if(ifNoneMatch !== etag){
return false
}
if(ifModifiedSince != since){
return false
}
res.statusCode = 304
res.end()
return true
}
复制代码
sendFile
中加这句话
//缓存
if(this.cache(req,res,statObj)) return
复制代码
那么访问index.html 访问的画面是
压缩
由于用到了zlib因此要在头上加上
let zlib = require('zlib');
复制代码
压缩方法
compress(req,res,statObj){
// 压缩 Accept-Encoding: gzip,deflate,br
// Content-Encoding:gzip
let header = req.headers['accept-encoding']
if(header){
if(header.match(/\bgzip\b/)){
res.setHeader('Content-Encoding','gzip')
return zlib.createGzip()
}else if(header.match(/\bdeflate\b/)){
res.setHeader('Content-Encoding','deflate')
return zlib.createDeflate()
}else{
return false //不支持压缩
}
}else{
return false
}
}
复制代码
sendFile
sendFile(req,res,p,statObj){
//缓存
if(this.cache(req,res,statObj)) return
//压缩
let s = this.compress(req, res, p, statObj);
console.log(s)
res.setHeader('Content-Type',mime.getType(p)+';charset=utf8')
let rs = fs.createReadStream(p)
if(s){
//若是支持就是返回的流
rs.pipe(s).pipe(res)
}else{
rs.pipe(res)
}
//是文件就直接读了
// fs.createReadStream(p).pipe(res)
}
复制代码
查看一下是否压缩成功:访问http://localhost:3000/index.css
范围请求
方法
range(req,res,statObj){
//范围请求的头 :Rang:bytes=1-100
//服务器 Accept-Ranges:bytes
//Content-Ranges:1-100/total
let header = req.headers['range']
//header =>bytes=1-100
let start = 0;
let end = statObj.size;//整个文件的大小
if(header){
res.setHeader('Content-Range','bytes')
res.setHeader('Accept-Ranges',`bytes ${start}-${end}/${statObj.size}`)
let [,s,e] = header.match(/bytes=(\d*)-(\d*)/);
start = s?parseInt(s):start
end = e? parseInt(e):end
}
return {start,end:end-1}//由于start是从0开始
}
复制代码
sendFile
文件就是这样了
sendFile(req, res, p, statObj) {
// 缓存的功能 对比 强制
if (this.cache(req, res, statObj)) return;
// 压缩 Accept-Encoding: gzip,deflate,br
// Content-Encoding:gzip
res.setHeader('Content-Type', mime.getType(p) + ';charset=utf8');
let s = this.compress(req, res, p, statObj);
// 范围请求
let {start,end} = this.range(req,res,statObj);
let rs = fs.createReadStream(p,{start,end})
if (s) {
rs.pipe(s).pipe(res);
} else {
rs.pipe(res);
}
}
复制代码
在命令行工具下执行
curl -v --header "Range:bytes=1-3" http://localhost:3000/index.html 就能够看到效果了
点个赞再走嘛