HTTP框架修炼之道 | 青训营

76 阅读5分钟

再谈HTTP协议

HTTP协议是什么

HTTP:超文本传输协议(Hypertext Transfer Protocol)

超文本超在哪里?就是可以传输比文本更多的信息,包括视频图片超链接等。

为什么需要协议?在网线上传输的其实都是01数据流,如果没有相应的规则,无法理解信息。首先需要一个明确的边界,也就是要知道开始和结束。然后需要描述信息的类型。 image.png

协议里面有什么

put是信息的更新,patch也是,但是put是幂等的,patch不是。 image.png 一个demo: image.png image.png

请求流程

image.png

不足与展望

image.png

HTTP框架的设计与实现

分层设计

网络架构的分层设计: image.png HTTP协议的分层设计:

  • 应用层:和用户直接打交道的一层,会提供一个丰富应用的api,以及对请求进行一个抽象。
  • 中间件层:可以对请求进行一个预处理或者后处理,比如说打点计时,捕获panic等
  • 路由层:会有一个原生的路由实现,提供选址和注册等
  • 协议层:只要实现一个抽象的接口,就可以完成协议的扩展了。
  • 网络层:灵活替换网络库 image.png

应用层设计

提供合理的API:要具有可理解性,简单性,冗余性,兼容性,可测性,可见性

中间件设计

中间件需求:

  • 配合 Handler 实现一个完整的请求处理生命周期
  • 拥有预处理逻辑与后处理逻辑
  • 可以注册多中间件
  • 对上层模块用户逻辑模块易用

洋葱模型的执行流程如下:

  1. 请求进入:初始请求进入中间件链的最外层。
  2. 向内执行:请求从外向内依次经过每一层中间件,每一层中间件在请求进入时可以执行前置逻辑。
  3. 内部处理:当请求到达最内层中间件(通常是应用逻辑处理器或控制器)时,执行实际的业务逻辑。
  4. 向外返回:请求从内向外经过每一层中间件,每一层中间件在请求返回时可以执行后置逻辑。
  5. 响应返回:请求经过所有中间件后,响应返回给客户端。

在洋葱模型中,每个中间件都有机会在请求到达时执行前置逻辑,并在请求返回时执行后置逻辑。这使得中间件可以实现各种功能,如身份验证、日志记录、缓存等。

以下是一个简化的示意代码,展示中间件的洋葱模型:

goCopy code
func Middleware1(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 在请求前执行的逻辑
        fmt.Println("Middleware1: Before request")
        next.ServeHTTP(w, r)
        // 在请求后执行的逻辑
        fmt.Println("Middleware1: After request")
    })
}

func Middleware2(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("Middleware2: Before request")
        next.ServeHTTP(w, r)
        fmt.Println("Middleware2: After request")
    })
}

func main() {
    router := mux.NewRouter()

    // 使用中间件
    router.Use(Middleware1)
    router.Use(Middleware2)

    // 注册路由处理函数
    router.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("Handling request")
        w.Write([]byte("Hello, world!"))
    })

    http.ListenAndServe(":8080", router)
}

在上面的代码中,两个中间件 Middleware1Middleware2 形成了洋葱模型,它们分别在请求前后添加了日志输出。当请求进入洋葱模型时,首先经过 Middleware1 的前置逻辑,然后进入 Middleware2 的前置逻辑,然后到达路由处理函数,在返回时按相反的顺序经过中间件的后置逻辑。这种流程就构成了中间件的洋葱模型。 image.png

image.png

image.png

image.png

image.png

路由设计

框架路由实际上就是为 URL 匹配对应的处理函数(Handlers

  • 静态路由: /a/b/c、/a/b/d
  • 参数路由: /a/:id/c (/a/b/c, /a/d/c)、/*all
  • 路由修复: /a/b <-> /a/b/
  • 冲突路由以及优先级: /a/b、/:id/c
  • 匹配 HTTP 方法
  • 多处理函数:方便添加中间件

image.png 参数路由:

image.png

image.png

image.png

协议层设计

image.png

网络层设计

image.png

image.png

性能修炼之道

针对网络库的优化

image.png

image.png

针对协议的优化

image.png

image.png

热点资源池化

image.png

课后作业

为什么 HTTP 框架做要分层设计? 分层设计有哪些优势与劣势

分层设计在HTTP框架中是为了实现更好的代码组织、可维护性、可扩展性以及降低耦合度。通过将不同功能模块分离到不同的层级,可以使代码更加清晰,并且更容易进行单元测试和重用。

现有开源社区 HTTP 框架有哪些优势与不足。

不同的开源HTTP框架有不同的优势和不足,以下是一些常见的开源HTTP框架:

优势:

  • Gin:轻量级、性能优异,适合快速构建API。
  • Echo:简单易用,提供中间件和路由功能,适合构建小型应用。
  • Beego:完整的MVC框架,提供了丰富的功能如ORM、缓存等。
  • Fiber:基于Fasthttp的框架,具有高性能和低内存消耗。

不足:

  • Gin:某些高级特性相对较少,适用于简单的API场景。
  • Echo:相对轻量,缺少一些高级功能。
  • Beego:框架比较重,适用于大型应用,但可能过于复杂。
  • Fiber:相对新的框架,社区生态可能相对较小。

中间件还有没有其他实现方式? 可以用伪代码说明

函数闭包:

func Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 这里可以执行前置处理逻辑
        // ...
        next.ServeHTTP(w, r)
        // 这里可以执行后置处理逻辑
        // ...
    })
}

func main() {
    // 创建路由
    router := mux.NewRouter()
    
    // 使用中间件
    router.Use(Middleware)
    
    // 添加路由处理函数
    router.HandleFunc("/user", GetUserHandler)
    
    // ...
}

完成基于前缀路由树的注册与查找功能?可以用伪代码说明

基于前缀路由树的注册与查找功能的伪代码

goCopy code
type Route struct {
    Path     string
    Handler  http.Handler
}

type PrefixRouter struct {
    tree *prefixTree
}

func NewPrefixRouter() *PrefixRouter {
    return &PrefixRouter{
        tree: newPrefixTree(),
    }
}

func (pr *PrefixRouter) Handle(path string, handler http.Handler) {
    pr.tree.insert(path, handler)
}

func (pr *PrefixRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    handler := pr.tree.lookup(r.URL.Path)
    if handler != nil {
        handler.ServeHTTP(w, r)
    } else {
        http.NotFound(w, r)
    }
}

func main() {
    router := NewPrefixRouter()
    router.Handle("/user", http.HandlerFunc(UserHandler))
    router.Handle("/product", http.HandlerFunc(ProductHandler))
    
    http.ListenAndServe(":8080", router)
}

路由还有没有其他的实现方式?

除了基于前缀路由树外,还有其他路由实现方式,如正则表达式路由、动态路由等。正则表达式路由使用正则模式匹配请求路径,而动态路由可以通过占位符来匹配不同的路径。不同的实现方式适用于不同的场景,选择合适的实现方式取决于项目的需求和性能要求。