HTTP框架设计
分层设计
HTTP框架采用分层设计,各层之间通过 API 通信,这样提高了代码的复用性和拓展性。
- 应用层——提供给用户使用的接口,需要保证可理解性和简单性
- 中间件层——对请求进行预处理和后处理
- 路由层——为 url 请求匹配相应的
Handler
处理函数 - 协议层——抽象出合适的接口,在连接上读取数据
- 传输层——为网络I/O提供了一个可移植的接口,包括TCP/IP、UDP、域名解析和Unix域套接字
中间件
常用于日志统计,性能统计,事务处理,安全控制,异常处理
洋葱模型
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-router
、vue-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
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END