zoukankan      html  css  js  c++  java
  • 使用node搭建静态资源服务器(1)

    node实现静态文件服务器的优势

    提到这就不得不说node的优势啦,node适合的场景是高并发I/O密集型,高并发就是同一时刻访问的人特别多,大家都跟服务器说我要什么什么,在这一时刻服务器要处理的请求也特别多。那I/O密集指什么呢,先来说下什么是I/O操作,一般对文件,数据库,网络操作都算为I/0操作,与之对应的概念是CPU操作,包括解密,加密,压缩,解压等。正常一个请求过来服务器就会开启一个线程,如果同时有多个请求就会开启多个线程,但操作系统能支持的并发数量是有限的,而node作为服务器端的JavaScript,也保持了单线程的特点。单线程的好处是什么?不会阻塞后续请求的响应。(额,似乎不够具体)举个例子,假设你开了家饭店,雇了3个服务员和3个大厨,客人来了服务员接待点餐,点餐后等待大厨做好饭菜,传给服务员,服务员在拿给客人,然后服务员才能接待下一位客人,假如同时来了4位客人,那必须有一位客人等待,以上是多线程并发的情景。单线程是什么样呢,我就需要一位服务员(作为老板的你好开心,少发了很多工资),客人来了服务员接待点餐,点餐后把客人点的菜通知给大厨,大厨就开始做菜,与此同时服务员可以接待下一位客人,而不必等待大厨把菜做好,等大厨把菜做好后就会通知服务员,服务员再把菜端给客人,对应的就是非阻塞请求,就是大厨在做菜过程中(类比操作系统执行I/O操作)服务员不会失去对其它客人请求的响应(类比为服务器可以处理后续的请求,不会占用过多的CPU资源)。从2个不同模式的点菜过程就可以看出node单线程带来的好处啦。

    一个静态资源服务器要实现的功能

    和需求的对话

    假设你作为一个node新手,来想象下你和需求方的的一段对话:
    需求方:当我访问一个url时,如果是文件,返回对应文件内容,如果是目录,返回文件列表,并且文件列表是可点击的。
    你:这个没问题。(内心活动:基本功能必须满足)
    需求方:还有最好有缓存功能,不希望每次都请求服务器。
    你:(停顿半秒)好的。(内心活动:缓存一般网站都支持,是要设置头标签啥的,具体方法要搜索啦,可以搞得定)

    简单的流程

    根据需求,你开始写代码之前,简单梳理了下程序的流程:
    (1)在本地根据指定端口启动一个http server,等待着来自客户端的请求
    (2)当请求抵达时,根据请求的url,以设置的静态文件目录为base,映射得到文件位置
    (3)检查文件是否存在
    (4)如果文件不存在,返回404状态码,发送not found页面到客户端
    (5)如果文件存在:
    * 打开文件待读取(要考虑缓存的实现)
    * 设置response header
    * 发送文件到客户端
    (6)等待来自客户端的下一个请求

    代码实现

    基本功能的代码

    //config.js
    module.exports = {
        root:process.cwd(),
        hostname:"127.0.0.1",
        port:"9876"
    }
    
    
    //dir.tpl
     <!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>
       </head>
       <body>
          {{#each files}} 
            <a href="{{../dir}}/{{this}}">{{this}}</a>
          {{/each}}
       </body>
       </html>
    
    //app.js
    const http = require('http');
    const config = require('./config/config');
    const path = require('path')
    const fs =require('fs');
    const Handlebars = require('handlebars') 
    const tplPath = path.join(__dirname,'./template/dir.tpl');
    const source =fs.readFileSync(tplPath);
    const template = Handlebars.compile(source.toString())
    
    const server = http.createServer((req,res)=>{
        //将客户端当前文件夹路径与请求的url拼接起来
        const filePath = path.join( config.root,req.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/html');
                fs.createReadStream(filePath).pipe(res);
            }else if(stats.isDirectory()){
                fs.readdir(filePath,(err,files)=>{
                    res.statusCode =200;
                    res.setHeader('Content-Type','text/html');
                    const dir =path.relative(config.root,filePath);
                    const data ={
                        title:path.basename(filePath),
                        dir:dir ? `/${dir}`:'',
                        files
                    }
                    res.end(template(data))
                })
            }
        })
    });
    
    server.listen(config.port,config.hostname,()=>{
        console.log(`Servser started at ${config.hostname}:${config.port}`)
    });
    

    到这里,就实现了静态资源服务器的基本功能,这里文件列表的渲染采用了handlebars模板引擎。我们用到handlebars模板引擎的流程是:
    (1)拿到模板文件
    (2使用handbars的compile()方法将模板文件编译成template
    (3)将数据传给template,返回html。
    这里只用到这三个方法,其它api可以去handlebars模板引擎官网看看。
    我们可以看到app.js里还是有很多回调,我们可以用util模块promisify把回调的方式改成异步调用。
    (1)引用对应的包

    const promisify = require('util').promisify;
    

    (2)然后将回调的方法变成异步的,举例:

    const stat = promisify(fs.stat);
    const readdir = promisify(fs.readdir);
    

    (3)用await关键字调用,有一个要注意的是await必须写在async修饰的function里。

    const stats = await  stat(filePath)
     const files = await readdir(filePath);
    

    缓存功能的代码

    先梳理下客户端发起请求,浏览器响应的流程(是否使用缓存)。
    对应流程图
    常见几个缓存头:

    • Experies:返回绝对时间校验,涉及到时区等,很少用。
    • Cache-Control:返回相对时间,相对上次请求的秒数,用Max-age表示
    • If-Modified-Since/Last-Modified:服务器时间校验
    • If-None-Match/Etag:服务器哈希校验
    //cache.js
    const { cache } = require('../config/config')
    
    function refreshRes(stats, res) {
        const { maxAge, expires, cacheControl, lastModified, etag } = cache;
        if (expires) {
            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.toUTCString()}`);
        }
    }
    
    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;
        }
        console.log("refreshRes执行完毕");
        return true;
    }
    
    //判断是否过期
    
     if(isFresh(stats,req,res)){
        res.statusCode = 304;
        res.end();
        return;
       }
    

    总结

    这个项目主要涉及到常用模块http,fs,其它的就是一些辅助的工具模块。这个静态服务器只有最基本的功能,还有一些功能,例如文件类型的判断,压缩等都没有实现,下篇文章继续更新。
    涉及api概览

  • 相关阅读:
    git commit --amend
    Interleaving String leetcode
    Longest Common Substring
    Distinct Subsequences Leetcode
    Longest Common Subsequence
    Palindrome Partitioning II Leetcode
    百度面试时遇到这样一个问题:给定数组a[];计算除最后一个元素之外其他元素的和,下面的代码有什么问题吗
    sizeof与strlen
    网络是怎么连接的(2)?
    网络是怎么连接的(1)?
  • 原文地址:https://www.cnblogs.com/JessicaIsEvolving/p/9455056.html
Copyright © 2011-2022 走看看