All files / src/utils middleware.ts

42.59% Statements 23/54
100% Branches 7/7
75% Functions 3/4
42.59% Lines 23/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120    1x                                                       1x         1x 13x 13x                                                 1x                                                                               1x 5x 5x 5x 10x 7x 5x 5x 7x 7x 10x 3x 3x 10x 5x 5x 5x  
import type { IncomingMessage, ServerResponse } from 'node:http';
import path from 'node:path';
import send from 'send';
import type { Esmx } from '../core';
 
/**
 * 中间件函数类型定义
 *
 * @description
 * 中间件是一个函数,用于处理 HTTP 请求。它接收请求对象、响应对象和下一个中间件函数作为参数。
 * 中间件可以执行以下操作:
 * - 修改请求和响应对象
 * - 结束请求-响应循环
 * - 调用下一个中间件
 *
 * @example
 * ```ts
 * // 创建一个简单的日志中间件
 * const loggerMiddleware: Middleware = (req, res, next) => {
 *   console.log(`${req.method} ${req.url}`);
 *   next();
 * };
 * ```
 */
export type Middleware = (
    req: IncomingMessage,
    res: ServerResponse,
    next: Function
) => void;
 
const reFinal = /\.final\.[a-zA-Z0-9]+$/;
/**
 * 判断文件路径是否是一个符合 esmx 规范的不可变文件
 * @param path 文件路径
 */
export function isImmutableFile(filename: string) {
    return reFinal.test(filename);
}
 
/**
 * 创建 Esmx 应用的中间件
 *
 * @param esmx - Esmx 实例
 * @returns 返回一个处理静态资源的中间件
 *
 * @description
 * 该函数创建一个中间件,用于处理模块的静态资源请求。它会:
 * - 根据模块配置创建对应的静态资源中间件
 * - 处理资源的缓存控制
 * - 支持不可变文件的长期缓存
 *
 * @example
 * ```ts
 * import { Esmx, createMiddleware } from '@esmx/core';
 *
 * const esmx = new Esmx();
 * const middleware = createMiddleware(esmx);
 *
 * // 在 HTTP 服务器中使用
 * server.use(middleware);
 * ```
 */
export function createMiddleware(esmx: Esmx): Middleware {
    const middlewares = Object.values(esmx.moduleConfig.links).map(
        (item): Middleware => {
            const base = `/${item.name}/`;
            const baseUrl = new URL(`file:`);
            const root = item.client;
            return (req, res, next) => {
                const url = req.url ?? '/';
                const { pathname } = new URL(req.url ?? '/', baseUrl);
 
                if (!url.startsWith(base) || req.method !== 'GET') {
                    next();
                    return;
                }
 
                send(req, pathname.substring(base.length - 1), {
                    root
                })
                    .on('headers', () => {
                        if (isImmutableFile(pathname)) {
                            res.setHeader(
                                'cache-control',
                                'public, max-age=31536000, immutable'
                            );
                        } else {
                            res.setHeader('cache-control', 'no-cache');
                        }
                    })
                    .pipe(res);
            };
        }
    );
    return mergeMiddlewares(middlewares);
}
 
/**
 * 将多个中间件,合并成一个中间件执行
 * @param middlewares 中间件列表
 * @returns
 */
export function mergeMiddlewares(middlewares: Middleware[]): Middleware {
    return (req, res, next) => {
        let index = 0;
        function dispatch() {
            if (index < middlewares.length) {
                middlewares[index](req, res, () => {
                    index++;
                    dispatch();
                });
                return;
            } else {
                next();
            }
        }
        dispatch();
    };
}