Browse Source

First alpha version committed.

ZRY 8 months ago
parent
commit
8500c5561f

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="XMakeProjectSettings">
+    <option name="currentArchitecture" value="x86" />
+    <option name="workingDirectory" value="$PROJECT_DIR$" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/zry-go-program-framework.iml" filepath="$PROJECT_DIR$/.idea/zry-go-program-framework.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 9 - 0
.idea/zry-go-program-framework.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="Go" enabled="true" />
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 23 - 21
core/intf.go

@@ -3,33 +3,33 @@ package core
 import "context"
 
 type ILogger interface {
-	Fatal(module string, d ...interface{})
-	FatalF(module string, format string, d ...interface{})
-	FatalC(module string, data map[string]string)
+	BLFatal(module string, d ...interface{})
+	BLFatalF(module string, format string, d ...interface{})
+	BLFatalC(module string, data map[string]string)
 
-	Panic(module string, d ...interface{})
-	PanicF(module string, format string, d ...interface{})
-	PanicC(module string, data map[string]string)
+	BLPanic(module string, d ...interface{})
+	BLPanicF(module string, format string, d ...interface{})
+	BLPanicC(module string, data map[string]string)
 
-	Error(module string, d ...interface{})
-	ErrorF(module string, format string, d ...interface{})
-	ErrorC(module string, data map[string]string)
+	BLError(module string, d ...interface{})
+	BLErrorF(module string, format string, d ...interface{})
+	BLErrorC(module string, data map[string]string)
 
-	Warn(module string, d ...interface{})
-	WarnF(module string, format string, d ...interface{})
-	WarnC(module string, data map[string]string)
+	BLWarn(module string, d ...interface{})
+	BLWarnF(module string, format string, d ...interface{})
+	BLWarnC(module string, data map[string]string)
 
-	Info(module string, d ...interface{})
-	InfoF(module string, format string, d ...interface{})
-	InfoC(module string, data map[string]string)
+	BLInfo(module string, d ...interface{})
+	BLInfoF(module string, format string, d ...interface{})
+	BLInfoC(module string, data map[string]string)
 
-	Verbose(module string, d ...interface{})
-	VerboseF(module string, format string, d ...interface{})
-	VerboseC(module string, data map[string]string)
+	BLVerbose(module string, d ...interface{})
+	BLVerboseF(module string, format string, d ...interface{})
+	BLVerboseC(module string, data map[string]string)
 
-	Debug(module string, d ...interface{})
-	DebugF(module string, format string, d ...interface{})
-	DebugC(module string, data map[string]string)
+	BLDebug(module string, d ...interface{})
+	BLDebugF(module string, format string, d ...interface{})
+	BLDebugC(module string, data map[string]string)
 
 	GetModuleLogger(module string) IModuleLogger
 }
