HTTP 框架设计 | 青训营

HTTP框架设计

分层设计

HTTP框架采用分层设计,各层之间通过 API 通信,这样提高了代码的复用性和拓展性。

  • 应用层——提供给用户使用的接口,需要保证可理解性和简单性
  • 中间件层——对请求进行预处理和后处理
  • 路由层——为 url 请求匹配相应的Handler处理函数
  • 协议层——抽象出合适的接口,在连接上读取数据
  • 传输层——为网络I/O提供了一个可移植的接口,包括TCP/IP、UDP、域名解析和Unix域套接字

中间件

常用于日志统计,性能统计,事务处理,安全控制,异常处理

洋葱模型

2023-08-02-21-03-47-image.png

Handler被多层中间件“包装”起来,Handler每次处理请求时都会对请求有一个预处理后处理

处理函数的调用

Next方法保证每次调用时都会增加index,使得每次调用正确的Handler

func (ctx *RequestContext) Next() {
    ctx.index++
    for ctx.index < int8(len(ctx.handlers)) {
        ctx.handlers[ctx.index]()
        ctx.index++
    }

}

若是调用链中出现异常需要中止,则直接Abort方法直接将index设置为最大值

func (ctx *RequestContext) Abort() {
    ctx.index = IndexMax
}

路由

路由匹配一般指动态路由的匹配,即一条路由规则可以匹配某一类型而非某一条固定的路由,例如 /detail:id,可以匹配 /detail/1/detail/2 等符合一定规则的 url

实现

前端的实现一般基于正则匹配,大多依赖依赖 path-to-regexp 这个库解析路由,例如react-routervue-router而后端的koa/router也使用了这个库

gin框架则使用了前缀树这种方式来实现路由匹配

前缀树Trie

Trie用来储存可被分割为单个节点,并且可以共享前缀的数据,即树中一个节点的所有子孙都有相同的前缀

  • 每个节点只储存了一个单元
  • 节点可以被多个数据共享
  • 标记代表数据结尾的节点
  • 根据一定的路径依次通过节点可以得到特定的数据

以下是简单的Trie结构

type node struct {
    path     string           // 路由路径
    part     string           // 路由中由'/'分隔的部分
    children map[string]*node // 子节点
    isWild   bool             // 是否是通配符节点
}

type router struct {
    root  map[string]*node       // 路由树根节点
    route map[string]HandlerFunc // 路由处理handler
}

对于不同的方法,由rooter对象的 root[method]确定是否支持某个方法。

绑定Handler和路由时,迭代地创建子节点(若不存在),最后再绑定路由和Handler处理函数

// addRoute 绑定路由到handler
func (r *router) addRoute(method, path string, handler HandlerFunc) {
    parts := parsePath(path)
    if _, ok := r.root[method]; !ok {
        r.root[method] = &node{children: make(map[string]*node)}
    }

    root := r.root[method]
    key := method + "-" + path
    // 将parts插入到路由树
    for _, part := range parts {
        if root.children[part] == nil {
            root.children[part] = &node{
                part:     part,
                children: make(map[string]*node),
                isWild:   part[0] == ':' || part[0] == '*'}
        }
        root = root.children[part]
    }
    root.path = path
    // 绑定路由和handler
    r.route[key] = handler
}

获取路由时,先判断路由是否支持该方法,然后层层查找对应节点

// getRoute 获取路由树节点以及路由变量
func (r *router) getRoute(method, path string) (node *node, params map[string]string) {
    params = map[string]string{}
    searchParts := parsePath(path)

    // get method trie
    var ok bool
    if node, ok = r.root[method]; !ok {
        return nil, nil
    }

    // 在该方法的路由树上查找该路径
    for i, part := range searchParts {
        var temp string
        // 查找child是否等于part
        for _, child := range node.children {
            if child.part == part || child.isWild {
                // 添加参数
                if child.part[0] == '*' {
                    params[child.part[1:]] = strings.Join(searchParts[i:], "/")
                }
                if child.part[0] == ':' {
                    params[child.part[1:]] = part
                }
                temp = child.part
            }

        }
        // 遇到通配符*,直接返回
        if temp[0] == '*' {
            return node.children[temp], params
        }
        node = node.children[temp]

    }

    return

}

代码来自web框架 gaga

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY5a9Jj2' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片