koa-send源码分析

步阐
• 阅读 4824

通常我们在做静态文件服务的时候,首选CDN。当文件内容需要经常变动时,则可以采用nginx代理的方式。node本身也可以搭建静态服务,用koa static可以很容易实现这个功能。

koa static是一个koa中间件,内部是对koa send的封装。koa static本身只做了一层简单的逻辑,所以这篇文章主要分析一下koa send的实现方式。

如果让我们自己实现这个功能,也很简单,逻辑就是根据用户请求路径,找到文件,然后做一个文件流的响应。

koa send的实现也大概是这个思路,另外多了一些基于http协议的处理,当然,阅读koa send的源码,还是有一些意外的收获。

koa send源码很简洁,唯一暴露了一个工具函数sendsend函数大致结构如下:

async function send(ctx, path, opts = {}) {
    // 1、参数path校验
    // 2、配置opts初始化
    // 3、accept encoding处理
    // 4、404、500处理
    // 5、缓存头处理
    // 6、流响应
}
  1. 第1步和第2步是koa send本身的一些配置的处理,代码比较啰嗦,我们可以忽略。
  2. 第3步,主要是根据请求头Accept-Encoding进行处理,如果用户浏览器支持br或者gzip的压缩方式,koa send会判断是否存在br或者gz格式文件,如果存在会优先响应br或者gz文件。
  3. 第4步,会做文件查找,如果不存在文件,或者文件查找异常,则进行404或者500的响应。具体代码如下:

    try {
        stats = await fs.stat(path)
        // 默认文件index
        if (stats.isDirectory()) {
            if (format && index) {
                path += '/' + index
                stats = await fs.stat(path)
            } else {
                return
            }
        }
    } catch (err) {
        const notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR']
        if (notfound.includes(err.code)) {
            throw createError(404, err)
        }
        err.status = 500
        throw err
    }
  4. 第5步,会设置协商缓存Last-Modified和强制缓存Cache-Control,代码很简单不解释,不过这里面有一个之前没遇到的知识点,koa send设置的Cache-Control会有类似max-age=10000,immutable的值,immutable表示永不改变,浏览器永不需要请求资源,这个感觉可以配合带hash或者版本号的资源使用。
  5. 第6步最有意思,代码很简单,只有一行:

    ctx.body = fs.createReadStream(path)

    熟悉node文件流的同学都会知道,fs.createReadStream创建了一个自path的文件流,调用.pipe即可通过管道做文件流传输。比如使用node的http模块实现的http服务,如果要对res做文件流响应,那么只要调用strame.pipe(res)即可。

    但是这里有意思的是,koa send把stream赋值给ctx.body就完了,没有看到任何流处理。另外平时我们做json格式的响应,也是类似的调用ctx.body={a:1}。也就是说,ctx.body可以做不同类型的赋值操作。要解释这个操作方式,就要回到koa本身对koa.body的实现。

    先贴上koa body的实现代码:

    set body(val) {
        
        // no content
        if (null == val) {
            if (!statuses.empty[this.status]) this.status = 204;
            this.remove('Content-Type');
            this.remove('Content-Length');
            this.remove('Transfer-Encoding');
            return;
        }
        
        // string
        if ('string' == typeof val) {
            if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
            this.length = Buffer.byteLength(val);
            return;
        }
        
        // buffer
        if (Buffer.isBuffer(val)) {
            if (setType) this.type = 'bin';
            this.length = val.length;
            return;
        }
        
        // stream
        if ('function' == typeof val.pipe) {
            // 结束关闭流 stream.destroy stream.close
            // https://www.npmjs.com/package/on-finished
            // https://www.npmjs.com/package/destroy
            onFinish(this.res, destroy.bind(null, val));
            ensureErrorHandler(val, err => this.ctx.onerror(err));
            // overwriting
            if (null != original && original != val) this.remove('Content-Length');
            if (setType) this.type = 'bin';
            return;
        }
        
        // json
        this.remove('Content-Length');
        this.type = 'json';
    }

    从截取的源码可以看到,原来是ctx.body做了一层setter拦截,当我们赋值的时候,koa对于不同格式的body,统一在setter中做了分类处理。从源码上看,body依次支持了空值、字符串、字节、文件流和json几种响应类型。对于是否流的判断,就是通过是否对象存在pipe函数确定的。

    这个实现方式挺有意思,以后对于一些不同类型的复制操作,可以把类型判断和一些逻辑放到setter中来做,代码会清晰很多。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
皕杰报表(关于日期时间时分秒显示不出来)
在使用皕杰报表设计器时,数据据里面是日期型,但当你web预览时候,发现有日期时间类型的数据时分秒显示不出来,只有年月日能显示出来,时分秒显示为0:00:00。1.可以使用tochar解决,数据集用selecttochar(flowdate,"yyyyMMddHH:mm:ss")fromtablename2.也可以把数据库日期类型date改成timestamp
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql中时间比较的实现
MySql中时间比较的实现unix\_timestamp()unix\_timestamp函数可以接受一个参数,也可以不使用参数。它的返回值是一个无符号的整数。不使用参数,它返回自1970年1月1日0时0分0秒到现在所经过的秒数,如果使用参数,参数的类型为时间类型或者时间类型的字符串表示,则是从1970010100:00:0
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
步阐
步阐
Lv1
热爱是不会降温的。
文章
4
粉丝
0
获赞
0