Node笔记四(静态资源服务器)

资源

各种进阶资源 + 联系方式请看下方链接
资源

http
  • 可以根据这个地址看看实例https://nodejs.org/en/docs/guides/getting-started-guide/
const http = require('http');
const path = require('path');
const fs = require('fs');
const hostname = '10.200.10.219';//主机名 一般本地开发用本地
const port = 3000//端口号
//createServer方法:返回一个新建的http.server的实例 http.cerateServer([requestListener])
//requestListener是一个函数,会被自动添加到'request'事件中
//request事件:每次接受一个请求时触发。需要注意的是 每个连接可能有多个请求
const server = http.createServer((req,res)=>{//创建一个http的server
//当想通过调试查看req res的值时 不要启动 直接打断点 然后启动断点 会看到控制台给出一个地址 在地址栏输入 就可以看到res req的值了
    //req就是把客户端发过来的请求封装到这个对象中
    //res服务器生成的内容 或 设置 都在这个对象中
    const filepath = path.join(process.cwd(),url)//获取当前文件路径
    fs.stat(filepath,(err,stats)=>{//判断是不是个文件
     if(err){
         res.statusCode = 404
          res.setHeader('Content-Type','text/plain');//响应的头 这里是文本
          res.end(`${filepath} is not a directory or file`);
          return
      }
      if(stats.isFile()){
         res.statusCode = 200;
          res.setHeader('Content-Type','text/plain');//响应的头 这里是文本
         fs.createReadStream(filepath).pipe(res)//把这个文件读出来通过流的形式返回给客户端 可以一点点返回
         如果用别的方式也可以但是是得全部读完才能返回
      }else if(stats.isDirectory()){//如果是文件夹
          res.statusCode = 200;
          res.setHeader('Content-Type','text/plain');//响应的头 这里是文本
         res.end(files.join(','));
      }
    })
});
server.listen(port,hostname,()=>{ //监听这个端口号 自定义回调函数返回的内容
    console.log(`Serve running at http://${hostname}:${port}`);
})
//以上调试每次都需要重启 如果不想重启可以安装
npm i supervisor -g 
安装完成后通过 supervisor 文件名来启动文件 然后在这个文件中做的更改可以实时监听到了
使用 async/await把异步操作抽取出来同时让读取的路径变为可点击的操作并压缩 有五个文件
  • http.js 主文件
const http = require('http');
const path = require('path');
const router = require('./router.js')

const hostname = '10.200.10.219'; //主机名 一般本地开发用本地
const port = 3000 //端口号
//createServer方法:返回一个新建的http.server的实例 http.cerateServer([requestListener])
//requestListener是一个函数,会被自动添加到'request'事件中
//request事件:每次接受一个请求时触发。需要注意的是 每个连接可能有多个请求
const server = http.createServer((req, res) => { //创建一个http的server
    //当想通过调试查看req res的值时 不要启动 直接打断点 然后启动断点 会看到控制台给出一个地址 在地址栏输入 就可以看到res req的值了
    //req就是把客户端发过来的请求封装到这个对象中
    //res服务器生成的内容 或 设置 都在这个对象中
    const filepath = path.join(process.cwd(), req.url) //获取当前文件路径
    router(req,res,filepath)
});
server.listen(port, hostname, () => { //监听这个端口号 自定义回调函数返回的内容
    console.log(`Serve running at http://${hostname}:${port}`);
})
//以上调试每次都需要重启 如果不想重启可以安装
  • router.js 抽离出来的异步操作并压缩文件
const fs = require('fs');
const path = require('path')
const config = require('./config/defaultConfig.js')
const mime = require('./mime.js')//获取文件后缀然后根据后缀读取mime文件中的匹配的键 提取对应的值
const compress = require('./compress.js')//压缩文件的js
const promisify = require('util').promisify; //把stat readdir转化为类似同步的
const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);
const Handlebars = require('handlebars');
const tplpath = path.join(__dirname,'./template/dir.tpl.html')//_dirname指向文件绝对路径
const source = fs.readFileSync(tplpath);//因为下面的内容要想工作这个必须得到 所以此处用同步
const template = Handlebars.compile(source.toString())

