min — 基于Deno的支持装饰器模式的http(s)服务框架

时间:2020-9-13 作者:admin

说在前面

  1. 关于deno, 已经有很多有深度, 有意义的文章了, 我就不强加凑字数了. 我就直接从重点循序渐进分享下吧.
  2. 代码地址: github地址. 觉得可以的可以帮忙点个star~ XD.

一. 介绍

因为我之前写koa多一些, 所以min在设计的时候就确定以中间件为基本功能点. 整个Application和中间件机制都是与koa一样的. 增加的功能点主要是Router的重写处理和装饰器模式的支持. 装饰器大部分设计与nest相似, 但是实现都有很大差异. (关于装饰器会在文章后面分享下). 话不多说, 言归正传.

二. 使用方法

觉得在分享实现之前还是列一下用法会更好理解一些~(关于装饰器会在文章后面分享下).
关于使用方法, 可以查看代码中提供的examples, 相关代码可以在其中找到.

// deps.ts
export {
  Application,
  Req,
  Res,
  MinConfig,
  HandlerFunc,
  MiddlewareFunc
} from 'https://raw.githubusercontent.com/lengfangbing/min/master/mod.ts';

// min.config.ts
import {
  Req,
  Res,
  MinConfig,
  MiddlewareFunc
} from './deps.ts';
const postData = (req: Req, res: Res) => {
  res.body = {
    data: req.body,
    name: 'post-data',
    isPost: true
  }
}
const redirect = (req: Req, res: Res) => {
  res.redirect('/');
}
const render = async (req: Req, res: Res) => {
  await res.render('template/index.html');
}
const routerMiddleware = (): MiddlewareFunc => {
  return async (req, res, next) => {
    const time1 = performance.now();
    await next();
    const time2 = performance.now();
    res.headers.set('router-response-time', (time2 - time1).toString());
  }
}
export default {
  server: {
    port: 7000,
    hostname: "127.0.0.1",
    // certFile: 'cert file path',
    // keyFile: 'key file path',
    // secure: false,
    addr: "http://127.0.0.1:8000"//(比port+hostname高优先级)
  },
  routes: [
    {
      url: '/',
      method: "GET",
      func: render
    },
    {
      url: '/postData',
      method: 'post',
      func: postData,
      // 路由中间件
      middleware: [routerMiddleware()]
    },
    {
      url: '/redirect',
      method: 'GET',
      func: redirect
    }
  ],
  cors: {
    origin: '*',
    allowMethods: ["get", "post", "options", "put", "delete"],
    allowHeaders: ["content-type"],
    maxAge: 0,
    allowCredentials: false
  },
  // set assets request render files directory, default is working directory
  assets: {
    path: "assets",
    onerror: (e: Error) => {
      console.log(e);
    }
  }
} as MinConfig;

// index.ts
import {
  Application,
  MiddlewareFunc
}  from './deps.ts';
const app = new Application();
import config from './min.config.ts';
function requestLogger(): MiddlewareFunc{
  return async function(req, res, next){
    const time1 = performance.now();
    await next();
    const time2 = performance.now();
    res.headers.set('request-response-time', (time2 - time1).toString());
  }
}
app
  .use(async (request, response, next) => {
    // 全局中间件
    console.log(request.url);
    await next();
    console.log(response.body);
  })
  .use(requestLogger());
await app.start(config);
// or
// await app.start(await import('./min.config.ts'));
/* 在终端运行: deno run --allow-net --allow-read --allow-hrtime index.ts */

上面的是使用min.config.ts配置文件的使用方法. 不使用的话在用法上和koa一样使用. 这其中支持min.config.ts配置文件是为了更方便进行扩展而提供的功能, 其实使用上很像vue-router那种使用方式, 更加友好一些.

三. 实现

其实基本流程就是一层层处理deno给我们提供的http服务的ServerRequest对象. 我主要分享下Router和装饰器吧.

  1. Router
    我看了一些陆游的处理, 如koa-router的实现, 使用的path-to-regexp这个库. 使用正则方式优点就是提供的特性非常全, 很方便. 但是我认为用正则并不是解决路由的最好方法. 所以我与其他服务一样, 使用的Trie树(前缀树). 这位大佬写的关于Trie树就挺好的. 我总结了一张图, 基本能代表我处理路由的实现.

    min -- 基于Deno的支持装饰器模式的http(s)服务框架

  2. 装饰器

    min -- 基于Deno的支持装饰器模式的http(s)服务框架

    其中实体保存中间件和路由等信息. 这里利用的是装饰器是编译时执行的特性, 这样不会有很大的运行时的性能损失, 而且写法上更直观

import {
  App,
  ApplyMiddleware,
  assets,
  cors,
  Get,
  Middleware,
  Prefix,
  Req,
  Res,
  Start,
  StartApplication
} from 'https://raw.githubusercontent.com/lengfangbing/min/master/mod.ts';

// 只能有一个StartApplication入口, 来实例化实体中的App
// extends App, 是给TestClass扩展一个启动服务的方法, 因为好像ts对装饰器没有非常好的支持?
@StartApplication
// 将这个类的请求方法增加公共前缀uri
@Prefix('/prefix')
export class TestClass extends App {

  // 全局应用中间件
  @ApplyMiddleware([assets('/static'), cors()])
  // 将下面的方法加到中间件中
  @Middleware
  async middle1(req: Req, res: Res, next: Function) {
    console.log('middle1');
    await next();
    console.log('middle1 end');
  }

  @Middleware
  async middle2(req: Req, res: Res, next: Function) {
    console.log('middle2');
    await next();
    console.log('middle2 end');
  }

  // url为/test的get方法
  @Get('/test')
  async testHandle(req: Req, res: Res) {
    // fetch url `${hostname}:${port}/test/?name=myName&age=20`
    res.cookies.append('name', '123');
    res.cookies.append('age', '22');
    res.body = req.query;
  }

  // url为/login, 并且有路由中间件的post方法
  @Post('/login', [async (req: Req, res: Res, next: Function) => {
    console.log('I will execute in post request with url /login');
    await next();
  }])
  login(req: Req, res: Res) {
    console.log(req.body);
    res.body = {
      body: req.body.value
    };
  }

  // 启动端口等设置
  @Start({port: 8000, hostname: '127.0.0.1'})
  async start() {
    await this.startServer();
  }

}

// 实例化并调用
const initial = new TestClass();

await initial.start();

更多关于装饰器的demo可以查看demo

写在最后

如果喜欢的话还劳烦点个star~
另外, 字节跳动现在依然在招人, 可以找我内推~
我所在的是产品研发和工程架构部门电商前端, base主要集中在北上深杭, 当然推其他的也可以, 欢迎来撩~

有什么问题欢迎提问、讨论~

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。