HTTP框架修炼之道 | 青训营笔记

286 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第13篇笔记. HyperText Transfer Protocal

  1. PATCH和PUT方法的区别? PATCH对应部分更新,PUT的语义是完整更新,具有幂等性。
package main

import (
   "context"

   //"code.byted.org/middleware/hertz/pkg/app"
   //"code.byted.org/middleware/hertz/pkg/app/server"
)


func main(){
   h:=server.New()

   h.POST("/sis",func(c context.Context,ctx *app.RequestContext){
      ctx.Data(200,"text/plain;charset=utf-8",[]byte("OK"))
   })
   h.Spin()
}

业务层->服务治理层/中间件层->路由层->协议编解码层->传输层

Http1

  • 对头阻塞,基于tcp,后序分片需要等待前面分片的结束,否则会一直等待
  • 传输效率低 不支持多路复用
  • 明文传输,不安全

http2

  • 多路复用
  • 头部压缩
  • 二进制协议 解析高效

QUIC

  • 基于UDP
  • 解决队头阻塞
  • 优化加密算法,减少握手次数
  • 快速启动

分层设计

应用层

  • 提供合理api
    • 可理解性
    • 简单性
    • 避免冗余性
    • 兼容性
    • 可测性
    • 可见性

中间件层

  • 需求
    • 配合Handler实现完整的请求处理生命周期
    • 预处理和后处理逻辑
    • 注册多中间件
    • 对上层模块和用户模块易用
  • 洋葱模型
    • 日志记录
    • 性能统计
    • 安全控制
    • 事务处理
    • 异常处理 日志->Metrics->Biz Handler->Metrix->日志
func main(){
 h:=server.New()
 
 
 h.Use(func(c context.Context,ctx *app.RequestContext){
    //print request
    logs.Infof("Received RawRequest:%s",ctx.Request.RawRequest())
    
    //next handler
    ctx.Next(c)
    //print response
    logs.Infof("Second RawResponse:%s",ctx.Response.RawResponse())
 })
 
 h.POST("/login",func(c context.Context,ctx *app.RequestContext){
    //some biz logic
    ctx.JSON(200,"OK")
 })
 h.POST("/logout",func(c context.Context,ctx *app.RequestContext){
    //some biz logic
    ctx.JSON(200,"OK")
 })
 h.spin()
}

关于中间件的设计:

//1.需要实现预处理和后处理,类似于函数的调用
func Middleware(some param){
   //pre-handle

   //调用下一个处理函数。
   Next()

   //after-handle

}
//路由上可以注册多middleware,也可以满足请求级别有效。只需将midlleware设计为业务和handler相同即可


//2. 如果用户不主动的调用下一个处理函数
func (ctx *RequestContext) Next(){
   ctx.index++
   for ctx.index<int8(len(ctx.handlers)){
      ctx.handlers[ctx.index]()
      //任何场景下index递增
      ctx.index++
   }
}

//3. 如果调用过程出现了异常,想要停止,如何处理
func (ctx *RequestContext) Abort(){
   //index设为最大值,直接跳出循环
   ctx.index=IndexMax
}

那么,有没有其他方式来实现中间件?

路由设计

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

协议层设计

  1. 不要把context写进struct,而是通过参数处理
  2. 在连接上读写数据

传输层设计

BIO

BLOCK

package main

go func(){
   for {
      conn,_:=listener.Accept()
      go func(){
         conn.Read(request)
         ...
         conn.Write(response)
      }
   }
}

go/net是BIO

NIO

NON-BLOCKING
注册监听器。

go func(){
   for{
      readableConn,_ :=Monitor(conns)
      for conn:=range radableConn{
         go func(){
            conn.Read(request)
            ...
            conn.Write(response)
         }
      }
   }
}

netpoll是NIO

网络库优化

go net的优化思路

  • 存下全部Header
  • 减少系统调用
  • 复用内存
  • 多次读 go net with bufio 绑定大小为4k的缓冲区
    netpoll的优化思路
  • 存下全部Header
  • 拷贝完整的Body