中间件-静态资源实现

April 18, 2020 · View on GitHub

前言

前一篇文章 6.1 中间件式框架简单实现 讲述了如何实现一个 DenoKoa.js。本篇就主要讲解,基于上述的中间件框架扩展的静态资源中间件的实现。

实现过程

  • 配置过程
    • 配置静态资源绝对目录地址 baseDir
    • 配置静态资源特定前缀 prefix
  • 运行过程
    • 判断是否为 GET 类型的请求
    • 过滤处理路径,防止访问设定资源路径外的资源
    • 读取静态文件,根据是否存在状态做对应的响应处理

具体实现

源码地址

https://github.com/chenshenhai/deno_note/blob/master/demo/web_static/mod.ts

源码讲解

./demo/web_static/mod.ts

import { Context } from './../web/mod.ts';

const readFileSync = Deno.readFileSync;
const lstatSync = Deno.lstatSync

const decoder = new TextDecoder();

interface ServeOptions {
  prefix: string;
}

/**
 * 读取静态文件
 * @param {string} fullFilePath 文件的绝对路径
 * @return {string}
 */
function renderFile( fullFilePath: string) {
  const bytes = readFileSync(fullFilePath);
  const content = decoder.decode(bytes);
  return content;
}

/**
 * 过滤静态资源请求路径
 * @param {string} path HTTP请求路径
 * @param {object} opts HTTP请求路径需要操作的参数
 *  opts.prefix {string} 清除掉HTTP路径的前缀
 * @param {string} 返回过滤掉的HTTP路径
 */
function pathFilter(path: string, opts?: ServeOptions) {
  const prefix = (opts && opts.prefix) ? opts.prefix : "";
  let result = "";
  result = path.replace(prefix, "");
  // 过滤掉路径里 ".." "//" 字符串,防止越级访问文件夹
  result = result.replace(/[\.]{2,}/ig, "").replace(/[\/]{2,}/ig, "/");
  return result;
}

/**
 * 静态服务中间件生成函数
 * @param baseDir 静态资源目录文件路径
 * @param options 静态资源配置参数
 *  opts.prefix {string} 清除掉HTTP路径的前缀
 * @param {function} 中间件函数
 */
function serve(baseDir: string, options?: ServeOptions): Function {
  return async function(ctx: Context, next: Function) {
    await next();
    const {req, res} = ctx;
    const gen = await req.getGeneral() || {};
    const pathname = gen.pathname;
    if ( options && typeof options.prefix === "string" && pathname.indexOf(options.prefix) === 0 ) {
      const path = pathFilter(pathname, options);
      const fullPath = `${baseDir}${path}`;
      let result = `${path} is not found!`;
      try {
        const stat = lstatSync(fullPath);
        // 判断是否为文件路径
        if (stat.isFile === true) {
          result = renderFile(fullPath);
          res.setStatus(200);
        }
      } catch (err) {
        // throw new Error(err);
      }
      res.setBody(`${result}`);
      res.setFinish();
    }
  };
}

export const staticServe = serve;

使用例子

使用源码

https://github.com/chenshenhai/deno_note/blob/master/demo/web_static/example.ts

./demo/web_static/example.ts

import { Application } from "./../web/mod.ts";
import { staticServe } from "./mod.ts";

const cwd = Deno.cwd

const app = new Application();
const opts: Deno.ListenOptions = {
  hostname: "127.0.0.1",
  port: 3001
}
const baseDir = [cwd(), "public"].join("/");

const staticMiddleware = staticServe(baseDir, {prefix: `/static-file`});

app.use(staticMiddleware);

app.listen(opts, function(){
  console.log(`listening on ${opts.hostname}:${opts.port}`);
});

执行结果

deno run --allow-read --allow-net example.ts 

结果显示

web_static

测试

单元测试