|
@@ -0,0 +1,174 @@
|
|
|
+package websubsvc
|
|
|
+
|
|
|
+import (
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "git.swzry.com/zry/zry-go-program-framework/core"
|
|
|
+ "github.com/gin-gonic/gin"
|
|
|
+ "net"
|
|
|
+ "net/http"
|
|
|
+ "net/http/httputil"
|
|
|
+ "os"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+type WebSubServiceContext struct {
|
|
|
+ subSvcCtx *core.SubServiceContext
|
|
|
+ core.IModuleLogger
|
|
|
+ s *WebSubService
|
|
|
+}
|
|
|
+
|
|
|
+// GetSubSvcCtx get the SubServiceContext of this sub service
|
|
|
+func (c *WebSubServiceContext) GetSubSvcCtx() *core.SubServiceContext {
|
|
|
+ return c.subSvcCtx
|
|
|
+}
|
|
|
+
|
|
|
+// GetRootRouter get the root RouterGroup of gin engine
|
|
|
+func (c *WebSubServiceContext) GetRootRouter() *gin.RouterGroup {
|
|
|
+ return &c.s.ginEngine.RouterGroup
|
|
|
+}
|
|
|
+
|
|
|
+// DefaultMiddleware will call EnableLogger and EnableRecovery
|
|
|
+func (c *WebSubServiceContext) DefaultMiddleware(logPrefix, recoveryLogPrefix string) {
|
|
|
+ c.EnableLogger(logPrefix)
|
|
|
+ c.EnableRecovery(recoveryLogPrefix)
|
|
|
+}
|
|
|
+
|
|
|
+// EnableTLS if you want to use TLS, please call this from IWebSubServiceLogic.GetHttpServer
|
|
|
+func (c *WebSubServiceContext) EnableTLS(certFile, keyFile string) {
|
|
|
+ c.s.enbaleTLS = true
|
|
|
+ c.s.certFile = certFile
|
|
|
+ c.s.keyFile = keyFile
|
|
|
+}
|
|
|
+
|
|
|
+// SetShutdownTimeout set the graceful shutdown timeout when stopping server.
|
|
|
+// will force stop server when timeout exceeded.
|
|
|
+func (c *WebSubServiceContext) SetShutdownTimeout(d time.Duration) {
|
|
|
+ c.s.shutdownTimeout = d
|
|
|
+}
|
|
|
+
|
|
|
+// MakeHttpServer create a http.Server for IWebSubServiceLogic.GetHttpServer
|
|
|
+// bindAddr: the address to bind
|
|
|
+// config: the config for http server. refer to http.Server. for basic usage, you can give it nil.
|
|
|
+func (c *WebSubServiceContext) MakeHttpServer(bindAddr string, config *HttpServerConfig) *http.Server {
|
|
|
+ cfg := config
|
|
|
+ if cfg == nil {
|
|
|
+ cfg = &HttpServerConfig{}
|
|
|
+ }
|
|
|
+ h := &http.Server{
|
|
|
+ Addr: bindAddr,
|
|
|
+ Handler: c.s.ginEngine,
|
|
|
+ DisableGeneralOptionsHandler: cfg.DisableGeneralOptionsHandler,
|
|
|
+ TLSConfig: cfg.TLSConfig,
|
|
|
+ ReadTimeout: cfg.ReadTimeout,
|
|
|
+ ReadHeaderTimeout: cfg.WriteTimeout,
|
|
|
+ WriteTimeout: cfg.IdleTimeout,
|
|
|
+ IdleTimeout: cfg.IdleTimeout,
|
|
|
+ MaxHeaderBytes: cfg.MaxHeaderBytes,
|
|
|
+ TLSNextProto: cfg.TLSNextProto,
|
|
|
+ ConnState: cfg.ConnState,
|
|
|
+ ErrorLog: cfg.ErrorLog,
|
|
|
+ BaseContext: cfg.BaseContext,
|
|
|
+ ConnContext: cfg.ConnContext,
|
|
|
+ }
|
|
|
+ return h
|
|
|
+}
|
|
|
+
|
|
|
+// EnableLogger enable gin logger middleware
|
|
|
+func (c *WebSubServiceContext) EnableLogger(logPrefix string) {
|
|
|
+ l := c.GetSubLog(logPrefix)
|
|
|
+ gf := func(c *gin.Context) {
|
|
|
+ start := time.Now()
|
|
|
+ path := c.Request.URL.Path
|
|
|
+ raw := c.Request.URL.RawQuery
|
|
|
+ c.Next()
|
|
|
+ end := time.Now()
|
|
|
+ latency := end.Sub(start).String()
|
|
|
+ clientIP := c.ClientIP()
|
|
|
+ method := c.Request.Method
|
|
|
+ statusCode := c.Writer.Status()
|
|
|
+ comment := c.Errors.ByType(gin.ErrorTypePrivate).String()
|
|
|
+
|
|
|
+ if raw != "" {
|
|
|
+ path = path + "?" + raw
|
|
|
+ }
|
|
|
+
|
|
|
+ mv := map[string]string{
|
|
|
+ "time": ginTimeFormat(end),
|
|
|
+ "status": strconv.Itoa(statusCode),
|
|
|
+ "latency": latency,
|
|
|
+ "clientIP": clientIP,
|
|
|
+ "method": method,
|
|
|
+ "path": path,
|
|
|
+ "comment": comment,
|
|
|
+ }
|
|
|
+ l.VerboseC(mv)
|
|
|
+ }
|
|
|
+ c.s.ginEngine.Use(gf)
|
|
|
+}
|
|
|
+
|
|
|
+// EnableRecovery enable gin recovery middleware
|
|
|
+func (c *WebSubServiceContext) EnableRecovery(recoveryLogPrefix string) {
|
|
|
+ l := c.GetSubLog(recoveryLogPrefix)
|
|
|
+ fn := func(c *gin.Context) {
|
|
|
+ defer func() {
|
|
|
+ if err := recover(); err != nil {
|
|
|
+ // Check for a broken connection, as it is not really a
|
|
|
+ // condition that warrants a panic stack trace.
|
|
|
+ var brokenPipe bool
|
|
|
+ if ne, ok := err.(*net.OpError); ok {
|
|
|
+ var se *os.SyscallError
|
|
|
+ if errors.As(ne, &se) {
|
|
|
+ seStr := strings.ToLower(se.Error())
|
|
|
+ if strings.Contains(seStr, "broken pipe") ||
|
|
|
+ strings.Contains(seStr, "connection reset by peer") {
|
|
|
+ brokenPipe = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ stack := ginStack(3)
|
|
|
+ httpRequest, _ := httputil.DumpRequest(c.Request, false)
|
|
|
+ headers := strings.Split(string(httpRequest), "\r\n")
|
|
|
+ for idx, header := range headers {
|
|
|
+ current := strings.Split(header, ":")
|
|
|
+ if current[0] == "Authorization" {
|
|
|
+ headers[idx] = current[0] + ": *"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ headersToStr := strings.Join(headers, "\r\n")
|
|
|
+ if brokenPipe {
|
|
|
+ l.WarnC(map[string]string{
|
|
|
+ "err_type": "connection_broken",
|
|
|
+ "raw_err": fmt.Sprintf("%v", err),
|
|
|
+ "headers": headersToStr,
|
|
|
+ })
|
|
|
+ } else if gin.IsDebugging() {
|
|
|
+ l.WarnC(map[string]string{
|
|
|
+ "err_type": "panic_recovery",
|
|
|
+ "raw_err": fmt.Sprintf("%v", err),
|
|
|
+ "headers": headersToStr,
|
|
|
+ "stack_trace": string(stack),
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ l.WarnC(map[string]string{
|
|
|
+ "err_type": "panic_recovery",
|
|
|
+ "raw_err": fmt.Sprintf("%v", err),
|
|
|
+ "headers": headersToStr,
|
|
|
+ "stack_trace": string(stack),
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if brokenPipe {
|
|
|
+ // If the connection is dead, we can't write a status to it.
|
|
|
+ c.Error(err.(error)) //nolint: errcheck
|
|
|
+ c.Abort()
|
|
|
+ } else {
|
|
|
+ c.AbortWithStatus(http.StatusInternalServerError)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ c.Next()
|
|
|
+ }
|
|
|
+ c.s.ginEngine.Use(fn)
|
|
|
+}
|