gin的timeout middleware实现(续2)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 笔者连续2篇文章,探讨如何开发一个gin的timeout middleware,但是"百密一疏"啊。仍然考虑的不够周全。 笔者的文章 gin的timeout middleware实现(续) 中实现的程序有2个问题 func Timeout(t time.Duration) gin.HandlerFunc { return func(c *gin.Context) { // sync.Pool buffer := buffpool.GetBuff() blw := &SimplebodyWriter{body: buffer, ResponseWriter: c.Writer} c.Writer = blw // wrap the request context with a timeout ctx, cancel := context.WithTimeout(c.Request.Context(), t) c.Request = c.Request.WithContext(ctx) finish := make(chan struct{}) // 子协程 // ****************注意*************** // 创建的子协程没有recover,存在程序崩溃的风险 go func() { c.Next() finish <- struct{}{} }() // ****************注意*************** select { case <-ctx.Done(): // ****************注意*************** // 子协程和父协程存在同时修改Header的风险 // 由于Header是个map,可能诱发 // fatal error: concurrent map read and map write c.Writer.WriteHeader(http.StatusGatewayTimeout) // ****************注意*************** c.Abort() // 超时发生, 通知子协程退出 cancel() // 如果超时的话,buffer无法主动清除,只能等待GC回收 case <-finish: // 结果只会在主协程中被写入 blw.ResponseWriter.Write(buffer.Bytes()) buffpool.PutBuff(buffer) } } } 2. 解决 main.go ...

June 6, 2019 · 4 min

gin的timeout middleware实现(续)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在笔者的上一篇文章中,我们探讨了如何开发一个对业务无侵入的timeout middleware的实现,但是遗留了问题。在超时发生时,后台运行的子协程可能会不断累积,造成协程的泄露,最终引发程序奔溃。 2. 解决 为了解决子协程退出的问题,我们需要在超时发生时,通知子协程,让其也尽快退出。 下面的例子中,gin的处理函数long(c *gin.Context)中有对gRPC服务的调用 我们使用grpc/grpc-go 中提供的 greeter_server来提供gRPC服务 // Package main implements a server for Greeter service. package main import ( "context" "log" "net" "time" "google.golang.org/grpc" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) const ( port = ":50051" ) // server is used to implement helloworld.GreeterServer. type server struct{} // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.Name) time.Sleep(2*time.Second) return &pb.HelloReply{Message: "Hello " + in.Name}, nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } gin相关代码 main.go ...

May 20, 2019 · 3 min

gin的timeout middleware实现

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 说到Golang中应用最广泛的web框架,恐怕非gin-gonic/gin莫属了。在服务中,如果它依赖的后端服务出现异常,我们希望错误能够快速的暴露给调用方,而部署无限期的等待。我们需要一个timeout middleware, 来完成这个目标。 对于gin框架,timeout middleware可参考的资料,比较多见的是参考资料1,但是它的实现,对业务代码造成了入侵,不是很友好,这里给出笔者的实现,供大家参考 2. 实现 直接上代码 main.go package main import ( "bytes" "github.com/gin-gonic/gin" "github.com/vearne/golib/buffpool" "log" "net/http" "time" ) type SimplebodyWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w SimplebodyWriter) Write(b []byte) (int, error) { return w.body.Write(b) } func Timeout(t time.Duration) gin.HandlerFunc { return func(c *gin.Context) { // sync.Pool buffer := buffpool.GetBuff() blw := &SimplebodyWriter{body: buffer, ResponseWriter: c.Writer} c.Writer = blw finish := make(chan struct{}) go func() { // 子协程只会将返回数据写入到内存buff中 c.Next() finish <- struct{}{} }() select { case <-time.After(t): c.Writer.WriteHeader(http.StatusGatewayTimeout) c.Abort() // 1. 主协程超时退出。此时,子协程可能仍在运行 // 如果超时的话,buffer无法主动清除,只能等待GC回收 case <-finish: // 2. 返回结果只会在主协程中被写入 blw.ResponseWriter.Write(buffer.Bytes()) buffpool.PutBuff(buffer) } } } func short(c *gin.Context) { time.Sleep(1 * time.Second) c.JSON(http.StatusOK, gin.H{"hello":"world"}) } func long(c *gin.Context) { time.Sleep(5 * time.Second) c.JSON(http.StatusOK, gin.H{"hello":"world"}) } func main() { // create new gin without any middleware engine := gin.New() // add timeout middleware with 2 second duration engine.Use(Timeout(time.Second * 2)) // create a handler that will last 1 seconds engine.GET("/short", short) // create a route that will last 5 seconds engine.GET("/long", long) // run the server log.Fatal(engine.Run(":8080")) } 简单解释一下: ...

May 16, 2019 · 2 min

gin 统计请求状态信息

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 gin-gonic/gin 是Golang API 开发中最常用到的web框架。我们可以轻松的写一个gin的中间件获取HTTP的状态码, 然后暴露给phrometheus。但是如果我想获取的body体中的错误码呢? 2. 官方的例子 package main import ( "time" "log" "github.com/gin-gonic/gin" ) func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set("example", "12345") // before request c.Next() // after request latency := time.Since(t) log.Println("latency", latency) // access the status we are sending status := c.Writer.Status() log.Println("status_code", status) } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // it would print: "12345" log.Println(example) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080") } 3. 读取body中的错误码 假定我们的服务在请求处理失败的情况下,返回如下结构体 ...

October 16, 2018 · 2 min

gin 优雅退出

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 gin底层使用的是net/http, 所以gin的优雅退出就等于 http.Server的优雅退出, Golang 1.8以后提供了Shutdown函数,可以优雅关闭http.Server func (srv *Server) Shutdown(ctx context.Context) error 优雅退出的过程 1)关闭所有的监听 2)后关闭所有的空闲连接 3)无限期等待活动的连接处理完毕转为空闲,并关闭 如果提供了带有超时的Context,将在服务关闭前返回 Context的超时错误 完整的例子 package main import ( "net/http" "time" "os" "os/signal" "syscall" "fmt" "github.com/gin-gonic/gin" "github.com/pkg/errors" ) func SlowHandler(c *gin.Context) { fmt.Println("[start] SlowHandler") //time.Sleep(30 * time.Second) time.Sleep(30 * time.Second) fmt.Println("[end] SlowHandler") c.JSON(http.StatusOK, gin.H{ "message": "success", }) } // 实现context.Context接口 type ExitContext struct{ Chan chan struct{} DeadLineTime time.Time } func NewExitContext(duration time.Duration) *ExitContext{ cxt := ExitContext{} cxt.DeadLineTime = time.Now().Add(duration) cxt.Chan = make(chan struct{}, 1) return &cxt } func (cxt *ExitContext) Done() <-chan struct{}{ if time.Now().After(cxt.DeadLineTime){ cxt.Chan <- struct{}{} } return cxt.Chan } func (cxt *ExitContext) Err() error{ return errors.New("can't exit before Specified time") } // 无意义的空函数 func(cxt *ExitContext) Value(key interface{}) interface{}{ return nil } func(ctx *ExitContext) Deadline() (deadline time.Time, ok bool){ deadline = ctx.DeadLineTime ok = true return } func main() { r := gin.Default() // 1. r.GET("/slow", SlowHandler) server := &http.Server{ Addr: ":8080", Handler: r, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } go server.ListenAndServe() // 设置优雅退出 gracefulExitWeb(server) } func gracefulExitWeb(server *http.Server) { ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT) sig := <-ch fmt.Println("got a signal", sig) now := time.Now() cxt := NewExitContext(3*time.Second) err := server.Shutdown(cxt) if err != nil{ fmt.Println("err", err) } // 看看实际退出所耗费的时间 fmt.Println("------exited--------", time.Since(now)) } 更简单的写法 ...

September 14, 2018 · 2 min