webctx.go 5.4 KB

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