@@ -82,4 +82,6 @@ type IModuleLogLevelLimiter interface {
 type IAppFramework interface {
 	ILogger
 	GetContext() context.Context
+	IsGlobalDebugMode() bool
+	SetGlobalDebugMode(en bool)
 }

+ 21 - 21
core/mod_log.go

@@ -37,121 +37,121 @@ func NewModuleLogger(module string, base ILogger) *ModuleLogger {
 }
 
 func (ml *ModuleLogger) Fatal(d ...interface{}) {
-	ml.baseLogger.Fatal(ml.module, d...)
+	ml.baseLogger.BLFatal(ml.module, d...)
 }
 
 func (ml *ModuleLogger) FatalF(format string, d ...interface{}) {
-	ml.baseLogger.FatalF(ml.module, format, d...)
+	ml.baseLogger.BLFatalF(ml.module, format, d...)
 }
 
 func (ml *ModuleLogger) FatalC(data map[string]string) {
-	ml.baseLogger.FatalC(ml.module, data)
+	ml.baseLogger.BLFatalC(ml.module, data)
 }
 
 func (ml *ModuleLogger) Panic(d ...interface{}) {
-	ml.baseLogger.Panic(ml.module, d...)
+	ml.baseLogger.BLPanic(ml.module, d...)
 }
 
 func (ml *ModuleLogger) PanicF(format string, d ...interface{}) {
-	ml.baseLogger.PanicF(ml.module, format, d...)
+	ml.baseLogger.BLPanicF(ml.module, format, d...)
 }
 
 func (ml *ModuleLogger) PanicC(data map[string]string) {
-	ml.baseLogger.PanicC(ml.module, data)
+	ml.baseLogger.BLPanicC(ml.module, data)
 }
 
 func (ml *ModuleLogger) Error(d ...interface{}) {
-	ml.baseLogger.Error(ml.module, d...)
+	ml.baseLogger.BLError(ml.module, d...)
 }
 
 func (ml *ModuleLogger) ErrorF(format string, d ...interface{}) {
-	ml.baseLogger.ErrorF(ml.module, format, d...)
+	ml.baseLogger.BLErrorF(ml.module, format, d...)
 }
 
 func (ml *ModuleLogger) ErrorC(data map[string]string) {
-	ml.baseLogger.ErrorC(ml.module, data)
+	ml.baseLogger.BLErrorC(ml.module, data)
 }
 
 func (ml *ModuleLogger) Warn(d ...interface{}) {
 	if ml.levelLimit > 3 {
 		return
 	}
-	ml.baseLogger.Warn(ml.module, d...)
+	ml.baseLogger.BLWarn(ml.module, d...)
 }
 
 func (ml *ModuleLogger) WarnF(format string, d ...interface{}) {
 	if ml.levelLimit > 3 {
 		return
 	}
-	ml.baseLogger.WarnF(ml.module, format, d...)
+	ml.baseLogger.BLWarnF(ml.module, format, d...)
 }
 
 func (ml *ModuleLogger) WarnC(data map[string]string) {
 	if ml.levelLimit > 3 {
 		return
 	}
-	ml.baseLogger.WarnC(ml.module, data)
+	ml.baseLogger.BLWarnC(ml.module, data)
 }
 
 func (ml *ModuleLogger) Info(d ...interface{}) {
 	if ml.levelLimit > 2 {
 		return
 	}
-	ml.baseLogger.Info(ml.module, d...)
+	ml.baseLogger.BLInfo(ml.module, d...)
 }
 
 func (ml *ModuleLogger) InfoF(format string, d ...interface{}) {
 	if ml.levelLimit > 2 {
 		return
 	}
-	ml.baseLogger.InfoF(ml.module, format, d...)
+	ml.baseLogger.BLInfoF(ml.module, format, d...)
 }
 
 func (ml *ModuleLogger) InfoC(data map[string]string) {
 	if ml.levelLimit > 2 {
 		return
 	}
-	ml.baseLogger.InfoC(ml.module, data)
+	ml.baseLogger.BLInfoC(ml.module, data)
 }
 
 func (ml *ModuleLogger) Verbose(d ...interface{}) {
 	if ml.levelLimit > 1 {
 		return
 	}
-	ml.baseLogger.Verbose(ml.module, d...)
+	ml.baseLogger.BLVerbose(ml.module, d...)
 }
 
 func (ml *ModuleLogger) VerboseF(format string, d ...interface{}) {
 	if ml.levelLimit > 1 {
 		return
 	}
-	ml.baseLogger.VerboseF(ml.module, format, d...)
+	ml.baseLogger.BLVerboseF(ml.module, format, d...)
 }
 
 func (ml *ModuleLogger) VerboseC(data map[string]string) {
 	if ml.levelLimit > 1 {
 		return
 	}
-	ml.baseLogger.VerboseC(ml.module, data)
+	ml.baseLogger.BLVerboseC(ml.module, data)
 }
 
 func (ml *ModuleLogger) Debug(d ...interface{}) {
 	if ml.levelLimit > 0 {
 		return
 	}
-	ml.baseLogger.Debug(ml.module, d...)
+	ml.baseLogger.BLDebug(ml.module, d...)
 }
 
 func (ml *ModuleLogger) DebugF(format string, d ...interface{}) {
 	if ml.levelLimit > 0 {
 		return
 	}
-	ml.baseLogger.DebugF(ml.module, format, d...)
+	ml.baseLogger.BLDebugF(ml.module, format, d...)
 }
 
 func (ml *ModuleLogger) DebugC(data map[string]string) {
 	if ml.levelLimit > 0 {
 		return
 	}
-	ml.baseLogger.DebugC(ml.module, data)
+	ml.baseLogger.BLDebugC(ml.module, data)
 }

+ 4 - 0
core/subsvc_ctx.go

@@ -20,3 +20,7 @@ func NewSubServiceContext(name string, fw IAppFramework) *SubServiceContext {
 func (c *SubServiceContext) GetParentContext() context.Context {
 	return c.fw.GetContext()
 }
+
+func (c *SubServiceContext) GetGlobalDebugMode() bool {
+	return c.fw.IsGlobalDebugMode()
+}

+ 7 - 0
go.work

@@ -0,0 +1,7 @@
+go 1.20
+
+use (
+	core
+	websubsvc
+	svcfw
+)

+ 12 - 0
go.work.sum

@@ -0,0 +1,12 @@
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=

+ 9 - 0
justfile

@@ -0,0 +1,9 @@
+#!/usr/bin/env just --justfile
+
+jfdir := replace(justfile_directory(), "\\", "/")
+
+default:
+  just --list
+
+tidy name:
+	cd {{jfdir / name}} && go mod tidy -e

+ 168 - 0
svcfw/framework.go

@@ -0,0 +1,168 @@
+package svcfw
+
+import (
+	"context"
+	"fmt"
+	"git.swzry.com/zry/GoHiedaLogger/hiedabke_console"
+	"git.swzry.com/zry/GoHiedaLogger/hiedalog"
+	"git.swzry.com/zry/zry-go-program-framework/core"
+	ordmap "github.com/edofic/go-ordmap/v2"
+	"github.com/oklog/run"
+	"io"
+	"sync"
+)
+
+var _ core.IAppFramework = (*AppFramework)(nil)
+var _ core.IModuleLogger = (*AppFramework)(nil)
+var _ core.IModuleLogLevelLimiter = (*AppFramework)(nil)
+
+type AppFramework struct {
+	rg                *run.Group
+	logger            *hiedalog.HiedaLogger
+	gctx              context.Context
+	gcncl             context.CancelFunc
+	subSvc            ordmap.NodeBuiltin[string, *core.SubServiceWrapper]
+	moduleLoggers     map[string]core.IModuleLogger
+	moduleLoggersLock sync.RWMutex
+	appLogModuleName  string
+	globalDebugSwitch bool
+	prepared          bool
+	consoleLogBackend *hiedabke_console.ConsoleBackend
+	mainLogLevelLimit uint8
+	core.IModuleLogger
+	core.IModuleLogLevelLimiter
+}
+
+func NewAppFramework(globalDebugMode bool, appLogModuleName string) *AppFramework {
+	f := &AppFramework{
+		rg:                &run.Group{},
+		logger:            hiedalog.NewHiedaLogger(),
+		subSvc:            ordmap.NewBuiltin[string, *core.SubServiceWrapper](),
+		appLogModuleName:  appLogModuleName,
+		moduleLoggers:     map[string]core.IModuleLogger{},
+		globalDebugSwitch: globalDebugMode,
+		prepared:          false,
+		mainLogLevelLimit: 0,
+	}
+	mlog := core.NewModuleLogger(appLogModuleName, f)
+	f.moduleLoggers[appLogModuleName] = mlog
+	f.IModuleLogger = mlog
+	f.IModuleLogLevelLimiter = mlog
+	return f
+}
+
+// InitConsoleLogBackend add a hiedalog console backend to logger system
+// if lv == "", for globalDebugMode is on, will use hiedalog.DLN_DEBUG;
+// for globalDebugMode is off, will use hiedalog.DLN_INFO.
+func (f *AppFramework) InitConsoleLogBackend(writer io.Writer, lv string) {
+	klv := lv
+	if klv == "" {
+		if f.globalDebugSwitch {
+			klv = hiedalog.DLN_DEBUG
+		} else {
+			klv = hiedalog.DLN_INFO
+		}
+	}
+	f.consoleLogBackend = hiedabke_console.NewConsoleBackend(writer)
+	f.logger.AddBackend(f.consoleLogBackend, f.logger.LevelFilter.NameToID(klv))
+}
+
+// ShutdownConsoleLogBackend shutdown the console backend which created by InitConsoleLogBackend
+func (f *AppFramework) ShutdownConsoleLogBackend() {
+	if f.consoleLogBackend != nil {
+		f.consoleLogBackend.Shutdown()
+	}
+}
+
+// AddSubSvc add a sub service
+func (f *AppFramework) AddSubSvc(name string, svc core.ISubService) {
+	_, ok := f.subSvc.Get(name)
+	if ok {
+		panic("SubService name exists.")
+	}
+	sw := core.NewSubServiceWrapper(f, name, svc)
+	f.subSvc = f.subSvc.Insert(name, sw)
+	f.rg.Add(sw.Run, sw.Stop)
+}
+
+// GetRawLogger get the HiedaLogger inside this
+func (f *AppFramework) GetRawLogger() *hiedalog.HiedaLogger {
+	return f.logger
+}
+
+func (f *AppFramework) GetModuleLogger(module string) core.IModuleLogger {
+	f.moduleLoggersLock.RLock()
+	v, ok := f.moduleLoggers[module]
+	if !ok {
+		f.moduleLoggersLock.RUnlock()
+		f.moduleLoggersLock.Lock()
+		v = core.NewModuleLogger(module, f)
+		f.moduleLoggers[module] = v
+		f.moduleLoggersLock.Unlock()
+	} else {
+		f.moduleLoggersLock.RUnlock()
+	}
+	return v
+}
+
+// Prepare call the `Prepare()` of each SubService by order of addition
+func (f *AppFramework) Prepare() error {
+	var err error
+	for i := f.subSvc.Iterate(); !i.Done(); i.Next() {
+		f.DebugF("preparing for sub service '%s'...", i.GetKey())
+		err = i.GetValue().Prepare()
+		if err != nil {
+			f.ErrorF("failed prepare sub service '%s': %v", i.GetKey(), err)
+			return err
+		}
+	}
+	f.prepared = true
+	return nil
+}
+
+// Run start the app
+func (f *AppFramework) Run() error {
+	if !f.prepared {
+		return fmt.Errorf("failed run app: `Prepare()` not called")
+	}
+	return f.RunWithContext(context.Background())
+}
+
+// RunWithContext start the app with custom context
+func (f AppFramework) RunWithContext(ctx context.Context) error {
+	ctx, cncl := context.WithCancel(ctx)
+	f.gcncl = cncl
+	f.gctx = ctx
+	f.rg.Add(f.bgRun, f.bgStop)
+	return f.rg.Run()
+}
+
+// Stop stop the app
+func (f *AppFramework) Stop() {
+	if f.gcncl != nil {
+		f.gcncl()
+	}
+}
+
+func (f *AppFramework) bgRun() error {
+	select {
+	case <-f.gctx.Done():
+		return nil
+	}
+}
+
+func (f *AppFramework) bgStop(xerr error) {
+	f.Stop()
+}
+
+func (f *AppFramework) GetContext() context.Context {
+	return f.gctx
+}
+
+func (f *AppFramework) IsGlobalDebugMode() bool {
+	return f.globalDebugSwitch
+}
+
+func (f *AppFramework) SetGlobalDebugMode(en bool) {
+	f.globalDebugSwitch = en
+}

+ 13 - 0
svcfw/go.mod

@@ -0,0 +1,13 @@
+module git.swzry.com/zry/zry-go-program-framework/svcfw
+
+go 1.20
+
+require (
+	git.swzry.com/zry/GoHiedaLogger/hiedabke_console v0.0.0-20230814164330-c2545e8bfba1
+	git.swzry.com/zry/GoHiedaLogger/hiedalog v0.0.0-20230712055224-281f754ce81c
+	git.swzry.com/zry/zry-go-program-framework/core v0.0.0-20230814163048-27836c09f169
+	github.com/edofic/go-ordmap/v2 v2.0.0
+	github.com/oklog/run v1.1.0
+)
+
+require github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 // indirect

+ 16 - 0
svcfw/go.sum

@@ -0,0 +1,16 @@
+git.swzry.com/zry/GoHiedaLogger/hiedabke_console v0.0.0-20230814164330-c2545e8bfba1 h1:68DRimhaFe4Tvx4kMIDNts/VpOnt4LxkRVXGmpOeHmc=
+git.swzry.com/zry/GoHiedaLogger/hiedabke_console v0.0.0-20230814164330-c2545e8bfba1/go.mod h1:QKGs1+W8+y6/RMTSaAv6LwJnY9/7zaGs38IbwmYlFbo=
+git.swzry.com/zry/GoHiedaLogger/hiedalog v0.0.0-20230712055224-281f754ce81c h1:plXCEHwBQ5Ub48r8mtGF715PXHKOMyVKDMg6eeVvb3Y=
+git.swzry.com/zry/GoHiedaLogger/hiedalog v0.0.0-20230712055224-281f754ce81c/go.mod h1:NMU7558kNXCUuK0qKYQMtYK/kn2lhwelnij295H3pdU=
+git.swzry.com/zry/zry-go-program-framework/core v0.0.0-20230814163048-27836c09f169 h1:yX2PxhD5SelqnjRPW5ClI4V79fikg+0i68/x43lqIXw=
+git.swzry.com/zry/zry-go-program-framework/core v0.0.0-20230814163048-27836c09f169/go.mod h1:3qpblCnuLmLKgdagaxqvx7HZyY+cOxuTHOCXupkIrt0=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/edofic/go-ordmap/v2 v2.0.0 h1:pWiYELTJdFAQ00TQSfA4PoyYsAfKxHNUff9x88lWZlY=
+github.com/edofic/go-ordmap/v2 v2.0.0/go.mod h1:BF4fX5pcyMRO/VidWMgFfDleHEdg+wPpCfQbORyOSCY=
+github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
+github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4=
+github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

+ 95 - 0
svcfw/ilog_impl.go

@@ -0,0 +1,95 @@
+package svcfw
+
+import (
+	"git.swzry.com/zry/GoHiedaLogger/hiedalog"
+)
+
+func (f *AppFramework) BLFatal(module string, d ...interface{}) {
+	f.logger.LogPrint(module, hiedalog.DLN_FATAL, d...)
+	f.Stop()
+}
+
+func (f *AppFramework) BLFatalF(module string, format string, d ...interface{}) {
+	f.logger.LogPrintf(module, hiedalog.DLN_FATAL, format, d...)
+	f.Stop()
+}
+
+func (f *AppFramework) BLFatalC(module string, data map[string]string) {
+	f.logger.LogComplex(module, hiedalog.DLN_FATAL, data)
+	f.Stop()
+}
+
+func (f *AppFramework) BLPanic(module string, d ...interface{}) {
+	f.logger.LogPrint(module, hiedalog.DLN_PANIC, d...)
+	panic("App Panic")
+}
+
+func (f *AppFramework) BLPanicF(module string, format string, d ...interface{}) {
+	f.logger.LogPrintf(module, hiedalog.DLN_PANIC, format, d...)
+	panic("App Panic")
+}
+
+func (f *AppFramework) BLPanicC(module string, data map[string]string) {
+	f.logger.LogComplex(module, hiedalog.DLN_PANIC, data)
+	panic("App Panic")
+}
+
+func (f *AppFramework) BLError(module string, d ...interface{}) {
+	f.logger.LogPrint(module, hiedalog.DLN_ERROR, d...)
+}
+
+func (f *AppFramework) BLErrorF(module string, format string, d ...interface{}) {
+	f.logger.LogPrintf(module, hiedalog.DLN_ERROR, format, d...)
+}
+
+func (f *AppFramework) BLErrorC(module string, data map[string]string) {
+	f.logger.LogComplex(module, hiedalog.DLN_ERROR, data)
+}
+
+func (f *AppFramework) BLWarn(module string, d ...interface{}) {
+	f.logger.LogPrint(module, hiedalog.DLN_WARN, d...)
+}
+
+func (f *AppFramework) BLWarnF(module string, format string, d ...interface{}) {
+	f.logger.LogPrintf(module, hiedalog.DLN_WARN, format, d...)
+}
+
+func (f *AppFramework) BLWarnC(module string, data map[string]string) {
+	f.logger.LogComplex(module, hiedalog.DLN_WARN, data)
+}
+
+func (f *AppFramework) BLInfo(module string, d ...interface{}) {
+	f.logger.LogPrint(module, hiedalog.DLN_INFO, d...)
+}
+
+func (f *AppFramework) BLInfoF(module string, format string, d ...interface{}) {
+	f.logger.LogPrintf(module, hiedalog.DLN_INFO, format, d...)
+}
+
+func (f *AppFramework) BLInfoC(module string, data map[string]string) {
+	f.logger.LogComplex(module, hiedalog.DLN_INFO, data)
+}
+
+func (f *AppFramework) BLVerbose(module string, d ...interface{}) {
+	f.logger.LogPrint(module, hiedalog.DLN_VERBOSE, d...)
+}
+
+func (f *AppFramework) BLVerboseF(module string, format string, d ...interface{}) {
+	f.logger.LogPrintf(module, hiedalog.DLN_VERBOSE, format, d...)
+}
+
+func (f *AppFramework) BLVerboseC(module string, data map[string]string) {
+	f.logger.LogComplex(module, hiedalog.DLN_VERBOSE, data)
+}
+
+func (f *AppFramework) BLDebug(module string, d ...interface{}) {
+	f.logger.LogPrint(module, hiedalog.DLN_DEBUG, d...)
+}
+
+func (f *AppFramework) BLDebugF(module string, format string, d ...interface{}) {
+	f.logger.LogPrintf(module, hiedalog.DLN_DEBUG, format, d...)
+}
+
+func (f *AppFramework) BLDebugC(module string, data map[string]string) {
+	f.logger.LogComplex(module, hiedalog.DLN_DEBUG, data)
+}

+ 1 - 0
svcfw/imlog_impl.go

@@ -0,0 +1 @@
+package svcfw

+ 70 - 0
svcfw/sig_quiter.go

@@ -0,0 +1,70 @@
+package svcfw
+
+import (
+	"context"
+	"git.swzry.com/zry/zry-go-program-framework/core"
+	"os"
+	"os/signal"
+)
+
+var _ core.ISubService = (*WatchSignalExitSubService)(nil)
+
+type WatchSignalExitSubService struct {
+	qchan            chan os.Signal
+	ctx              context.Context
+	cncl             context.CancelFunc
+	SignalNotifyFunc func(sig os.Signal)
+	sigList          []os.Signal
+}
+
+func NewWatchSignalExitSubService(sig ...os.Signal) *WatchSignalExitSubService {
+	r := &WatchSignalExitSubService{
+		qchan:   make(chan os.Signal, 0),
+		sigList: sig,
+	}
+	return r
+}
+
+func NewWatchSignalExitSubServiceWithDefault() *WatchSignalExitSubService {
+	r := &WatchSignalExitSubService{
+		qchan: make(chan os.Signal, 0),
+		sigList: []os.Signal{
+			os.Interrupt,
+			os.Kill,
+		},
+	}
+	return r
+}
+
+func (q *WatchSignalExitSubService) Prepare(ctx *core.SubServiceContext) error {
+	return nil
+}
+
+func (q *WatchSignalExitSubService) Run(ctx *core.SubServiceContext) error {
+	signal.Notify(q.qchan, q.sigList...)
+	q.ctx, q.cncl = context.WithCancel(context.Background())
+	select {
+	case <-q.ctx.Done():
+		{
+			close(q.qchan)
+			return nil
+		}
+	case sig := <-q.qchan:
+		{
+			ctx.InfoF("signal '%s' caught. will shutdown program.", sig.String())
+			if q.cncl != nil {
+				q.cncl()
+			}
+			if q.SignalNotifyFunc != nil {
+				q.SignalNotifyFunc(sig)
+			}
+			return nil
+		}
+	}
+}
+
+func (q *WatchSignalExitSubService) Stop(ctx *core.SubServiceContext) {
+	if q.cncl != nil {
+		q.cncl()
+	}
+}

+ 79 - 0
websubsvc/gin_recov.go

@@ -0,0 +1,79 @@
+package websubsvc
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"runtime"
+	"time"
+)
+
+var (
+	dunno     = []byte("???")
+	centerDot = []byte("·")
+	dot       = []byte(".")
+	slash     = []byte("/")
+)
+
+func ginStack(skip int) []byte {
+	buf := new(bytes.Buffer) // the returned data
+	// As we loop, we open files and read them. These variables record the currently
+	// loaded file.
+	var lines [][]byte
+	var lastFile string
+	for i := skip; ; i++ { // Skip the expected number of frames
+		pc, file, line, ok := runtime.Caller(i)
+		if !ok {
+			break
+		}
+		// Print this much at least.  If we can't find the source, it won't show.
+		fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
+		if file != lastFile {
+			data, err := os.ReadFile(file)
+			if err != nil {
+				continue
+			}
+			lines = bytes.Split(data, []byte{'\n'})
+			lastFile = file
+		}
+		fmt.Fprintf(buf, "\t%s: %s\n", ginFunction(pc), source(lines, line))
+	}
+	return buf.Bytes()
+}
+func source(lines [][]byte, n int) []byte {
+	n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
+	if n < 0 || n >= len(lines) {
+		return dunno
+	}
+	return bytes.TrimSpace(lines[n])
+}
+
+// function returns, if possible, the name of the function containing the PC.
+func ginFunction(pc uintptr) []byte {
+	fn := runtime.FuncForPC(pc)
+	if fn == nil {
+		return dunno
+	}
+	name := []byte(fn.Name())
+	// The name includes the path name to the package, which is unnecessary
+	// since the file name is already included.  Plus, it has center dots.
+	// That is, we see
+	//	runtime/debug.*T·ptrmethod
+	// and want
+	//	*T.ptrmethod
+	// Also the package path might contain dot (e.g. code.google.com/...),
+	// so first eliminate the path prefix
+	if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
+		name = name[lastSlash+1:]
+	}
+	if period := bytes.Index(name, dot); period >= 0 {
+		name = name[period+1:]
+	}
+	name = bytes.ReplaceAll(name, centerDot, dot)
+	return name
+}
+
+// timeFormat returns a customized time string for logger.
+func ginTimeFormat(t time.Time) string {
+	return t.Format("2006/01/02 - 15:04:05")
+}

