webctx.go 5.7 KB

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