说在前面
- 关于deno, 已经有很多有深度, 有意义的文章了, 我就不强加凑字数了. 我就直接从重点循序渐进分享下吧.
- 代码地址:
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和装饰器吧.
- Router
我看了一些陆游的处理, 如koa-router的实现, 使用的path-to-regexp这个库. 使用正则方式优点就是提供的特性非常全, 很方便. 但是我认为用正则并不是解决路由的最好方法. 所以我与其他服务一样, 使用的Trie树(前缀树). 这位大佬写的关于Trie树就挺好的. 我总结了一张图, 基本能代表我处理路由的实现. - 装饰器
其中实体保存中间件和路由等信息. 这里利用的是装饰器是编译时执行的特性, 这样不会有很大的运行时的性能损失, 而且写法上更直观
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主要集中在北上深杭, 当然推其他的也可以, 欢迎来撩~