Fen - 基于deno的简单Typescript Web框架

2,844 阅读3分钟

deno 从诞生,到现在有自己的标准库,以及一些第三方库,让我对deno在web的应用有了一定的兴趣,

我知道deno尚未成熟,但是刚好最近心闲下来了,就找点事做做,作为一个前端,我也期待通过这个机会了解更多的一些后台的事情。

如果你能在看到文章之后,给我提出一些建议我将不胜感激~

如果你也想一起开发,我也非常欢迎~

阅读大约需要 4 - 5 分钟

基于deno v0.3.0,Github:https://github.com/fen-land/deno-fen

简介

Fen 是一个使用Typescript基于deno的轻量级web框架,他通过Server主体来实现服务,通过Process以及Tool来赋予Server各种各样的功能。

Fen 仅仅通过上下问context来连接各个环节,虽然这样的模式存在着上下文在修改过程中难以保证稳定的问题,但是如果Process和Tool的开发遵守一些基本原则,还是能基本维持context的稳定的。同时虽然 Fen 通过Process以及Tool来赋能,但是其本身还是进行了一定的封装以及进行了一些操作来让使用更加便捷。

因为其实没有提出很新的想法,所以还是使用了很多之前在java或者node中用过的后台的用法,如果大家觉得有怎样的新想法,也欢迎大家提issue。

怎样开始

首先,如果你不知道deno,快去看看吧: https://deno.land/

你需要安装最新的deno:

curl -fsSL https://deno.land/x/install/install.sh | sh

然后,你需要写你的第一个Fen的文件

// hello.ts
import { Server } from "https://raw.githubusercontent.com/fen-land/deno-fen/master/src/server.ts";

const s = new Server();

s.port = 8882;

s.start();

因为deno还没有相应成熟的包管理(也可能不会有)这里引用文件还暂时是通过github来做一个托管,之后会完善一下,有更好的地址使用方式。

最后你需要运行这个文件

deno -A hello.ts

在浏览器中访问:http://127.0.0.1:8882 你将会看到:

You have success build a server with fen on  0.0.0.0:1882

            Try set controller using setController method,
            Or try our route tool :)

这就是你的第一个 Fen 服务器。

Context

Context是Fen的核心概念,他起到了一个在生命周期里面传递的作用,也是controller进行操作的手段。

结构如下:

{
 // ---- 这些变量都取自http派发都ServerRequest
  url,
  method,
  proto,
  headers,
  conn,
  reader,
  writer,
 // ----     
  request,    
  path, // 不带有参数的路径
  params, // Map<string, string> url里面得到的参数 
  data: new Map<string, any>(),
  body: "",// respond 返回的body
  status: 200,// 状态码
  config: {
    disableRespond: false, // 禁用fen自带的respond流程
    disableBodyEncode: false, // 禁止按照类型来encode body的流程
    disableContentType: false, // 禁止根据config来设置header
    mimeType: "text/plain", // 返回结果的mimeType
    charset: "utf-8" // 返回结果的Type
  },
  reqBody, // 尝试解码后的请求的body
  originBody, // 原本请求的body
  logger // tool 中带有的Logger
};

通过使用context,你可以通过简单的赋值来决定响应:

s.setController(async ctx => {
  ctx.config.mimeType = "application/json";
  ctx.body = {
    now: "you",
    can: ["see"],
    me: 2
  };
});

Process

Process通常运行在controller前,为context赋能,使其拥有特定的功能,Process通过addProcess方法来进行添加。

Session

Session是现有的唯一Process,他为context加入了session这样一个键,这样就赋予了controller了web session的能力。

import {Server} from 'https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/server.ts';
import Session from 'https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/process/session.ts'

const session = new Session();

const s = new Server();

// session 加入的仅仅是session的process
s.addProcess(session.process);

s.port = 1882;

s.setController(
    async (ctx) => {
        // session 是一个 Map<string, any>()
        const {session} = ctx;
        let c = session.get('c') || 1;

        if(ctx.path === '/') {
            session.set('c',  c + 1);
        }

        ctx.body = `It\'s alive for path '/' ${c} times in this browser!`;
    }
);

s.start();

Tool

Fen要实现很多基本的功能类似于静态文件,路由之类的,需要使用Tool,并且在Server中也使用了一些Tool。

Logger

Fen 自己有一个log系统,虽然是很简陋的那种,但是可以帮助开发时候得到更多信息,你可以在Server或者context中找到它。log是按照级别来提供的,你可以在开发中使用logger.debug('debug')这样的方式来产出log,低于设置level的log都不会产出。

    'ALL': 所有log都输出,
    'TRACE',
    'DEBUG',
    'INFO',
    'WARN',
    'ERROR',
    'FATAL',
    'OFF': 禁止所有log

你可以通过 changeLevel来改变level:

logger.changeLevel('ALL');

Static

Static 为 Server提供了静态代理的功能,

import {Server} from 'https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/server.ts';
import {staticProcess} from "https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/tool/static.ts";

const s = new Server();

s.port = 1882;
// it will respond file from the path where deno run
s.setController(staticProcess({root: ''}));

s.start();

static提供了一些额外的option:

{
    root: root path of the file,
    maxAge: (s),
    allowHidden: allow access hidden file,
    index: access if no file name provide 'index.html',
    immutable: immutable in cache-control,
    pathRender: (path) => afterpath, if you want do sth. with path
};

Router

我们为Fen 也提供了路由Tool,他有很灵活的使用方法,你也可以通过使用多个Router来进行开发,最后把他们merge到同一个上,下面展示了大部分可以使用的路由方法的例子。

import { Server } from "https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/server.ts";
import { Router } from "https://raw.githubusercontent.com/fen-land/deno-fen/raw/master/src/tool/router.ts";

const s = new Server();

s.port = 1882;

s.logger.changeLevel('ALL');

let mergeRouter = new Router('merge');

mergeRouter
  .get('/', async (ctx) => ctx.body = `${ctx.router.name} in ${ctx.router.route}`)
  .post('/', async (ctx) => ctx.body = `POST ${ctx.router.name} in ${ctx.router.route}`)
  .get('me', async (ctx) => ctx.body = `${ctx.router.name} in ${ctx.router.route}`);

let router = new Router();

router
  .get('/:id', async (ctx) => {
  ctx.body = `we have ${JSON.stringify(ctx.router.params)} in ${ctx.router.route}`
})
  .get('/:id/:name', async (ctx) => {
  ctx.body = `we have ${JSON.stringify(ctx.router.params)} in ${ctx.router.route}`
})
  .get('/hello/:name', async (ctx) => {
    ctx.body = `hello ${ctx.router.params.name} in ${ctx.router.route}`
  })
  .use({ '/use': {get: async (ctx) => ctx.body = `use in ${ctx.router.route}`}})
  .merge('/merge', mergeRouter);
;

s.setController(router.controller);

s.start();

Router 有以下的方法:

    use(route: IRoute) // 一种区别于下面方法的直接批量添加路由的方式
    // IRoute 结构:
    // {[path]: {[method]: async function controller(cxt)}}
    merge(route: string, router:Router) // 你甚至可以通过路径前缀来合并Router们
    get
    post
    head
    put
    delete
    connect
    options
    trace

接下来

接下来Fen的开发计划是:

  • 补充注释和类型
  • 支持socket
  • 继续开发Process和Tool
  • 添加自动测试
  • ……

(其实也存在我被毕业设计压榨后到答辩前无法完成的情况)XD