webctx.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package websubsvc
  2. import (
  3. "errors"
  4. "fmt"
  5. "git.swzry.com/zry/zry-go-program-framework/core"
  6. "github.com/gin-gonic/gin"
  7. "net"
  8. "net/http"
  9. "net/http/httputil"
  10. "os"
  11. "strconv"
  12. "strings"
  13. "time"
  14. )
  15. type WebSubServiceContext struct {
  16. subSvcCtx *core.SubServiceContext
  17. core.IModuleLogger
  18. s *WebSubService
  19. }
  20. // GetSubSvcCtx get the SubServiceContext of this sub service
  21. func (c *WebSubServiceContext) GetSubSvcCtx() *core.SubServiceContext {
  22. return c.subSvcCtx
  23. }
  24. // GetRootRouter get the root RouterGroup of gin engine
  25. func (c *WebSubServiceContext) GetRootRouter() *gin.RouterGroup {
  26. return &c.s.ginEngine.RouterGroup
  27. }
  28. // DefaultMiddleware will call EnableLogger and EnableRecovery
  29. func (c *WebSubServiceContext) DefaultMiddleware(logPrefix, recoveryLogPrefix string) {
  30. c.EnableLogger(logPrefix)
  31. c.EnableRecovery(recoveryLogPrefix)
  32. }
  33. // EnableTLS if you want to use TLS, please call this from IWebSubServiceLogic.GetHttpServer
  34. func (c *WebSubServiceContext) EnableTLS(certFile, keyFile string) {
  35. c.s.enbaleTLS = true
  36. c.s.certFile = certFile
  37. c.s.keyFile = keyFile
  38. }
  39. // SetShutdownTimeout set the graceful shutdown timeout when stopping server.
  40. // will force stop server when timeout exceeded.
  41. func (c *WebSubServiceContext) SetShutdownTimeout(d time.Duration) {
  42. c.s.shutdownTimeout = d
  43. }
  44. // MakeHttpServer create a http.Server for IWebSubServiceLogic.GetHttpServer
  45. // bindAddr: the address to bind
  46. // config: the config for http server. refer to http.Server. for basic usage, you can give it nil.
  47. func (c *WebSubServiceContext) MakeHttpServer(bindAddr string, config *HttpServerConfig) *http.Server {
  48. cfg := config
  49. if cfg == nil {
  50. cfg = &HttpServerConfig{}
  51. }
  52. h := &http.Server{
  53. Addr: bindAddr,
  54. Handler: c.s.ginEngine,
  55. DisableGeneralOptionsHandler: cfg.DisableGeneralOptionsHandler,
  56. TLSConfig: cfg.TLSConfig,
  57. ReadTimeout: cfg.ReadTimeout,
  58. ReadHeaderTimeout: cfg.WriteTimeout,
  59. WriteTimeout: cfg.IdleTimeout,
  60. IdleTimeout: cfg.IdleTimeout,
  61. MaxHeaderBytes: cfg.MaxHeaderBytes,
  62. TLSNextProto: cfg.TLSNextProto,
  63. ConnState: cfg.ConnState,
  64. ErrorLog: cfg.ErrorLog,
  65. BaseContext: cfg.BaseContext,
  66. ConnContext: cfg.ConnContext,
  67. }
  68. return h
  69. }
  70. // EnableLogger enable gin logger middleware
  71. func (c *WebSubServiceContext) EnableLogger(logPrefix string) {
  72. l := c.GetSubLog(logPrefix)
  73. gf := func(c *gin.Context) {
  74. start := time.Now()
  75. path := c.Request.URL.Path
  76. raw := c.Request.URL.RawQuery
  77. c.Next()
  78. end := time.Now()
  79. latency := end.Sub(start).String()
  80. clientIP := c.ClientIP()
  81. method := c.Request.Method
  82. statusCode := c.Writer.Status()
  83. comment := c.Errors.ByType(gin.ErrorTypePrivate).String()
  84. if raw != "" {
  85. path = path + "?" + raw
  86. }
  87. mv := map[string]string{
  88. "time": ginTimeFormat(end),
  89. "status": strconv.Itoa(statusCode),
  90. "latency": latency,
  91. "clientIP": clientIP,
  92. "method": method,
  93. "path": path,
  94. "comment": comment,
  95. }
  96. l.VerboseC(mv)
  97. }
  98. c.s.ginEngine.Use(gf)
  99. }
  100. // EnableRecovery enable gin recovery middleware
  101. func (c *WebSubServiceContext) EnableRecovery(recoveryLogPrefix string) {
  102. l := c.GetSubLog(recoveryLogPrefix)
  103. fn := func(c *gin.Context) {
  104. defer func() {
  105. if err := recover(); err != nil {
  106. // Check for a broken connection, as it is not really a
  107. // condition that warrants a panic stack trace.
  108. var brokenPipe bool
  109. if ne, ok := err.(*net.OpError); ok {
  110. var se *os.SyscallError
  111. if errors.As(ne, &se) {
  112. seStr := strings.ToLower(se.Error())
  113. if strings.Contains(seStr, "broken pipe") ||
  114. strings.Contains(seStr, "connection reset by peer") {
  115. brokenPipe = true
  116. }
  117. }
  118. }
  119. stack := ginStack(3)
  120. httpRequest, _ := httputil.DumpRequest(c.Request, false)
  121. headers := strings.Split(string(httpRequest), "\r\n")
  122. for idx, header := range headers {
  123. current := strings.Split(header, ":")
  124. if current[0] == "Authorization" {
  125. headers[idx] = current[0] + ": *"
  126. }
  127. }
  128. headersToStr := strings.Join(headers, "\r\n")
  129. if brokenPipe {
  130. l.WarnC(map[string]string{
  131. "err_type": "connection_broken",
  132. "raw_err": fmt.Sprintf("%v", err),
  133. "headers": headersToStr,
  134. })
  135. } else if gin.IsDebugging() {
  136. l.WarnC(map[string]string{
  137. "err_type": "panic_recovery",
  138. "raw_err": fmt.Sprintf("%v", err),
  139. "headers": headersToStr,
  140. "stack_trace": string(stack),
  141. })
  142. } else {
  143. l.WarnC(map[string]string{
  144. "err_type": "panic_recovery",
  145. "raw_err": fmt.Sprintf("%v", err),
  146. "headers": headersToStr,
  147. "stack_trace": string(stack),
  148. })
  149. }
  150. if brokenPipe {
  151. // If the connection is dead, we can't write a status to it.
  152. c.Error(err.(error)) //nolint: errcheck
  153. c.Abort()
  154. } else {
  155. c.AbortWithStatus(http.StatusInternalServerError)
  156. }
  157. }
  158. }()
  159. c.Next()
  160. }
  161. c.s.ginEngine.Use(fn)
  162. }