+ 32 - 0
websubsvc/go.mod

@@ -0,0 +1,32 @@
+module git.swzry.com/zry/zry-go-program-framework/websubsvc
+
+go 1.20
+
+require github.com/gin-gonic/gin v1.9.1
+
+require (
+	github.com/bytedance/sonic v1.9.1 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.14.0 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.11 // indirect
+	golang.org/x/arch v0.3.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
+	google.golang.org/protobuf v1.30.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 85 - 0
websubsvc/go.sum

@@ -0,0 +1,85 @@
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
+github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
+github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
+golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 99 - 0
websubsvc/http_svr_cfg.go

@@ -0,0 +1,99 @@
+package websubsvc
+
+import (
+	"context"
+	"crypto/tls"
+	"log"
+	"net"
+	"net/http"
+	"time"
+)
+
+type HttpServerConfig struct {
+	// DisableGeneralOptionsHandler, if true, passes "OPTIONS *" requests to the Handler,
+	// otherwise responds with 200 OK and Content-Length: 0.
+	DisableGeneralOptionsHandler bool
+
+	// TLSConfig optionally provides a TLS configuration for use
+	// by ServeTLS and ListenAndServeTLS. Note that this value is
+	// cloned by ServeTLS and ListenAndServeTLS, so it's not
+	// possible to modify the configuration with methods like
+	// tls.Config.SetSessionTicketKeys. To use
+	// SetSessionTicketKeys, use Server.Serve with a TLS Listener
+	// instead.
+	TLSConfig *tls.Config
+
+	// ReadTimeout is the maximum duration for reading the entire
+	// request, including the body. A zero or negative value means
+	// there will be no timeout.
+	//
+	// Because ReadTimeout does not let Handlers make per-request
+	// decisions on each request body's acceptable deadline or
+	// upload rate, most users will prefer to use
+	// ReadHeaderTimeout. It is valid to use them both.
+	ReadTimeout time.Duration
+
+	// ReadHeaderTimeout is the amount of time allowed to read
+	// request headers. The connection's read deadline is reset
+	// after reading the headers and the Handler can decide what
+	// is considered too slow for the body. If ReadHeaderTimeout
+	// is zero, the value of ReadTimeout is used. If both are
+	// zero, there is no timeout.
+	ReadHeaderTimeout time.Duration
+
+	// WriteTimeout is the maximum duration before timing out
+	// writes of the response. It is reset whenever a new
+	// request's header is read. Like ReadTimeout, it does not
+	// let Handlers make decisions on a per-request basis.
+	// A zero or negative value means there will be no timeout.
+	WriteTimeout time.Duration
+
+	// IdleTimeout is the maximum amount of time to wait for the
+	// next request when keep-alives are enabled. If IdleTimeout
+	// is zero, the value of ReadTimeout is used. If both are
+	// zero, there is no timeout.
+	IdleTimeout time.Duration
+
+	// MaxHeaderBytes controls the maximum number of bytes the
+	// server will read parsing the request header's keys and
+	// values, including the request line. It does not limit the
+	// size of the request body.
+	// If zero, DefaultMaxHeaderBytes is used.
+	MaxHeaderBytes int
+
+	// TLSNextProto optionally specifies a function to take over
+	// ownership of the provided TLS connection when an ALPN
+	// protocol upgrade has occurred. The map key is the protocol
+	// name negotiated. The Handler argument should be used to
+	// handle HTTP requests and will initialize the Request's TLS
+	// and RemoteAddr if not already set. The connection is
+	// automatically closed when the function returns.
+	// If TLSNextProto is not nil, HTTP/2 support is not enabled
+	// automatically.
+	TLSNextProto map[string]func(*http.Server, *tls.Conn, http.Handler)
+
+	// ConnState specifies an optional callback function that is
+	// called when a client connection changes state. See the
+	// ConnState type and associated constants for details.
+	ConnState func(net.Conn, http.ConnState)
+
+	// ErrorLog specifies an optional logger for errors accepting
+	// connections, unexpected behavior from handlers, and
+	// underlying FileSystem errors.
+	// If nil, logging is done via the log package's standard logger.
+	ErrorLog *log.Logger
+
+	// BaseContext optionally specifies a function that returns
+	// the base context for incoming requests on this server.
+	// The provided Listener is the specific Listener that's
+	// about to start accepting requests.
+	// If BaseContext is nil, the default is context.Background().
+	// If non-nil, it must return a non-nil context.
+	BaseContext func(net.Listener) context.Context
+
+	// ConnContext optionally specifies a function that modifies
+	// the context used for a new connection c. The provided ctx
+	// is derived from the base context and has a ServerContextKey
+	// value.
+	ConnContext func(ctx context.Context, c net.Conn) context.Context
+}

+ 87 - 0
websubsvc/web.go

@@ -0,0 +1,87 @@
+package websubsvc
+
+import (
+	"context"
+	"git.swzry.com/zry/zry-go-program-framework/core"
+	"github.com/gin-gonic/gin"
+	"net/http"
+	"time"
+)
+
+var _ core.ISubService = (*WebSubService)(nil)
+
+type WebSubService struct {
+	logic           IWebSubServiceLogic
+	ginEngine       *gin.Engine
+	wctx            *WebSubServiceContext
+	httpServer      *http.Server
+	enbaleTLS       bool
+	certFile        string
+	keyFile         string
+	running         bool
+	shutdownTimeout time.Duration
+}
+
+type IWebSubServiceLogic interface {
+	Prepare(ctx *WebSubServiceContext) error
+	GetHttpServer(ctx *WebSubServiceContext) *http.Server
+}
+
+func NewWebSubService(logic IWebSubServiceLogic) *WebSubService {
+	s := &WebSubService{
+		logic:           logic,
+		ginEngine:       gin.New(),
+		running:         false,
+		shutdownTimeout: 10 * time.Second,
+	}
+	return s
+}
+
+func (w *WebSubService) Prepare(ctx *core.SubServiceContext) error {
+	w.wctx = &WebSubServiceContext{
+		subSvcCtx:     ctx,
+		IModuleLogger: ctx,
+		s:             w,
+	}
+	if ctx.GetGlobalDebugMode() {
+		gin.SetMode(gin.DebugMode)
+	} else {
+		gin.SetMode(gin.ReleaseMode)
+	}
+	err := w.logic.Prepare(w.wctx)
+	w.httpServer = w.logic.GetHttpServer(w.wctx)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (w *WebSubService) Run(ctx *core.SubServiceContext) error {
+	ctx.Info("listening at: ", w.httpServer.Addr)
+	var err error
+	w.running = true
+	if w.enbaleTLS {
+		err = w.httpServer.ListenAndServeTLS(w.certFile, w.keyFile)
+	} else {
+		err = w.httpServer.ListenAndServe()
+	}
+	w.running = false
+	if err != nil {
+		ctx.Error("error in running web server: ", err)
+		return err
+	} else {
+		ctx.Info("web server end normally")
+	}
+	return nil
+}
+
+func (w *WebSubService) Stop(ctx *core.SubServiceContext) {
+	if w.running {
+		sctx, cncl := context.WithTimeout(context.Background(), w.shutdownTimeout)
+		defer cncl()
+		err := w.httpServer.Shutdown(sctx)
+		if err != nil {
+			ctx.Error("error in shutting down web api server: ", err)
+		}
+	}
+}

+ 174 - 0
websubsvc/webctx.go

@@ -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)
+}