module.exports = async function (req, res, filepath) {
    try {
        const stats = await stat(filepath)//从这里把异步转为同步了
        if (stats.isFile()) {
            const contentType = mime(filepath)
            res.statusCode = 200;
            res.setHeader('Content-Type', contentType); //响应的头 这里是文本
            let rs = fs.createReadStream(filepath);
            if(filepath.match(config.compress)){//压缩文件的逻辑
                rs = compress(rs,req,res)
            }
            rs.pipe(res); //把这个文件读出来通过流的形式返回给客户端 可以一点点返回
            //如果用别的方式也可以但是是得全部读完才能返回 用户体验不好
        } else if (stats.isDirectory()) { //如果是文件夹
            const files = await readdir(filepath)//异步必须使用await调用
            res.statusCode = 200;
            res.setHeader('Content-Type', 'text/html'); //响应的头 这里是文本
           // res.end(files.join(',')); //读出所在文件夹中的文件名如果想看单一文件的内容可以再网址后跟具体的文件名
           //使用这种方式读取出来文件名 但是并不可以点击进入相应的文件 如果想要查看相应的文件还得手工输入 这样很不友好 
           //接下来使用handlebars模版引擎来渲染全部是a标签可以点击进入相应的文件
           //1.首先npm install handlebars 安装
           //2.引用handlebars
           const dir = path.relative(process.cwd(),filepath);//方法用于获取从第一个参数进入第二个参数的相对路径
           const data = {
              title:path.basename(filepath),//basename获得路径中的最后一段 也就是文件名
              dir:dir?`/${dir}`:'', //如果不是根路径就前面加/如果是就''
              files:files.map(file=>{
                  return {
                      file,
                      icon:mime(file)
                  }
              })
           };
           res.end(template(data));
        }
    } catch (ex) {
        console.log(ex)
        res.statusCode = 404
        res.setHeader('Content-Type', 'text/plain'); //响应的头 这里是文本
        res.end(`${filepath} is not a directory or file`);
    }
}
  • 压缩文件js
const {createGzip,createDeflate} = require('zlib');

module.exports = (rs,req,res)=>{//rs代表你要压缩什么 从req中获取浏览器支持的压缩类型
//,respones告诉浏览器要用什么解压
  const acceptEncoding = req.headers['accept-encoding']; //读取浏览器支持的压缩方式
  if(!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)){
      //match 函数返回指定数值在数组中的位置此处是边界意思是只能匹配这两个类型
     return rs;
  }else if(acceptEncoding.match(/\bgzip\b/)){
      res.setHeader('Content-Encoding','gzip');//如果读取到了浏览器支持gzip那么就返回gzip方式的压缩文件
     return rs.pipe(createGzip());//能帮我们处理压缩好的流返回给我们
  }else if(acceptEncoding.match(/\bdeflate\b/)){
    res.setHeader('Content-Encoding','deflate');//如果读取到了浏览器支持deflate那么就返回deflate方式的压缩文件
   return rs.pipe(createGzip());//能帮我们处理压缩好的流返回给我们
}
};
  • 缓存文件
const {cache} = require('./config/defaultConfig.js');

function refreshRes(stats, res){
  const {maxAge,expire,cacheControl,lastModified,etag} = cache;//cache maxAge:600,//十分钟
  //expires:true,
  //cacheControl:true,
  //lastModified:true,
  //etag:true
  //以下都是设置的东西 expire是过期时间 lastModified etag都是校验过期的标志
  if(expire){
      res.setHeader('Expires',(new Date(Date.now()+maxAge*1000)).toUTCString())
  }
  if(cacheControl){
      res.setHeader('Cache-Control',`public,max-age=${maxAge}`);
  }
  if(lastModified){
      res.setHeader('Last-Modified',stats.mtime.toUTCString());
  }
  if(etag){
      res.setHeader('ETag',`${stats.size}-${stats.mtime}`);
  }
}
module.exports = function isFresh(stats,req,res){
    refreshRes(stats,res);
    const lastModified = req.headers['if-modified-since'];
    const etag = req.headers['if-none-match'];
    if(!lastModified && !etag){ //如果没有不能使用缓存
        return false;
    }
    if(lastModified && lastModified!==res.getHeader('Last-Modified')){//如果不一样说明缓存改变 也不能使用
        return false;
    }
    if(etag && etag!==res.getHeader('ETag')){//同上
        return false;
    }
    return true
}
  • 公共使用对象
module.exports = {
    root:process.cwd(),
    hostname:'127.0.0.1',
    port:9527,
    compress:/\.(html|js|css|md)/,
}
  • 类型展示文件
