再谈HTTP协议
HTTP协议是什么
HTTP:超文本传输协议(Hypertext Transfer Protocol)
超文本超在哪里?就是可以传输比文本更多的信息,包括视频图片超链接等。
为什么需要协议?在网线上传输的其实都是01数据流,如果没有相应的规则,无法理解信息。首先需要一个明确的边界,也就是要知道开始和结束。然后需要描述信息的类型。
协议里面有什么
put是信息的更新,patch也是,但是put是幂等的,patch不是。
一个demo:
请求流程
不足与展望
HTTP框架的设计与实现
分层设计
网络架构的分层设计:
HTTP协议的分层设计:
- 应用层:和用户直接打交道的一层,会提供一个丰富应用的api,以及对请求进行一个抽象。
- 中间件层:可以对请求进行一个预处理或者后处理,比如说打点计时,捕获panic等
- 路由层:会有一个原生的路由实现,提供选址和注册等
- 协议层:只要实现一个抽象的接口,就可以完成协议的扩展了。
- 网络层:灵活替换网络库
应用层设计
提供合理的API:要具有可理解性,简单性,冗余性,兼容性,可测性,可见性
中间件设计
中间件需求:
- 配合 Handler 实现一个完整的请求处理生命周期
- 拥有预处理逻辑与后处理逻辑
- 可以注册多中间件
- 对上层模块用户逻辑模块易用
洋葱模型的执行流程如下:
- 请求进入:初始请求进入中间件链的最外层。
- 向内执行:请求从外向内依次经过每一层中间件,每一层中间件在请求进入时可以执行前置逻辑。
- 内部处理:当请求到达最内层中间件(通常是应用逻辑处理器或控制器)时,执行实际的业务逻辑。
- 向外返回:请求从内向外经过每一层中间件,每一层中间件在请求返回时可以执行后置逻辑。
- 响应返回:请求经过所有中间件后,响应返回给客户端。
在洋葱模型中,每个中间件都有机会在请求到达时执行前置逻辑,并在请求返回时执行后置逻辑。这使得中间件可以实现各种功能,如身份验证、日志记录、缓存等。
以下是一个简化的示意代码,展示中间件的洋葱模型:
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)
}
在上面的代码中,两个中间件 Middleware1 和 Middleware2 形成了洋葱模型,它们分别在请求前后添加了日志输出。当请求进入洋葱模型时,首先经过 Middleware1 的前置逻辑,然后进入 Middleware2 的前置逻辑,然后到达路由处理函数,在返回时按相反的顺序经过中间件的后置逻辑。这种流程就构成了中间件的洋葱模型。
路由设计
框架路由实际上就是为 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 方法
- 多处理函数:方便添加中间件
参数路由:
协议层设计
网络层设计
性能修炼之道
针对网络库的优化
针对协议的优化
热点资源池化
课后作业
为什么 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)
}
路由还有没有其他的实现方式?
除了基于前缀路由树外,还有其他路由实现方式,如正则表达式路由、动态路由等。正则表达式路由使用正则模式匹配请求路径,而动态路由可以通过占位符来匹配不同的路径。不同的实现方式适用于不同的场景,选择合适的实现方式取决于项目的需求和性能要求。