const path = require('path')
const mimeTypes = {
    'css':'text/css',
    'gif':'image/gif',
    'html':'text/html',
    'ico':'image/x-icon',
    'jpeg':'image/jpeg',
    'jpg':'image/jpeg',
    'js':'text/javascript',
    'json':'application/json',
    'pdf':'application/pdf',
    'png':'image/png',
    'svg':'image/svg+xml',
    'swf':'application/x-shockwave-flash',
    'tiff':'image/tiff',
    'txt':'text/plain',
    'wav':'audio/x-wav',
    'wma':'audio/x-ms-wma',
    'wmv':'audio/x-ms-wmv',
    'xml':'text/xml',
}
module.exports = (filepath)=>{
    let ext = path.extname(filepath).split('.').pop().toLowerCase();
    //获取拓展名并从.开始分割然后获得最后部分最后转为小写
    if(!ext){ //如果没有扩展名就直接用传递过来的名称
        ext = filepath
    }
    return mimeTypes[ext] || mimeTypes['txt']; //如果列表中没有就用默认属性txt
}
  • range设置请求范围文件
module.exports= (totalSize,req,res) =>{
    const range = req.headers['range'];
    if(!range){
        return {code:200}
    }
    const sizes = range.match(/bytes=(\d*)-(\d*)/);//可以得到一个数组第一个表示匹配到的内容
    //第二个表示第一个分组第三个表示第二个分组
    const end = sizes[2] || totalSize - 1;//那第二个分组 如果拿不到 就取结尾
    const start = sizes[1] || totalSize - end;//拿第一个分组 如果拿不到
    if(start>end || start<0 || end>totalSize){
        return { code : 200}
    }
    res.setHeader('Accept-Ranges','bytes');
    res.setHeader('Content-Range',`bytes ${start}-${end}/${totalSize}`);
    res.setHeader('Content-Length',end-start);
    return {
        code:206,
        start:parseInt(start),
        end:parseInt(end)
    }
}
  • 模版文件
<!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>{{title}}</title>
    <style>
       body{
           margin: 30px;
       }
       a{
           display: block;
           font-size: 30px;
       }
    </style>
</head>
<body>
    {{#each files}}
    <a href="{{../dir}}/{{file}}">【{{icon}}】{{file}}</a>
    {{/each}}
</body>
</html>
range(请求范围 例如字节 1字节-200字节)
  1. 请求时 在request Header中 range:bytes=[start]-[end]
  2. 在响应中加上 Accept-Ranges:bytes
  3. 在返回respones中Content-Range:bytes start-end/total //这句话说明了返回开始字节 结束字节 总量多少\
  4. 使用range+curl可以创造出快捷范围请求
  5. 可以看下http中range的细节说明
缓存
  1. 浏 览器发送请求的时候看本地有没有缓存,如果有就使用缓存 如果没有就发送请求请求资源然后浏览器获取资源之后缓存到本地 当本地再次发送请求的时候 就请求本地缓存了
  2. 当使用本地缓存的时候要检查下缓存是否失效 怎么知道缓存是否失效?靠两个重要的HTTP的头验证Cache-Control中的max-age时间限定、验证Expires到期日如果没有到期就使用缓存 如果过期了那就可以通过服务器告诉客户端修改时间 如果没变 还是使用缓存 如果变了就把新的修改时间和内容发送给客户端 还可以通过hash码来判断 如下图所示
    在这里插入图片描述
  • 缓存header
    1. Expires,Cache-Control 前者绝对时间 后者相对时间 现在一般用后者
    2. If-Modified-Since/Last-Modified 前者是服务器告诉客户端Last-Modified 客户端再次验证的时候的字段 如果更改了服务端会返回新的 客户端就再用新的
    3. if-None-Match/Etag 功能与上面一样
cli(把包做成一个cli工具)
  • 版本号解释 x.y.z
    1. 升级z位是修复bug
    2. 升级y位是新增功能并保证兼容之前版本
    3. 升级x位是大版本升级可能不保证兼容是全新的API了
    4. 1.2.*z位自动升 用最新的
    5. 2.x只有大版本不自动升级 y位 x位都自动升
    6. 只有个*代表任意版本
    发npm包流程
    1. 首先注册npm的账号
    2. 打开命令窗口 npm login 输入账号密码
    3. 如果代码已经提交到github上面了 那么就输入npm publish 然后就发完了 可以去npm上面查下
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值