Selaa lähdekoodia

添加HTTP服务器及相关配置

新增了HTTP服务器的实现及其相关配置文件,包括CORS配置、远程IP信任配置等,并且实现了服务器的基础功能如启动、停止及请求处理。同时增加了多个工具函数来辅助服务器操作,例如文件读取、响应处理等。
SongZihuan 3 kuukautta sitten
vanhempi
sitoutus
73369716b6
52 muutettua tiedostoa jossa 2924 lisäystä ja 84 poistoa
  1. 3 83
      README.md
  2. 13 0
      REEPORT
  3. 9 0
      go.mod
  4. 7 0
      go.sum
  5. 6 1
      resource.go
  6. 3 0
      src/cmd/define/mainfuc.go
  7. 17 0
      src/cmd/v1/main.go
  8. 98 0
      src/config/config.go
  9. 49 0
      src/config/corsconfig.go
  10. 52 0
      src/config/corsorigin.go
  11. 45 0
      src/config/ctrl.go
  12. 83 0
      src/config/error.go
  13. 77 0
      src/config/globalconfig.go
  14. 35 0
      src/config/httpconfig.go
  15. 66 0
      src/config/proxyconfig.go
  16. 37 0
      src/config/proxyrule.go
  17. 44 0
      src/config/proxyserverconfig.go
  18. 45 0
      src/config/remoteiptrustsconfig.go
  19. 19 0
      src/config/signal.go
  20. 58 0
      src/config/yamlconfig.go
  21. 301 0
      src/flagparser/data.go
  22. 29 0
      src/flagparser/error.go
  23. 82 0
      src/flagparser/flag.go
  24. 103 0
      src/flagparser/getter.go
  25. 99 0
      src/logger/export.go
  26. 201 0
      src/logger/logger.go
  27. 80 0
      src/mainfunc/v1.go
  28. 152 0
      src/server/api.go
  29. 32 0
      src/server/cors.go
  30. 115 0
      src/server/dir.go
  31. 36 0
      src/server/file.go
  32. 49 0
      src/server/proxytrust.go
  33. 40 0
      src/server/respose.go
  34. 88 0
      src/server/server.go
  35. 17 0
      src/utils/cidr.go
  36. 53 0
      src/utils/cmd.go
  37. 45 0
      src/utils/exit.go
  38. 40 0
      src/utils/file.go
  39. 10 0
      src/utils/hash.go
  40. 26 0
      src/utils/http.go
  41. 16 0
      src/utils/ip.go
  42. 34 0
      src/utils/mimetype.go
  43. 20 0
      src/utils/rand.go
  44. 37 0
      src/utils/restart.go
  45. 25 0
      src/utils/runtime.go
  46. 14 0
      src/utils/slice.go
  47. 141 0
      src/utils/string.go
  48. 75 0
      src/utils/stringbool.go
  49. 26 0
      src/utils/time.go
  50. 23 0
      src/utils/url.go
  51. 50 0
      src/utils/utf8.go
  52. 99 0
      src/utils/version.go

+ 3 - 83
README.md

@@ -1,84 +1,4 @@
-# 猫猫超市后端
-前端:[github.com/SongZihuan/cat-shop-front](https://github.com/SongZihuan/cat-shop-front)
+# 桓代理(HuanProxy)
 
-技术栈:Golang + gin + gorm + jwt + yaml
-
-Golang: 作者本人使用`go1.23.2`。
-
-数据库:MySQL 8.0 以上的版本。
-
-其他细节可见:[go.mod](./go.mod)
-
-## 许可(License)
-本项目使用[MIT LICENSE](./LICENSE)许可证发布。
-
-MIT License: [mit-license.org](https://mit-license.org/)
-
-## 配置文件
-```yaml
-mysql:
-  username: # mysql用户名
-  password: # mysql用户密码
-  address: # mysql地址
-  post: # mysql端口号
-  dbname: # mysql数据库名称
-
-file:
-  localpath: # 文件上传时保存的地理位置(为一个文件夹地址,可不存在)
-
-http:
-  address: # http监听地址 默认:localhost:2689
-  debugmsg: # api是否返回调试信息
-  baseapi: # api前缀 默认:/api (为空则使用默认)
-  testapi: # 是否开启测试api
-
-jwt:
-  secretpath: # jwt密钥保存地址(为一个文件地址,可不存在)
-  hour: 5 # jwt令牌有效期(小时)
-  resetmin: 30  # jwt令牌重置倒计时,当令牌距离过期时间短于此设定时将自动更新令牌(分钟)
-```
-
-## 运行
-参数:
-```
--help 查看帮助详情(打印帮助信息,服务不会运行)
--config 配置文件信息(默认:config.yaml)
-```
-
-### 测试运行
-可以通过`go run`直接运行项目。
-
-```shell
-# 显示把昂住信息
-go run github.com/SongZihuan/cat-shop-backend/src/cmd/v1 -help
-
-# 运行服务并指定配置文件
-go run github.com/SongZihuan/cat-shop-backend/src/cmd/v1 -config ./etc/config.yaml
-```
-
-### 实际运行
-构建成可执行程序后可实际运行。构建请参考下文。若可执行文件为`./shop.exe`,则运行方式为:
-
-```shell
-# 显示把昂住信息
-./shop.exe -help
-
-# 运行服务并指定配置文件
-./shop.exe -config ./etc/config.yaml
-```
-
-## 构建
-可以使用`go build`进行构建。
-
-```shell
-go build github.com/SongZihuan/cat-shop-backend/src/cmd/v1
-```
-
-## 鸣谢
-感谢Jetbrains AI Assistant(中国大陆版)为本项目提供了AI(人工智能)技术支持。
-
-感谢Golang、Gin、Gorm等开源项目为本项目提供了技术支持。
-
-感谢Github平台为本项目提供了代码托管服务。
-
-特别鸣谢本项目所有贡献者和贡献团体对本项目的支持,你可以从PR记录、Commit记录中查看到他们的名字和贡献。
+## 简介
+HuanProxy:简单的反向代理和静态文件托管服务器,支持SSL证书申请和续订

+ 13 - 0
REEPORT

@@ -0,0 +1,13 @@
+How to report of Huan-Proxy
+
+Author: 宋子桓(SongZihuan)
+Author Github: https://github.com/SongZihuan
+Author Website: https://song-zh.com
+Author Email: songzihuan@song-zh.com
+
+Github: https://github.com/SongZihuan/huan-proxy
+Github Issues: https://github.com/SongZihuan/huan-proxy/issues
+
+Report: You can report issues and contact the author through Github Issues or Author Email.
+Quality Assurance: If you only have the license in the LICENSE file in the root directory of this project, you will not get any quality assurance. But the author is happy to solve the problem for you, unless this project has been archived as read-only.
+Other Fork versions: Please contact the author of the Fork version for assistance.

+ 9 - 0
go.mod

@@ -3,3 +3,12 @@ module github.com/SongZihuan/huan-proxy
 go 1.21.0
 
 toolchain go1.23.2
+
+require (
+	github.com/gabriel-vasile/mimetype v1.4.7
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	golang.org/x/net v0.33.0 // indirect
+)

+ 7 - 0
go.sum

@@ -0,0 +1,7 @@
+github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA=
+github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 6 - 1
resource.go

@@ -1,9 +1,14 @@
 package resource
 
-import _ "embed"
+import (
+	_ "embed"
+)
 
 //go:embed VERSION
 var Version string
 
 //go:embed LICENSE
 var License string
+
+//go:embed REEPORT
+var Report string

+ 3 - 0
src/cmd/define/mainfuc.go

@@ -0,0 +1,3 @@
+package define
+
+type MainFunc func() int

+ 17 - 0
src/cmd/v1/main.go

@@ -0,0 +1,17 @@
+package main
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/cmd/define"
+	"github.com/SongZihuan/huan-proxy/src/mainfunc"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+var v1Main define.MainFunc = mainfunc.MainV1
+
+func main() {
+	utils.Exit(_main())
+}
+
+func _main() int {
+	return v1Main()
+}

+ 98 - 0
src/config/config.go

@@ -0,0 +1,98 @@
+package config
+
+import "os"
+
+type ConfigStruct struct {
+	configReady   bool
+	yamlHasParser bool
+	sigChan       chan os.Signal
+
+	Yaml        YamlConfig
+	CoreOrigin  CorsOrigin
+	ProxyServer ProxyServerConfig
+}
+
+func (c *ConfigStruct) init() error {
+	c.configReady = false
+	c.yamlHasParser = false
+
+	c.sigChan = make(chan os.Signal)
+	err := initSignal(c.sigChan)
+	if err != nil {
+		return err
+	}
+
+	err = c.Yaml.init()
+	if err != nil {
+		return err
+	}
+
+	err = c.CoreOrigin.init()
+	if err != nil {
+		return err
+	}
+
+	err = c.ProxyServer.init()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (c *ConfigStruct) parser() ParserError {
+	err := c.Yaml.parser()
+	if err != nil {
+		return err
+	}
+
+	c.yamlHasParser = true
+	return nil
+}
+
+func (c *ConfigStruct) setDefault() {
+	if !c.yamlHasParser {
+		panic("yaml must parser first")
+	}
+
+	c.Yaml.setDefault()
+}
+
+func (c *ConfigStruct) check() (err ConfigError) {
+	err = c.Yaml.check(&c.CoreOrigin, &c.ProxyServer)
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	return nil
+}
+
+func (c *ConfigStruct) ready() (err ConfigError) {
+	if c.configReady {
+		return nil
+	}
+
+	initErr := c.init()
+	if initErr != nil {
+		return NewConfigError("init error: " + initErr.Error())
+	}
+
+	parserErr := c.parser()
+	if parserErr != nil {
+		return NewConfigError("parser error: " + parserErr.Error())
+	} else if !c.yamlHasParser {
+		return NewConfigError("parser error: unknown")
+	}
+
+	c.setDefault()
+	err = c.check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	c.configReady = true
+	return nil
+}
+
+func (c *ConfigStruct) GetSignalChan() chan os.Signal {
+	return c.sigChan
+}

+ 49 - 0
src/config/corsconfig.go

@@ -0,0 +1,49 @@
+package config
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+const CorsMaxAgeSec = 86400
+const CorsDefaultMaxAgeSec = CorsMaxAgeSec
+
+type CorsConfig struct {
+	AllowCors      utils.StringBool `json:"allowcors"`
+	AllowOrigin    []string         `json:"alloworigin"`
+	AllowOriginReg []string         `json:"alloworiginres"`
+	MaxAgeSec      int              `json:"maxagesec"`
+}
+
+func (c *CorsConfig) setDefault() {
+	c.AllowCors.SetDefaultDisable()
+	if c.AllowCors.IsEnable() && c.MaxAgeSec == 0 {
+		c.MaxAgeSec = CorsMaxAgeSec
+	}
+}
+
+func (c *CorsConfig) check(co *CorsOrigin) ConfigError {
+	if c.AllowCors.IsEnable() {
+		if c.MaxAgeSec <= 0 || c.MaxAgeSec > CorsMaxAgeSec {
+			return NewConfigError(fmt.Sprintf("cors maxagesec %d is invalid", c.MaxAgeSec))
+		}
+
+		err := co.SetString(c.AllowOrigin)
+		if err != nil {
+			return NewConfigError("cors allowcors is invalid")
+		}
+
+		for _, r := range c.AllowOriginReg {
+			_ = co.ApplyReg(r)
+		}
+	}
+	return nil
+}
+
+func (c *CorsConfig) Enable() bool {
+	return c.AllowCors.IsEnable()
+}
+
+func (c *CorsConfig) Disable() bool {
+	return c.AllowCors.IsDisable()
+}

+ 52 - 0
src/config/corsorigin.go

@@ -0,0 +1,52 @@
+package config
+
+import "regexp"
+
+const DefaultOriginListSize = 10
+const AllowAllOrigin = "*"
+
+type CorsOrigin struct {
+	OriginReg    []*regexp.Regexp
+	OriginString []string
+}
+
+func (c *CorsOrigin) init() error {
+	c.OriginReg = make([]*regexp.Regexp, 0, DefaultOriginListSize)
+	c.OriginString = nil
+	return nil
+}
+
+func (c *CorsOrigin) ApplyReg(origin string) error {
+	reg, err := regexp.Compile(origin)
+	if err != nil {
+		return err
+	}
+	c.OriginReg = append(c.OriginReg, reg)
+	return nil
+}
+
+func (c *CorsOrigin) SetString(origins []string) error {
+	c.OriginString = origins
+	return nil
+}
+
+func (c *CorsOrigin) InOriginList(origin string) bool {
+	if (c.OriginString == nil || len(c.OriginString) == 0) && len(c.OriginReg) == 0 {
+		return true
+	}
+
+	for _, org := range c.OriginString {
+		if org == AllowAllOrigin {
+			return true
+		} else if org == origin {
+			return true
+		}
+	}
+
+	for _, reg := range c.OriginReg {
+		if reg != nil && reg.MatchString(origin) {
+			return true
+		}
+	}
+	return false
+}

+ 45 - 0
src/config/ctrl.go

@@ -0,0 +1,45 @@
+package config
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+)
+
+func newConfig() ConfigStruct {
+	return ConfigStruct{
+		configReady:   false,
+		yamlHasParser: false,
+	}
+}
+
+func InitConfig() ConfigError {
+	if !flagparser.IsReady() {
+		return NewConfigError("flag not ready")
+	}
+
+	config = newConfig()
+	err := config.ready()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	if !config.configReady {
+		return NewConfigError("config not ready")
+	}
+
+	return nil
+}
+
+func IsReady() bool {
+	return config.yamlHasParser && config.configReady
+}
+
+func Config() *ConfigStruct {
+	if !IsReady() {
+		panic("config not ready")
+	}
+
+	tmp := config
+	return &tmp
+}
+
+var config ConfigStruct

+ 83 - 0
src/config/error.go

@@ -0,0 +1,83 @@
+package config
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type ConfigError interface {
+	error
+	Msg() string
+	Error() string
+	Warning() string
+	IsError() bool
+	IsWarning() bool
+}
+
+func NewConfigError(msg string) ConfigError {
+	fmt.Println(utils.FormatTextToWidth(fmt.Sprintf("config error: %s", msg), utils.NormalConsoleWidth))
+	return &configError{msg: msg, isError: true}
+}
+
+func NewConfigWarning(msg string) ConfigError {
+	fmt.Println(utils.FormatTextToWidth(fmt.Sprintf("config warning: %s", msg), utils.NormalConsoleWidth))
+	return &configError{msg: msg, isError: false}
+}
+
+type configError struct {
+	msg     string
+	isError bool
+}
+
+func (e *configError) Msg() string {
+	if e.isError {
+		return "config error: " + e.Error()
+	}
+	return "config warning: " + e.Warning()
+}
+
+func (e *configError) Error() string {
+	return e.msg
+}
+
+func (e *configError) Warning() string {
+	return e.msg
+}
+
+func (e *configError) IsError() bool {
+	return e.isError
+}
+
+func (e *configError) IsWarning() bool {
+	return !e.isError
+}
+
+type ParserError interface {
+	error
+	Error() string
+	Data() interface{}
+}
+
+type parserError struct {
+	msg  string
+	data interface{}
+}
+
+func NewParserError(data interface{}, msg ...string) ParserError {
+	if len(msg) == 1 {
+		return &parserError{msg[0], data}
+	}
+	return &parserError{"config parser error: " + fmt.Sprint(data), data}
+}
+
+func WarpParserError(err error) ParserError {
+	return &parserError{"config parser error: " + err.Error(), err}
+}
+
+func (e *parserError) Error() string {
+	return e.msg
+}
+
+func (e *parserError) Data() interface{} {
+	return e.data
+}

+ 77 - 0
src/config/globalconfig.go

@@ -0,0 +1,77 @@
+package config
+
+import (
+	"os"
+)
+
+const EnvModeName = "HUAN_PROXY_MODE"
+
+const (
+	DebugMode   = "debug"
+	ReleaseMode = "release"
+	TestMode    = "test"
+)
+
+type LoggerLevel string
+
+var levelMap = map[string]bool{
+	"debug": true,
+	"info":  true,
+	"warn":  true,
+	"error": true,
+	"panic": true,
+	"none":  true,
+}
+
+type GlobalConfig struct {
+	Mode     string `json:"mode"`
+	LogLevel string `json:"loglevel"`
+}
+
+func (g *GlobalConfig) setDefault() {
+	if g.Mode == "" {
+		g.Mode = os.Getenv(EnvModeName)
+	}
+
+	if g.Mode == "" {
+		g.Mode = DebugMode
+	}
+
+	_ = os.Setenv(EnvModeName, g.Mode)
+
+	if g.LogLevel == "" && (g.Mode == DebugMode || g.Mode == TestMode) {
+		g.LogLevel = "debug"
+	} else if g.LogLevel == "" {
+		g.LogLevel = "warn"
+	}
+
+	return
+}
+
+func (g *GlobalConfig) check() ConfigError {
+	if g.Mode != DebugMode && g.Mode != ReleaseMode && g.Mode != TestMode {
+		return NewConfigError("bad mode")
+	}
+
+	if _, ok := levelMap[g.LogLevel]; !ok {
+		return NewConfigError("log level error")
+	}
+
+	return nil
+}
+
+func (g *GlobalConfig) GetGinMode() string {
+	return g.Mode
+}
+
+func (g *GlobalConfig) IsDebug() bool {
+	return g.Mode == DebugMode
+}
+
+func (g *GlobalConfig) IsRelease() bool {
+	return g.Mode == ReleaseMode
+}
+
+func (g *GlobalConfig) IsTest() bool {
+	return g.Mode == TestMode
+}

+ 35 - 0
src/config/httpconfig.go

@@ -0,0 +1,35 @@
+package config
+
+type HttpConfig struct {
+	Address        string            `yaml:"address"`
+	RemoteTrust    RemoteTrustConfig `yaml:"remotetrust"`
+	StopWaitSecond int               `yaml:"stopwaitsecond"`
+	Cors           CorsConfig        `yaml:"cors"`
+}
+
+func (h *HttpConfig) setDefault(global *GlobalConfig) {
+	if h.Address == "" {
+		h.Address = "localhost:2689"
+	}
+
+	if h.StopWaitSecond <= 0 {
+		h.StopWaitSecond = 10
+	}
+
+	h.RemoteTrust.setDefault(global)
+	h.Cors.setDefault()
+}
+
+func (h *HttpConfig) check(co *CorsOrigin) ConfigError {
+	err := h.RemoteTrust.check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = h.Cors.check(co)
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	return nil
+}

+ 66 - 0
src/config/proxyconfig.go

@@ -0,0 +1,66 @@
+package config
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net/url"
+)
+
+const (
+	ProxyTypeFile = "file"
+	ProxyTypeDir  = "dir"
+	ProxyTypeAPI  = "api"
+)
+
+type ProxyConfig struct {
+	Type     string `yaml:"type"`
+	BasePath string `yaml:"basepath"`
+
+	ProxyFileConfig `yaml:",inline"`
+	ProxyDirConfig  `yaml:",inline"`
+	ProxyAPIConfig  `yaml:",inline"`
+}
+
+type ProxyFileConfig struct {
+	File string `yaml:"file"`
+}
+
+type ProxyDirConfig struct {
+	Dir string `yaml:"dir"`
+}
+
+type ProxyAPIConfig struct {
+	Address    string           `yaml:"address"`
+	PrefixPath string           `yaml:"prefixpath"`
+	EnableSSL  utils.StringBool `yaml:"enablessl"`
+}
+
+func (p *ProxyConfig) setDefault() {
+	p.BasePath = utils.ProcessPath(p.BasePath)
+	fmt.Printf("TAG NB [%s]\n", p.BasePath)
+
+	if p.Type == ProxyTypeAPI {
+		p.PrefixPath = utils.ProcessPath(p.PrefixPath)
+	}
+}
+
+func (p *ProxyConfig) check() ConfigError {
+	if p.Type == ProxyTypeFile {
+		if !utils.IsFile(p.File) {
+			return NewConfigError(fmt.Sprintf("file path %s not exist", p.File))
+		}
+	} else if p.Type == ProxyTypeDir {
+		if !utils.IsDir(p.Dir) {
+			return NewConfigError(fmt.Sprintf("dir path %s not exist", p.Dir))
+		}
+	} else if p.Type == ProxyTypeAPI {
+		_, err := url.Parse(p.Address)
+		if err != nil {
+			return NewConfigError(fmt.Sprintf("Failed to parse target URL: %v", err))
+		}
+	} else {
+		return NewConfigError("proxy type must be file or dir or api")
+	}
+	fmt.Printf("TAG CC [%s]\n", p.BasePath)
+	return nil
+}

+ 37 - 0
src/config/proxyrule.go

@@ -0,0 +1,37 @@
+package config
+
+import "fmt"
+
+type ProxyRuleConfig struct {
+	Rules []*ProxyConfig `yaml:"rules"`
+}
+
+func (r *ProxyRuleConfig) setDefault() {
+	for _, rule := range r.Rules {
+		rule.setDefault()
+		fmt.Printf("TAG DD [%s]\n", rule.BasePath)
+	}
+	fmt.Printf("TAG DDAA [%s]\n", r.Rules[0].BasePath)
+}
+
+func (r *ProxyRuleConfig) check(ps *ProxyServerConfig) ConfigError {
+	if len(r.Rules) == 0 {
+		return NewConfigError("proxy rule is empty")
+	}
+
+	for index, rule := range r.Rules {
+		err := rule.check()
+		if err != nil && err.IsError() {
+			return err
+		}
+
+		if rule.Type == ProxyTypeAPI {
+			err := ps.Add(index, rule)
+			if err != nil {
+				return NewConfigError(fmt.Sprintf("proxy server can not create: %s", err.Error()))
+			}
+		}
+	}
+
+	return nil
+}

+ 44 - 0
src/config/proxyserverconfig.go

@@ -0,0 +1,44 @@
+package config
+
+import (
+	"fmt"
+	"net/http/httputil"
+	"net/url"
+)
+
+const defaultServerPort = 10
+
+type ProxyServerConfig struct {
+	Server map[int]*httputil.ReverseProxy
+}
+
+func (p *ProxyServerConfig) init() error {
+	p.Server = make(map[int]*httputil.ReverseProxy, defaultServerPort)
+	return nil
+}
+
+func (p *ProxyServerConfig) Add(index int, rule *ProxyConfig) error {
+	if rule.Type != ProxyTypeAPI {
+		return nil
+	}
+
+	if _, ok := p.Server[index]; ok {
+		return fmt.Errorf("proxy server %d already exists", index)
+	}
+
+	targetURL, err := url.Parse(rule.Address)
+	if err != nil {
+		return err
+	}
+
+	p.Server[index] = httputil.NewSingleHostReverseProxy(targetURL)
+
+	return nil
+}
+
+func (p *ProxyServerConfig) Get(index int) *httputil.ReverseProxy {
+	if proxy, ok := p.Server[index]; ok {
+		return proxy
+	}
+	return nil
+}

+ 45 - 0
src/config/remoteiptrustsconfig.go

@@ -0,0 +1,45 @@
+package config
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+type RemoteTrustConfig struct {
+	RemoteTrust utils.StringBool `json:"remotetrust"`
+	TrustedIPs  []string         `json:"trustedips"`
+}
+
+func (p *RemoteTrustConfig) setDefault(global *GlobalConfig) {
+	if global.IsDebug() || global.IsTest() {
+		p.RemoteTrust.SetDefaultEanble()
+	} else {
+		p.RemoteTrust.SetDefaultDisable()
+	}
+
+	if p.RemoteTrust.IsEnable() && len(p.TrustedIPs) == 0 {
+		p.TrustedIPs = []string{"127.0.0.0/8", "::1"}
+	}
+}
+
+func (p *RemoteTrustConfig) check() ConfigError {
+	if p.RemoteTrust.IsEnable() {
+		if len(p.TrustedIPs) == 0 {
+			_ = NewConfigWarning("proxy trusts ips will be ignore because proxy is disabled")
+		} else {
+			for _, ip := range p.TrustedIPs {
+				if !utils.ValidIPv4(ip) && !utils.ValidIPv6(ip) && !utils.IsValidIPv4CIDR(ip) && !utils.IsValidIPv6CIDR(ip) {
+					return NewConfigError(fmt.Sprintf("bad proxy trusts ip address: %s", ip))
+				}
+			}
+		}
+	} else {
+		_ = NewConfigWarning("You trusted all proxies, this is NOT safe. We recommend you to set a value.")
+	}
+
+	return nil
+}
+
+func (p *RemoteTrustConfig) Enable() bool {
+	return p.RemoteTrust.IsEnable()
+}

+ 19 - 0
src/config/signal.go

@@ -0,0 +1,19 @@
+package config
+
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"syscall"
+)
+
+func initSignal(sigChan chan os.Signal) (err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("init signal error: %v", r)
+		}
+	}()
+
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	return nil
+}

+ 58 - 0
src/config/yamlconfig.go

@@ -0,0 +1,58 @@
+package config
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"gopkg.in/yaml.v3"
+	"os"
+)
+
+type YamlConfig struct {
+	GlobalConfig `yaml:",inline"`
+	Http         HttpConfig      `yaml:"http"`
+	Rules        ProxyRuleConfig `yaml:"rules"`
+}
+
+func (y *YamlConfig) init() error {
+	return nil
+}
+
+func (y *YamlConfig) setDefault() {
+	y.GlobalConfig.setDefault()
+	y.Http.setDefault(&y.GlobalConfig)
+	y.Rules.setDefault()
+	fmt.Printf("TAG DDCC [%s]\n", y.Rules.Rules[0].BasePath)
+}
+
+func (y *YamlConfig) check(co *CorsOrigin, ps *ProxyServerConfig) (err ConfigError) {
+	err = y.GlobalConfig.check()
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = y.Http.check(co)
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	err = y.Rules.check(ps)
+	if err != nil && err.IsError() {
+		return err
+	}
+
+	return nil
+}
+
+func (y *YamlConfig) parser() ParserError {
+	file, err := os.ReadFile(flagparser.ConfigFile())
+	if err != nil {
+		return NewParserError(err, err.Error())
+	}
+
+	err = yaml.Unmarshal(file, y)
+	if err != nil {
+		return NewParserError(err, err.Error())
+	}
+
+	return nil
+}

+ 301 - 0
src/flagparser/data.go

@@ -0,0 +1,301 @@
+package flagparser
+
+import (
+	"flag"
+	"fmt"
+	resource "github.com/SongZihuan/huan-proxy"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"io"
+	"os"
+	"reflect"
+	"strings"
+)
+
+const MinWaitSec = 0
+const MaxWaitSec = 60
+const OptionIdent = "  "
+const OptionPrefix = "--"
+const UseagePrefixWidth = 10
+
+type flagData struct {
+	flagReady  bool
+	flagSet    bool
+	flagParser bool
+
+	HelpData         bool
+	HelpName         string
+	HelpUseage       string
+	VersionData      bool
+	VersionName      string
+	VersionUseage    string
+	LicenseData      bool
+	LicenseName      string
+	LicenseUseage    string
+	ReportData       bool
+	ReportName       string
+	ReportUseage     string
+	ConfigFileData   string
+	ConfigFileName   string
+	ConfigFileUseage string
+
+	Useage string
+}
+
+func initData() {
+	data = flagData{
+		flagReady:  false,
+		flagSet:    false,
+		flagParser: false,
+
+		HelpData:         false,
+		HelpName:         "help",
+		HelpUseage:       fmt.Sprintf("Show usage of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		VersionData:      false,
+		VersionName:      "version",
+		VersionUseage:    fmt.Sprintf("Show version of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		LicenseData:      false,
+		LicenseName:      "license",
+		LicenseUseage:    fmt.Sprintf("Show license of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		ReportData:       false,
+		ReportName:       "report",
+		ReportUseage:     fmt.Sprintf("Show how to report questions/errors of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		ConfigFileData:   "config.yaml",
+		ConfigFileName:   "config",
+		ConfigFileUseage: fmt.Sprintf("%s", "The location of the running configuration file of the backend service. The option is a string, the default value is config.yaml in the running directory."),
+		Useage:           "",
+	}
+
+	data.ready()
+}
+
+func (d *flagData) writeUseAge() {
+	if len(d.Useage) != 0 {
+		return
+	}
+
+	if d.isFlagSet() || d.isFlagParser() {
+		panic("flag is parser")
+	}
+
+	var result strings.Builder
+	result.WriteString(utils.FormatTextToWidth(fmt.Sprintf("Usage of %s:", utils.GetArgs0Name()), utils.NormalConsoleWidth))
+	result.WriteString("\n")
+
+	val := reflect.ValueOf(*d)
+	typ := val.Type()
+
+	for i := 0; i < val.NumField(); i++ {
+		field := typ.Field(i)
+
+		if !strings.HasSuffix(field.Name, "Data") {
+			continue
+		}
+
+		option := field.Name[:len(field.Name)-4]
+		optionName, ok := val.FieldByName(option + "Name").Interface().(string)
+		if !ok {
+			panic("can not get option name")
+		}
+
+		optionUseage := val.FieldByName(option + "Useage").Interface().(string)
+		if !ok {
+			panic("can not get option useage")
+		}
+
+		var title string
+		if field.Type.Kind() == reflect.Bool {
+			optionData, ok := val.FieldByName(option + "Data").Interface().(bool)
+			if !ok {
+				panic("can not get option data")
+			}
+
+			if optionData == true {
+				panic("bool option can not be true")
+			}
+
+			title1 := fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(optionName, utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			title2 := fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(optionName[0:1], utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			title = fmt.Sprintf("%s\n%s", title1, title2)
+		} else if field.Type.Kind() == reflect.String {
+			optionData, ok := val.FieldByName(option + "Data").Interface().(string)
+			if !ok {
+				panic("can not get option data")
+			}
+
+			title1 := fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s string, default: '%s'", optionName, optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			title2 := fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s string, default: '%s'", optionName[0:1], optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			title = fmt.Sprintf("%s\n%s", title1, title2)
+		} else if field.Type.Kind() == reflect.Uint {
+			optionData, ok := val.FieldByName(option + "Data").Interface().(uint)
+			if !ok {
+				panic("can not get option data")
+			}
+
+			title1 := fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s number, default: %d", optionName, optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			title2 := fmt.Sprintf("%s%s%s", OptionIdent, OptionPrefix, utils.FormatTextToWidth(fmt.Sprintf("%s number, default: %d", optionName[0:1], optionData), utils.NormalConsoleWidth-len(OptionIdent)-len(OptionPrefix)))
+			title = fmt.Sprintf("%s\n%s", title1, title2)
+		} else {
+			panic("error flag type")
+		}
+
+		result.WriteString(title)
+		result.WriteString("\n")
+
+		usegae := utils.FormatTextToWidthAndPrefix(optionUseage, UseagePrefixWidth, utils.NormalConsoleWidth)
+		result.WriteString(usegae)
+		result.WriteString("\n\n")
+	}
+
+	d.Useage = strings.TrimRight(result.String(), "\n")
+}
+
+func (d *flagData) setFlag() {
+	if d.isFlagSet() {
+		return
+	}
+
+	flag.BoolVar(&d.HelpData, data.HelpName, data.HelpData, data.HelpUseage)
+	flag.BoolVar(&d.HelpData, data.HelpName[0:1], data.HelpData, data.HelpUseage)
+
+	flag.BoolVar(&d.VersionData, data.VersionName, data.VersionData, data.VersionUseage)
+	flag.BoolVar(&d.VersionData, data.VersionName[0:1], data.VersionData, data.VersionUseage)
+
+	flag.BoolVar(&d.LicenseData, data.LicenseName, data.LicenseData, data.LicenseUseage)
+	flag.BoolVar(&d.LicenseData, data.LicenseName[0:1], data.LicenseData, data.LicenseUseage)
+
+	flag.BoolVar(&d.ReportData, data.ReportName, data.ReportData, data.ReportUseage)
+	flag.BoolVar(&d.ReportData, data.ReportName[0:1], data.ReportData, data.ReportUseage)
+
+	flag.StringVar(&d.ConfigFileData, data.ConfigFileName, data.ConfigFileData, data.ConfigFileUseage)
+	flag.StringVar(&d.ConfigFileData, data.ConfigFileName[0:1], data.ConfigFileData, data.ConfigFileUseage)
+
+	flag.Usage = func() {
+		_, _ = d.PrintUseage()
+	}
+	d.flagSet = true
+}
+
+func (d *flagData) parser() {
+	if d.flagParser {
+		return
+	}
+
+	if !d.isFlagSet() {
+		panic("flag not set")
+	}
+
+	flag.Parse()
+	d.flagParser = true
+}
+
+func (d *flagData) ready() {
+	if d.isReady() {
+		return
+	}
+
+	d.writeUseAge()
+	d.setFlag()
+	d.parser()
+	d.flagReady = true
+}
+
+func (d *flagData) isReady() bool {
+	return d.isFlagSet() && d.isFlagParser() && d.flagReady
+}
+
+func (d *flagData) isFlagSet() bool {
+	return d.flagSet
+}
+
+func (d *flagData) isFlagParser() bool {
+	return d.flagParser
+}
+
+func (d *flagData) Help() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.HelpData
+}
+
+func (d *flagData) FprintUseage(writer io.Writer) (int, error) {
+	return fmt.Fprintf(writer, "%s\n", d.Useage)
+}
+
+func (d *flagData) PrintUseage() (int, error) {
+	return d.FprintUseage(flag.CommandLine.Output())
+}
+
+func (d *flagData) Version() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.VersionData
+}
+
+func (d *flagData) FprintVersion(writer io.Writer) (int, error) {
+	version := utils.FormatTextToWidth(fmt.Sprintf("Version of %s: %s", utils.GetArgs0Name(), resource.Version), utils.NormalConsoleWidth)
+	return fmt.Fprintf(writer, "%s\n", version)
+}
+
+func (d *flagData) PrintVersion() (int, error) {
+	return d.FprintVersion(flag.CommandLine.Output())
+}
+
+func (d *flagData) FprintLicense(writer io.Writer) (int, error) {
+	title := utils.FormatTextToWidth(fmt.Sprintf("License of %s:", utils.GetArgs0Name()), utils.NormalConsoleWidth)
+	license := utils.FormatTextToWidth(resource.License, utils.NormalConsoleWidth)
+	return fmt.Fprintf(writer, "%s\n%s\n", title, license)
+}
+
+func (d *flagData) PrintLicense() (int, error) {
+	return d.FprintLicense(flag.CommandLine.Output())
+}
+
+func (d *flagData) FprintReport(writer io.Writer) (int, error) {
+	// 不需要title
+	report := utils.FormatTextToWidth(resource.Report, utils.NormalConsoleWidth)
+	return fmt.Fprintf(os.Stderr, "%s\n", report)
+}
+
+func (d *flagData) PrintReport() (int, error) {
+	return d.FprintReport(flag.CommandLine.Output())
+}
+
+func (d *flagData) FprintLF(writer io.Writer) (int, error) {
+	return fmt.Fprintf(os.Stderr, "\n")
+}
+
+func (d *flagData) PrintLF() (int, error) {
+	return d.FprintLF(flag.CommandLine.Output())
+}
+
+func (d *flagData) License() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.LicenseData
+}
+
+func (d *flagData) Report() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.ReportData
+}
+
+func (d *flagData) ConfigFile() string {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.ConfigFileData
+}
+
+func (d *flagData) SetOutput(writer io.Writer) {
+	flag.CommandLine.SetOutput(writer)
+}

+ 29 - 0
src/flagparser/error.go

@@ -0,0 +1,29 @@
+package flagparser
+
+import "fmt"
+
+type FlagError interface {
+	error
+	Error() string
+	Data() interface{}
+}
+
+type flagError struct {
+	msg  string
+	data interface{}
+}
+
+func NewFlagError(data interface{}, msg ...string) FlagError {
+	if len(msg) == 1 {
+		return &flagError{msg[0], data}
+	}
+	return &flagError{"flag error: " + fmt.Sprint(data), data}
+}
+
+func (e *flagError) Error() string {
+	return e.msg
+}
+
+func (e *flagError) Data() interface{} {
+	return e.data
+}

+ 82 - 0
src/flagparser/flag.go

@@ -0,0 +1,82 @@
+package flagparser
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"os"
+)
+
+var isReady = false
+
+func IsReady() bool {
+	return data.isReady() && isReady
+}
+
+var StopFlag = fmt.Errorf("stop")
+
+func InitFlag() (err error) {
+	if isReady {
+		return nil
+	}
+
+	defer func() {
+		if e := recover(); e != nil {
+			err = NewFlagError(e)
+			return
+		}
+	}()
+
+	initData()
+
+	SetOutput(os.Stdout)
+
+	var hasPrint = false
+
+	if Version() {
+		_, _ = PrintVersion()
+		hasPrint = true
+	}
+
+	if License() {
+		if hasPrint {
+			PrintLF()
+		}
+		_, _ = PrintLicense()
+		hasPrint = true
+	}
+
+	if Report() {
+		if hasPrint {
+			PrintLF()
+		}
+		_, _ = PrintReport()
+	}
+
+	if Help() {
+		if hasPrint {
+			PrintLF()
+		}
+		_, _ = PrintUseage()
+		hasPrint = true
+	}
+
+	if NotRunMode() {
+		return StopFlag
+	}
+
+	err = checkFlag()
+	if err != nil {
+		return err
+	}
+
+	isReady = true
+	return nil
+}
+
+func checkFlag() error {
+	if !utils.IsExists(ConfigFile()) {
+		return fmt.Errorf("config file not exists")
+	}
+
+	return nil
+}

+ 103 - 0
src/flagparser/getter.go

@@ -0,0 +1,103 @@
+package flagparser
+
+import (
+	"fmt"
+	"io"
+	"strings"
+)
+
+var data flagData
+
+func Help() bool {
+	return data.Help()
+}
+
+func FprintUseage(writer io.Writer) (int, error) {
+	return data.FprintUseage(writer)
+}
+
+func PrintUseage() (int, error) {
+	return data.PrintUseage()
+}
+
+func FprintVersion(writer io.Writer) (int, error) {
+	return data.FprintVersion(writer)
+}
+
+func PrintVersion() (int, error) {
+	return data.PrintVersion()
+}
+
+func FprintLicense(writer io.Writer) (int, error) {
+	return data.FprintLicense(writer)
+}
+
+func PrintLicense() (int, error) {
+	return data.PrintLicense()
+}
+
+func FprintReport(writer io.Writer) (int, error) {
+	return data.FprintReport(writer)
+}
+
+func PrintReport() (int, error) {
+	return data.PrintReport()
+}
+
+func FprintLF(writer io.Writer) (int, error) {
+	return data.FprintLF(writer)
+}
+
+func PrintLF() (int, error) {
+	return data.PrintLF()
+}
+
+func Version() bool {
+	return data.Version()
+}
+
+func License() bool {
+	return data.License()
+}
+
+func Report() bool {
+	return data.Report()
+}
+
+func NotRunMode() bool {
+	return Help() || Version() || License() || Report()
+}
+
+func NotRunModeOption() string {
+	if !NotRunMode() {
+		return ""
+	}
+
+	var result strings.Builder
+
+	if data.Help() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.HelpName))
+	}
+
+	if data.Version() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.VersionName))
+	}
+
+	if data.License() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.LicenseName))
+	}
+
+	if data.Report() {
+		result.WriteString(fmt.Sprintf("%s%s, ", OptionPrefix, data.ReportName))
+	}
+
+	return strings.TrimSuffix(result.String(), ", ")
+}
+
+func ConfigFile() string {
+	return data.ConfigFile()
+}
+
+func SetOutput(writer io.Writer) {
+	data.SetOutput(writer)
+}

+ 99 - 0
src/logger/export.go

@@ -0,0 +1,99 @@
+package logger
+
+func Executablef(format string, args ...interface{}) string {
+	if !IsReady() {
+		return ""
+	}
+	return globalLogger.Executablef(format, args...)
+}
+
+func Executable() string {
+	if !IsReady() {
+		return ""
+	}
+	return globalLogger.Executable()
+}
+
+func Tagf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.TagSkipf(1, format, args...)
+}
+
+func Debugf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Debugf(format, args...)
+}
+
+func Infof(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Infof(format, args...)
+}
+
+func Warnf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Warnf(format, args...)
+}
+
+func Errorf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Errorf(format, args...)
+}
+
+func Panicf(format string, args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Panicf(format, args...)
+}
+
+func Tag(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.TagSkip(1, args...)
+}
+
+func Debug(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Debug(args...)
+}
+
+func Info(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Info(args...)
+}
+
+func Warn(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Warn(args...)
+}
+
+func Error(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Error(args...)
+}
+
+func Panic(args ...interface{}) {
+	if !IsReady() {
+		return
+	}
+	globalLogger.Panic(args...)
+}

+ 201 - 0
src/logger/logger.go

@@ -0,0 +1,201 @@
+package logger
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"io"
+	"os"
+)
+
+type LoggerLevel string
+
+const (
+	LevelDebug LoggerLevel = "debug"
+	LevelInfo  LoggerLevel = "info"
+	LevelWarn  LoggerLevel = "warn"
+	LevelError LoggerLevel = "error"
+	LevelPanic LoggerLevel = "panic"
+	LevelNone  LoggerLevel = "none"
+)
+
+type loggerLevel int64
+
+const (
+	levelDebug loggerLevel = 1
+	levelInfo  loggerLevel = 2
+	levelWarn  loggerLevel = 3
+	levelError loggerLevel = 4
+	levelPanic loggerLevel = 5
+	levelNone  loggerLevel = 6
+)
+
+var levelMap = map[LoggerLevel]loggerLevel{
+	LevelDebug: levelDebug,
+	LevelInfo:  levelInfo,
+	LevelWarn:  levelWarn,
+	LevelError: levelError,
+	LevelPanic: levelPanic,
+	LevelNone:  levelNone,
+}
+
+type Logger struct {
+	level      LoggerLevel
+	logLevel   loggerLevel
+	warnWriter io.Writer
+	errWriter  io.Writer
+	args0      string
+	args0Name  string
+}
+
+var globalLogger *Logger = nil
+
+func InitLogger() error {
+	level := LoggerLevel(config.Config().Yaml.GlobalConfig.LogLevel)
+	logLevel, ok := levelMap[level]
+	if !ok {
+		return fmt.Errorf("invalid log level: %s", level)
+	}
+
+	logger := &Logger{
+		level:      level,
+		logLevel:   logLevel,
+		warnWriter: os.Stdout,
+		errWriter:  os.Stderr,
+		args0:      utils.GetArgs0(),
+		args0Name:  utils.GetArgs0Name(),
+	}
+
+	globalLogger = logger
+	return nil
+}
+
+func IsReady() bool {
+	return globalLogger != nil
+}
+
+func (l *Logger) Executablef(format string, args ...interface{}) string {
+	str := fmt.Sprintf(format, args...)
+	if str == "" {
+		_, _ = fmt.Fprintf(l.warnWriter, "executable: %s\n", l.args0)
+	} else {
+		_, _ = fmt.Fprintf(l.warnWriter, "executable[%s]: %s\n", str, l.args0)
+	}
+	return l.args0
+}
+
+func (l *Logger) Executable() string {
+	return l.Executablef("")
+}
+
+func (l *Logger) Tagf(format string, args ...interface{}) {
+	l.TagSkipf(1, format, args...)
+}
+
+func (l *Logger) TagSkipf(skip int, format string, args ...interface{}) {
+	funcName, file, _, line := utils.GetCallingFunctionInfo(skip + 1)
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: TAG %s %s %s:%d\n", l.args0Name, str, funcName, file, line)
+}
+
+func (l *Logger) Debugf(format string, args ...interface{}) {
+	if l.logLevel > levelDebug {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Infof(format string, args ...interface{}) {
+	if l.logLevel > levelInfo {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Warnf(format string, args ...interface{}) {
+	if l.logLevel > levelWarn {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Errorf(format string, args ...interface{}) {
+	if l.logLevel > levelError {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.errWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Panicf(format string, args ...interface{}) {
+	if l.logLevel > levelPanic {
+		return
+	}
+
+	str := fmt.Sprintf(format, args...)
+	_, _ = fmt.Fprintf(l.errWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Tag(args ...interface{}) {
+	l.TagSkip(1, args...)
+}
+
+func (l *Logger) TagSkip(skip int, args ...interface{}) {
+	funcName, file, _, line := utils.GetCallingFunctionInfo(skip + 1)
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: TAG %s %s %s:%d\n", l.args0Name, str, funcName, file, line)
+}
+
+func (l *Logger) Debug(args ...interface{}) {
+	if l.logLevel > levelDebug {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Info(args ...interface{}) {
+	if l.logLevel > levelInfo {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Warn(args ...interface{}) {
+	if l.logLevel > levelWarn {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.warnWriter, "%s: %\ns", l.args0Name, str)
+}
+
+func (l *Logger) Error(args ...interface{}) {
+	if l.logLevel > levelError {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.errWriter, "%s: %s\n", l.args0Name, str)
+}
+
+func (l *Logger) Panic(args ...interface{}) {
+	if l.logLevel > levelPanic {
+		return
+	}
+
+	str := fmt.Sprint(args...)
+	_, _ = fmt.Fprintf(l.errWriter, "%s: %s\n", l.args0Name, str)
+}

+ 80 - 0
src/mainfunc/v1.go

@@ -0,0 +1,80 @@
+package mainfunc
+
+import (
+	"errors"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"github.com/SongZihuan/huan-proxy/src/server"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+)
+
+func MainV1() int {
+	var err error
+
+	err = flagparser.InitFlag()
+	if errors.Is(err, flagparser.StopFlag) {
+		return 0
+	} else if err != nil {
+		return utils.ExitByError(err)
+	}
+
+	if !flagparser.IsReady() {
+		return utils.ExitByErrorMsg("flag parser unknown error")
+	}
+
+	utils.SayHellof("%s", "The backend service program starts normally, thank you.")
+	defer func() {
+		utils.SayGoodByef("%s", "The backend service program is offline/shutdown normally, thank you.")
+	}()
+
+	err = config.InitConfig()
+	if err != nil {
+		return utils.ExitByError(err)
+	}
+
+	if !config.IsReady() {
+		return utils.ExitByErrorMsg("config parser unknown error")
+	}
+
+	cfg := config.Config()
+
+	err = logger.InitLogger()
+	if err != nil {
+		return utils.ExitByError(err)
+	}
+
+	if !logger.IsReady() {
+		return utils.ExitByErrorMsg("logger unknown error")
+	}
+
+	logger.Executable()
+	logger.Infof("run mode: %s\n", cfg.Yaml.GlobalConfig.GetGinMode())
+
+	ser := server.NewServer()
+
+	serstop := make(chan bool)
+	sererror := make(chan error)
+
+	go func() {
+		err := ser.Run()
+		if errors.Is(err, server.ServerStop) {
+			serstop <- true
+		} else if err != nil {
+			sererror <- err
+		} else {
+			serstop <- false
+		}
+	}()
+
+	select {
+	case <-cfg.GetSignalChan():
+		break
+	case err := <-sererror:
+		return utils.ExitByError(err)
+	case <-serstop:
+		break
+	}
+
+	return 0
+}

+ 152 - 0
src/server/api.go

@@ -0,0 +1,152 @@
+package server
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"net"
+	"net/http"
+	"net/url"
+	"strings"
+)
+
+func (s *HTTPServer) apiServer(index int, rule *config.ProxyConfig, w http.ResponseWriter, r *http.Request) {
+	proxy := s.cfg.ProxyServer.Get(index)
+	if proxy == nil {
+		s.abortServerError(w)
+		return
+	}
+
+	targetURL, err := url.Parse(rule.Address)
+	if err != nil {
+		s.abortServerError(w)
+		return
+	}
+
+	s.processProxyHeader(r)
+
+	r.URL.Scheme = targetURL.Scheme
+	r.URL.Host = targetURL.Host
+	r.URL.Path = rule.PrefixPath + r.URL.Path
+
+	proxy.ServeHTTP(w, r) // 反向代理
+}
+
+func (s *HTTPServer) processProxyHeader(r *http.Request) {
+	if r.RemoteAddr == "" {
+		return
+	}
+
+	remoteIPStr, _, err := net.SplitHostPort(r.RemoteAddr)
+	if err != nil {
+		return
+	}
+
+	remoteIP := net.ParseIP(remoteIPStr)
+
+	var ProxyList, ForwardedList []string
+	var host, proto string
+
+	if r.Header.Get("Forwarded") != "" {
+		ProxyList, ForwardedList, host, proto = s.getProxyListForwarder(remoteIP, r)
+	} else if r.Header.Get("X-Forwarded-For") != "" {
+		ProxyList, ForwardedList, host, proto = s.getProxyListFromXForwardedFor(remoteIP, r)
+	} else {
+		host = r.Header.Get("X-Forwarded-Host")
+		proto = r.Header.Get("X-Forwarded-Proto")
+
+		if host == "" {
+			host = r.URL.Host
+		}
+
+		if proto == "" {
+			proto = r.URL.Scheme
+		}
+
+		ProxyList = append(make([]string, 0, 1), remoteIP.String())
+		ForwardedList = append(make([]string, 0, 1),
+			fmt.Sprintf("for=%s", remoteIP.String()),
+			fmt.Sprintf("host=%s", host),
+			fmt.Sprintf("proto=%s", proto))
+	}
+
+	r.Header.Set("Forwarded", strings.Join(ForwardedList, ","))
+	r.Header.Set("X-Forwarded-For", strings.Join(ProxyList, ","))
+	r.Header.Set("X-Forwarded-Host", host)
+	r.Header.Set("X-Forwarded-Proto", proto)
+
+}
+
+func (s *HTTPServer) getProxyListForwarder(remoteIP net.IP, r *http.Request) ([]string, []string, string, string) {
+	ForwardedList := strings.Split(r.Header.Get("Forwarded"), ",")
+	ProxyList := make([]string, 0, len(ForwardedList)+1)
+	NewForwardedList := make([]string, 0, len(ForwardedList)+1)
+
+	host := r.URL.Host
+	proto := r.URL.Scheme
+
+	for _, keyStr := range ForwardedList {
+		kv := strings.Split(strings.ReplaceAll(keyStr, " ", ""), "=")
+		if len(kv) != 2 {
+			continue
+		}
+
+		if kv[0] == "for" {
+			forIP := net.ParseIP(strings.TrimSpace(kv[1]))
+			if forIP != nil {
+				NewForwardedList = append(NewForwardedList, keyStr)
+				ProxyList = append(ProxyList, forIP.String())
+			} else if kv[1] == "_hidden" || kv[1] == "_secret" || kv[1] == "unknown" {
+				NewForwardedList = append(NewForwardedList, keyStr)
+			}
+		} else if kv[0] == "by" {
+			byIP := net.ParseIP(strings.TrimSpace(kv[1]))
+			if byIP != nil || kv[1] == "_hidden" || kv[1] == "_secret" || kv[1] == "unknown" {
+				NewForwardedList = append(NewForwardedList, keyStr)
+			}
+		} else if kv[0] == "host" {
+			host = kv[1]
+		} else if kv[0] == "proto" {
+			proto = kv[1]
+		}
+	}
+
+	ProxyList = append(ProxyList, remoteIP.String())
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("for=%s", remoteIP.String()))
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("host=%s", host))
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("proto=%s", proto))
+	return ProxyList, NewForwardedList, host, proto
+}
+
+func (s *HTTPServer) getProxyListFromXForwardedFor(remoteIP net.IP, r *http.Request) ([]string, []string, string, string) {
+	XFroWardedForList := strings.Split(r.Header.Get("X-Forwarded-For"), ",")
+	ProxyList := make([]string, 0, len(XFroWardedForList)+1)
+	NewForwardedList := make([]string, 0, len(XFroWardedForList)+1)
+
+	for _, forIPStr := range XFroWardedForList {
+		forIP := net.ParseIP(strings.TrimSpace(forIPStr))
+		if forIP != nil {
+			ProxyList = append(ProxyList, forIP.String())
+		}
+	}
+
+	host := r.Header.Get("X-Forwarded-Host")
+	proto := r.Header.Get("X-Forwarded-Proto")
+
+	if host == "" {
+		host = r.URL.Host
+	}
+
+	if proto == "" {
+		proto = r.URL.Scheme
+	}
+
+	ProxyList = append(ProxyList, remoteIP.String())
+
+	for _, ip := range ProxyList {
+		NewForwardedList = append(NewForwardedList, fmt.Sprintf("for=%s", ip))
+	}
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("host=%s", host))
+	NewForwardedList = append(NewForwardedList, fmt.Sprintf("proto=%s", proto))
+
+	return ProxyList, NewForwardedList, host, proto
+}

+ 32 - 0
src/server/cors.go

@@ -0,0 +1,32 @@
+package server
+
+import (
+	"fmt"
+	"net/http"
+)
+
+func (s *HTTPServer) corsHandler(w http.ResponseWriter, r *http.Request) bool {
+	if s.cfg.Yaml.Http.Cors.Disable() {
+		if r.Method == http.MethodOptions {
+			s.abortNoContent(w)
+		}
+		return true
+	}
+
+	origin := r.Header.Get("Origin")
+	if origin == "" {
+		s.abortForbidden(w)
+		return false
+	}
+
+	if !s.cfg.CoreOrigin.InOriginList(origin) {
+		s.abortForbidden(w)
+		return false
+	}
+
+	w.Header().Set("Access-Control-Allow-Origin", origin)
+	w.Header().Set("Access-Control-Allow-Methods", "GET,OPTIONS")
+	w.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", s.cfg.Yaml.Http.Cors.MaxAgeSec))
+
+	return true
+}

+ 115 - 0
src/server/dir.go

@@ -0,0 +1,115 @@
+package server
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/gabriel-vasile/mimetype"
+	"net/http"
+	"os"
+	"path"
+	"strings"
+)
+
+const IndexMaxDeep = 5
+
+func (s *HTTPServer) dirServer(rule *config.ProxyConfig, w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		s.abortMethodNotAllowed(w)
+		return
+	}
+
+	dirBasePath := rule.Dir
+	filePath := ""
+
+	url := utils.ProcessPath(r.URL.Path)
+	if url == rule.BasePath {
+		filePath = dirBasePath
+	} else if strings.HasPrefix(url, rule.BasePath+"/") {
+		filePath = path.Join(dirBasePath, url[len(rule.BasePath+"/"):])
+	} else {
+		s.abortNotFound(w)
+		return
+	}
+
+	if filePath == "" {
+		s.abortNotFound(w)
+		return
+	}
+
+	if !utils.IsFile(filePath) {
+		filePath = s.getIndexFile(filePath)
+	}
+
+	if filePath == "" || !utils.IsFile(filePath) {
+		s.abortNotFound(w)
+		return
+	}
+
+	file, err := os.ReadFile(filePath)
+	if err != nil {
+		s.abortServerError(w)
+		return
+	}
+
+	mimeType := mimetype.Detect(file)
+	accept := r.Header.Get("Accept")
+	if !utils.AcceptMimeType(accept, mimeType.String()) {
+		s.abortNotAcceptable(w)
+		return
+	}
+
+	_, err = w.Write(file)
+	if err != nil {
+		s.abortServerError(w)
+		return
+	}
+	w.Header().Set("Content-Type", mimeType.String())
+}
+
+func (s *HTTPServer) getIndexFile(dir string) string {
+	return s._getIndexFile(dir, IndexMaxDeep)
+}
+
+func (s *HTTPServer) _getIndexFile(dir string, deep int) string {
+	if deep == 0 {
+		return ""
+	}
+
+	if !utils.IsDir(dir) {
+		return ""
+	}
+
+	lst, err := os.ReadDir(dir)
+	if err != nil {
+		return ""
+	}
+
+	var nextDir os.DirEntry = nil
+	var indexHTML os.DirEntry = nil
+	var indexXML os.DirEntry = nil
+	var index os.DirEntry = nil
+
+	for _, i := range lst {
+		if i.IsDir() {
+			nextDir = i
+		} else if i.Name() == "index.html" {
+			indexHTML = i
+		} else if i.Name() == "index.xml" {
+			indexXML = i
+		} else if strings.HasPrefix(i.Name(), "index.") {
+			index = i
+		}
+	}
+
+	if indexHTML != nil {
+		return path.Join(dir, indexHTML.Name())
+	} else if indexXML != nil {
+		return path.Join(dir, indexXML.Name())
+	} else if index != nil {
+		return path.Join(dir, index.Name())
+	} else if nextDir != nil {
+		return s._getIndexFile(path.Join(dir, nextDir.Name()), deep-1)
+	} else {
+		return ""
+	}
+}

+ 36 - 0
src/server/file.go

@@ -0,0 +1,36 @@
+package server
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"github.com/gabriel-vasile/mimetype"
+	"net/http"
+	"os"
+)
+
+func (s *HTTPServer) fileServer(rule *config.ProxyConfig, w http.ResponseWriter, r *http.Request) {
+	if r.Method != http.MethodGet {
+		s.abortMethodNotAllowed(w)
+		return
+	}
+
+	file, err := os.ReadFile(rule.File)
+	if err != nil {
+		s.abortServerError(w)
+		return
+	}
+
+	mimeType := mimetype.Detect(file)
+	accept := r.Header.Get("Accept")
+	if !utils.AcceptMimeType(accept, mimeType.String()) {
+		s.abortNotAcceptable(w)
+		return
+	}
+
+	_, err = w.Write(file)
+	if err != nil {
+		s.abortServerError(w)
+		return
+	}
+	w.Header().Set("Content-Type", mimeType.String())
+}

+ 49 - 0
src/server/proxytrust.go

@@ -0,0 +1,49 @@
+package server
+
+import (
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net"
+	"net/http"
+)
+
+func (s *HTTPServer) checkProxyTrust(w http.ResponseWriter, r *http.Request) bool {
+	if !s.cfg.Yaml.Http.RemoteTrust.Enable() {
+		return true
+	}
+
+	if r.RemoteAddr == "" {
+		s.abortForbidden(w)
+		return false
+	}
+
+	remoteIPStr, _, err := net.SplitHostPort(r.RemoteAddr)
+	if err != nil {
+		s.abortForbidden(w)
+		return false
+	}
+
+	remoteIP := net.ParseIP(remoteIPStr)
+
+	trust := s.cfg.Yaml.Http.RemoteTrust.TrustedIPs
+
+	for _, t := range trust {
+		if utils.ValidIPv4(t) || utils.ValidIPv6(t) {
+			trustIP := net.ParseIP(t)
+			if trustIP == nil {
+				continue
+			} else if trustIP.Equal(remoteIP) {
+				return true
+			}
+		} else if utils.IsValidIPv4CIDR(t) || utils.IsValidIPv6CIDR(t) {
+			_, trustCIDR, err := net.ParseCIDR(t)
+			if err != nil || trustCIDR == nil {
+				continue
+			} else if trustCIDR.Contains(remoteIP) {
+				return true
+			}
+		}
+	}
+
+	s.abortForbidden(w)
+	return false
+}

+ 40 - 0
src/server/respose.go

@@ -0,0 +1,40 @@
+package server
+
+import (
+	resource "github.com/SongZihuan/huan-proxy"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net/http"
+)
+
+func (s *HTTPServer) writeHuanProxyHeader(w http.ResponseWriter) {
+	w.Header().Set("HuanProxy", utils.StringToOnlyPrint(resource.Version))
+}
+
+func (s *HTTPServer) abortForbidden(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusForbidden)
+	_, _ = w.Write([]byte("Forbidden"))
+}
+
+func (s *HTTPServer) abortNotFound(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusNotFound)
+	_, _ = w.Write([]byte("NotFound"))
+}
+
+func (s *HTTPServer) abortNotAcceptable(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusNotAcceptable)
+	_, _ = w.Write([]byte("NotAcceptable"))
+}
+
+func (s *HTTPServer) abortMethodNotAllowed(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusMethodNotAllowed)
+	_, _ = w.Write([]byte("MethodNotAllowed"))
+}
+
+func (s *HTTPServer) abortServerError(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusInternalServerError)
+	_, _ = w.Write([]byte("ServerError"))
+}
+
+func (s *HTTPServer) abortNoContent(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusNoContent)
+}

+ 88 - 0
src/server/server.go

@@ -0,0 +1,88 @@
+package server
+
+import (
+	"errors"
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/config"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"github.com/SongZihuan/huan-proxy/src/utils"
+	"net/http"
+	"strings"
+)
+
+var ServerStop = fmt.Errorf("server stop")
+
+type HTTPServer struct {
+	address string
+	cfg     *config.ConfigStruct
+}
+
+func NewServer() *HTTPServer {
+	if !flagparser.IsReady() || !config.IsReady() {
+		panic("not ready")
+	}
+
+	return &HTTPServer{
+		address: config.Config().Yaml.Http.Address,
+		cfg:     config.Config(),
+	}
+}
+
+func (s *HTTPServer) Run() error {
+	err := s.run()
+	if errors.Is(err, http.ErrServerClosed) {
+		return ServerStop
+	} else if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *HTTPServer) run() error {
+	logger.Infof("start server in %s", s.address)
+	return http.ListenAndServe(s.address, s)
+}
+
+func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	s.writeHuanProxyHeader(w)
+	if !s.checkProxyTrust(w, r) {
+		return
+	}
+
+	func() {
+		for index, rule := range s.cfg.Yaml.Rules.Rules {
+			if rule.Type == config.ProxyTypeFile {
+				url := utils.ProcessPath(r.URL.Path)
+				logger.Tagf("A [%s] [%s]", url, rule.BasePath)
+				if url == rule.BasePath {
+					if s.corsHandler(w, r) {
+						s.fileServer(rule, w, r)
+					}
+					return
+				}
+			} else if rule.Type == config.ProxyTypeFile {
+				if r.Method == http.MethodGet {
+					urlpath := utils.ProcessPath(r.URL.Path)
+					if urlpath == rule.BasePath || strings.HasPrefix(urlpath, rule.BasePath+"/") {
+						if s.corsHandler(w, r) {
+							s.dirServer(rule, w, r)
+						}
+						return
+					}
+				}
+			} else if rule.File == config.ProxyTypeAPI {
+				urlpath := utils.ProcessPath(r.URL.Path)
+				if urlpath == rule.BasePath || strings.HasPrefix(urlpath, rule.BasePath+"/") {
+					s.apiServer(index, rule, w, r)
+					return
+				}
+			} else {
+				s.abortNotFound(w)
+			}
+		}
+
+		s.abortNotFound(w)
+	}()
+}

+ 17 - 0
src/utils/cidr.go

@@ -0,0 +1,17 @@
+package utils
+
+import (
+	"net"
+	"strings"
+)
+
+func IsValidIPv4CIDR(cidr string) bool {
+	_, _, err := net.ParseCIDR(cidr)
+	return err == nil && !strings.Contains(cidr, ":")
+}
+
+// IsValidIPv6CIDR checks if the given string is a valid IPv6 CIDR notation.
+func IsValidIPv6CIDR(cidr string) bool {
+	_, _, err := net.ParseCIDR(cidr)
+	return err == nil && strings.Contains(cidr, ":")
+}

+ 53 - 0
src/utils/cmd.go

@@ -0,0 +1,53 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+var _args0 = ""
+
+func init() {
+	var err error
+	if len(os.Args) > 0 {
+		_args0, err = os.Executable()
+		if err != nil {
+			_args0 = os.Args[0]
+		}
+	}
+
+	if _args0 == "" {
+		panic("args was empty")
+	}
+}
+
+func GetArgs0() string {
+	return _args0
+}
+
+func GetArgs0Name() string {
+	return filepath.Base(_args0)
+}
+
+func SayHellof(format string, args ...interface{}) {
+	var msg string
+	if len(format) == 0 && len(args) == 0 {
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), "Normal startup, thank you.")
+	} else {
+		str := fmt.Sprintf(format, args...)
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), str)
+	}
+	fmt.Println(FormatTextToWidth(msg, NormalConsoleWidth))
+}
+
+func SayGoodByef(format string, args ...interface{}) {
+	var msg string
+	if len(format) == 0 && len(args) == 0 {
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), "Normal shutdown, thank you.")
+	} else {
+		str := fmt.Sprintf(format, args...)
+		msg = fmt.Sprintf("%s: %s", GetArgs0Name(), str)
+	}
+	fmt.Println(FormatTextToWidth(msg, NormalConsoleWidth))
+}

+ 45 - 0
src/utils/exit.go

@@ -0,0 +1,45 @@
+package utils
+
+import (
+	"os"
+)
+
+func ExitByError(err error, code ...int) int {
+	if err == nil {
+		return ExitByErrorMsg("")
+	} else {
+		return ExitByErrorMsg(err.Error(), code...)
+	}
+}
+
+func ExitByErrorMsg(msg string, code ...int) int {
+	if len(msg) == 0 {
+		msg = "exit: unknown error"
+	}
+
+	return ErrorExit(msg, code...)
+}
+
+func ErrorExit(msg string, code ...int) int {
+	if len(msg) == 0 {
+		SayGoodByef("%s", "Encountered an error, abnormal offline/shutdown.")
+	} else {
+		SayGoodByef("Encountered an error, abnormal offline/shutdown: %s\n", msg)
+	}
+
+	if len(code) == 1 && code[0] != 0 {
+		return Exit(code[0])
+	} else {
+		return Exit(1)
+	}
+}
+
+func Exit(code ...int) int {
+	if len(code) == 1 {
+		os.Exit(code[0])
+		return code[0]
+	} else {
+		os.Exit(0)
+		return 0
+	}
+}

+ 40 - 0
src/utils/file.go

@@ -0,0 +1,40 @@
+package utils
+
+import (
+	"errors"
+	"os"
+)
+
+func IsExists(path string) bool {
+	_, err := os.Stat(path)
+	if err != nil && errors.Is(err, os.ErrNotExist) {
+		return false
+	}
+	return true
+}
+
+func IsDir(path string) bool {
+	s, err := os.Stat(path)
+	if err != nil {
+		return false
+	}
+
+	return s.IsDir()
+}
+
+func IsFile(path string) bool {
+	s, err := os.Stat(path)
+	if err != nil {
+		return false
+	}
+
+	return !s.IsDir()
+}
+
+func MakeDir(path string) error {
+	if IsExists(path) && IsDir(path) {
+		return nil
+	}
+
+	return os.MkdirAll(path, os.ModePerm)
+}

+ 10 - 0
src/utils/hash.go

@@ -0,0 +1,10 @@
+package utils
+
+import (
+	"crypto/sha256"
+	"fmt"
+)
+
+func SHA256(data []byte) string {
+	return fmt.Sprintf("%x", sha256.Sum256(data))
+}

+ 26 - 0
src/utils/http.go

@@ -0,0 +1,26 @@
+package utils
+
+import (
+	"net/http"
+)
+
+var statusOK = []int{
+	http.StatusOK,
+	http.StatusCreated,
+	http.StatusAccepted,
+	http.StatusNonAuthoritativeInfo,
+	http.StatusNoContent,
+	http.StatusResetContent,
+	http.StatusPartialContent,
+	http.StatusMultiStatus,
+	http.StatusAlreadyReported,
+}
+
+func HttpStatusOK(status int) bool {
+	for _, s := range statusOK {
+		if status == s {
+			return true
+		}
+	}
+	return false
+}

+ 16 - 0
src/utils/ip.go

@@ -0,0 +1,16 @@
+package utils
+
+import "net"
+
+func ValidIPv4(ipString string) bool {
+	ip := net.ParseIP(ipString)
+	if ip == nil || ip.To4() == nil {
+		return false
+	}
+	return true
+}
+
+func ValidIPv6(ipString string) bool {
+	ip := net.ParseIP(ipString)
+	return ip != nil && ip.To4() == nil
+}

+ 34 - 0
src/utils/mimetype.go

@@ -0,0 +1,34 @@
+package utils
+
+import (
+	"fmt"
+	"strings"
+)
+
+func AcceptMimeType(mimeType string, accept string) bool {
+	accept = StringToOnlyPrint(accept)
+	accept = strings.ToLower(accept)
+
+	mimeType = StringToOnlyPrint(mimeType)
+	mimeType = strings.ToLower(mimeType)
+
+	mimeTypeSplit := strings.Split(mimeType, "/")
+	if len(mimeTypeSplit) != 0 {
+		return true
+	}
+
+	mimeTypeFather := fmt.Sprintf("%s/*", mimeTypeSplit[0])
+
+	if accept == "" {
+		return true
+	}
+
+	acceptLst := strings.Split(accept, ",")
+	for _, a := range acceptLst {
+		if strings.Contains(a, mimeType) || strings.Contains(a, mimeTypeFather) || strings.Contains(a, "*/*") {
+			return true
+		}
+	}
+
+	return false
+}

+ 20 - 0
src/utils/rand.go

@@ -0,0 +1,20 @@
+package utils
+
+import (
+	"math/rand"
+	"time"
+)
+
+var r *rand.Rand = nil
+
+func init() {
+	r = rand.New(rand.NewSource(time.Now().UnixNano()))
+}
+
+func Rand() *rand.Rand {
+	if r == nil {
+		panic("nil Rand")
+	}
+
+	return r
+}

+ 37 - 0
src/utils/restart.go

@@ -0,0 +1,37 @@
+package utils
+
+import (
+	"os"
+)
+
+func Restart(newArgs ...string) (*os.Process, error) {
+	args := make([]string, 0, len(os.Args))
+	copy(args, os.Args)
+
+	if len(newArgs) != 0 {
+		args = append(args, newArgs...)
+	}
+
+	wd, err := os.Getwd()
+	if err != nil {
+		return nil, err
+	}
+
+	args0, err := os.Executable()
+	if err != nil {
+		return nil, err
+	}
+
+	attr := &os.ProcAttr{
+		Dir:   wd,                                         // 新进程的工作目录
+		Env:   os.Environ(),                               // 新进程的环境变量列表
+		Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, // 对应标准输入,标准输出和标准错误输出,若为nil,表示该进程启动时file是关闭的
+	}
+
+	p, err := os.StartProcess(args0, args[1:], attr)
+	if err != nil {
+		return nil, err
+	}
+
+	return p, nil
+}

+ 25 - 0
src/utils/runtime.go

@@ -0,0 +1,25 @@
+package utils
+
+import (
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+func GetCallingFunctionInfo(skip int) (string, string, string, int) {
+	pc, file, line, ok := runtime.Caller(skip + 1)
+	if !ok {
+		return "", "", "", 0
+	}
+
+	var funcName string
+	tmp := runtime.FuncForPC(pc).Name()
+	tmpLst := strings.Split(tmp, "/")
+	if len(tmpLst) == 0 {
+		funcName = tmp
+	} else {
+		funcName = tmpLst[len(tmpLst)-1]
+	}
+
+	return funcName, file, filepath.Base(file), line
+}

+ 14 - 0
src/utils/slice.go

@@ -0,0 +1,14 @@
+package utils
+
+func Filter[E any](src []E, callback func(E) bool) []E {
+	res := make([]E, 0, len(src))
+
+	for i := 0; i < len(src); i++ {
+		srci := src[i]
+		if callback(srci) {
+			res = append(res, srci)
+		}
+	}
+
+	return res
+}

+ 141 - 0
src/utils/string.go

@@ -0,0 +1,141 @@
+package utils
+
+import (
+	"database/sql"
+	"regexp"
+	"strings"
+	"unicode"
+)
+
+const BASE_CHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+func RandStr(length int) string {
+	bytes := []byte(BASE_CHAR)
+
+	var result []byte
+	for i := 0; i < length; i++ {
+		result = append(result, bytes[Rand().Intn(len(bytes))])
+	}
+
+	return string(result)
+}
+
+func InvalidPhone(phone string) bool {
+	pattern := `^1[3-9]\d{9}$`
+	matched, _ := regexp.MatchString(pattern, phone)
+	return matched
+}
+
+func IsValidEmail(email string) bool {
+	pattern := `^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`
+	matched, _ := regexp.MatchString(pattern, email)
+	return matched
+}
+
+func GetSQLNullString(s sql.NullString) string {
+	if s.Valid {
+		return s.String
+	}
+	return ""
+}
+
+const NormalConsoleWidth = 80
+
+func FormatTextToWidth(text string, width int) string {
+	return FormatTextToWidthAndPrefix(text, 0, width)
+}
+
+func FormatTextToWidthAndPrefix(text string, prefixWidth int, overallWidth int) string {
+	var result strings.Builder
+
+	width := overallWidth - prefixWidth
+	if width <= 0 {
+		panic("bad width")
+	}
+
+	text = strings.ReplaceAll(text, "\r\n", "\n")
+
+	for _, line := range strings.Split(text, "\n") {
+		result.WriteString(strings.Repeat(" ", prefixWidth))
+
+		if line == "" {
+			result.WriteString("\n")
+			continue
+		}
+
+		spaceCount := CountSpaceInStringPrefix(line) % width
+		newLineLength := 0
+		if spaceCount < 80 {
+			result.WriteString(strings.Repeat(" ", spaceCount))
+			newLineLength = spaceCount
+		}
+
+		for _, word := range strings.Fields(line) {
+			if newLineLength+len(word) >= width {
+				result.WriteString("\n")
+				result.WriteString(strings.Repeat(" ", prefixWidth))
+				newLineLength = 0
+			}
+
+			// 不是第一个词时,添加空格
+			if newLineLength != 0 {
+				result.WriteString(" ")
+				newLineLength += 1
+			}
+
+			result.WriteString(word)
+			newLineLength += len(word)
+		}
+
+		if newLineLength != 0 {
+			result.WriteString("\n")
+			newLineLength = 0
+		}
+	}
+
+	return strings.TrimRight(result.String(), "\n")
+}
+
+func CountSpaceInStringPrefix(str string) int {
+	var res int
+	for _, r := range str {
+		if r == ' ' {
+			res += 1
+		} else {
+			break
+		}
+	}
+
+	return res
+}
+
+func IsValidURLPath(path string) bool {
+	if path == "" {
+		return true
+	} else if path == "/" {
+		return false
+	}
+
+	pattern := `^\/[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;%=]+$`
+	matched, _ := regexp.MatchString(pattern, path)
+	return matched
+}
+
+func IsValidDomain(domain string) bool {
+	pattern := `^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$`
+	matched, _ := regexp.MatchString(pattern, domain)
+	return matched
+}
+
+func StringToOnlyPrint(str string) string {
+	runeLst := []rune(str)
+	res := make([]rune, 0, len(runeLst))
+
+	for _, r := range runeLst {
+		if unicode.IsPrint(r) {
+			res = append(res, r)
+		}
+	}
+
+	return string(res)
+}

+ 75 - 0
src/utils/stringbool.go

@@ -0,0 +1,75 @@
+package utils
+
+type StringBool string
+
+const enable StringBool = "enable"
+const disable StringBool = "disable"
+
+func (s *StringBool) check() bool {
+	return *s == enable || *s == disable
+}
+
+func (s *StringBool) is(v StringBool) bool {
+	if !s.check() {
+		panic("bad value")
+	}
+
+	return *s == v
+}
+
+func (s *StringBool) IsEnable() bool {
+	return s.is(enable)
+}
+
+func (s *StringBool) IsDisable() bool {
+	return s.is(disable)
+}
+
+func (s *StringBool) setDefault(v StringBool) {
+	if !s.check() {
+		*s = v
+	}
+}
+
+func (s *StringBool) SetDefaultEanble() {
+	s.setDefault(enable)
+}
+
+func (s *StringBool) SetDefaultDisable() {
+	s.setDefault(disable)
+}
+
+func (s *StringBool) ToString() string {
+	return s.ToStringDefaultEnable()
+}
+
+func (s *StringBool) ToStringDefaultEnable() string {
+	if s.check() {
+		return string(*s)
+	}
+
+	return string(enable)
+}
+
+func (s *StringBool) ToStringDefaultDisable() string {
+	if s.check() {
+		return string(*s)
+	}
+
+	return string(disable)
+}
+
+func (s *StringBool) ToBool(defaultVal ...bool) (res bool) {
+	defer func() {
+		if e := recover(); e != nil {
+			if len(defaultVal) == 1 {
+				res = defaultVal[0]
+			} else {
+				panic(e)
+			}
+		}
+	}()
+
+	res = s.IsEnable()
+	return
+}

+ 26 - 0
src/utils/time.go

@@ -0,0 +1,26 @@
+package utils
+
+import (
+	"database/sql"
+	"time"
+)
+
+func GetSQLNullTimeUnix(s sql.NullTime) int64 {
+	if s.Valid {
+		return s.Time.Unix()
+	}
+	return 0
+}
+
+func SqlTimeNow() sql.NullTime {
+	return sql.NullTime{
+		Valid: true,
+		Time:  time.Now(),
+	}
+}
+
+func SqlTimeNull() sql.NullTime {
+	return sql.NullTime{
+		Valid: false,
+	}
+}

+ 23 - 0
src/utils/url.go

@@ -0,0 +1,23 @@
+package utils
+
+import "strings"
+
+func ProcessPath(path string, defaultUrl ...string) string {
+	if len(path) == 0 && len(defaultUrl) == 1 {
+		path = defaultUrl[0]
+	}
+
+	path = strings.TrimSpace(path)
+
+	if !strings.HasPrefix(path, "/") {
+		path = "/" + path
+	}
+
+	path = strings.TrimRight(path, "/")
+
+	if !IsValidURLPath(path) {
+		panic("A serious error occurred in 'ProcessPath', and the generated Path does not conform to the 'IsValidURLPath' validation logic.")
+	}
+
+	return path
+}

+ 50 - 0
src/utils/utf8.go

@@ -0,0 +1,50 @@
+package utils
+
+import (
+	"fmt"
+	"unicode"
+	"unicode/utf8"
+)
+
+func IsUTF8(b []byte) bool {
+	return utf8.Valid(b)
+}
+
+func IsUTF8String(s string) bool {
+	return utf8.ValidString(s)
+}
+
+func HasUTF8BOM(data []byte) bool {
+	if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
+		return true
+	}
+	return false
+}
+
+func RemoveBOMIfExists(data []byte) []byte {
+	if HasUTF8BOM(data) {
+		return data[3:]
+	}
+	return data
+}
+
+func HasInvisibleByteSlice(data []byte) bool {
+	for i := 0; i < len(data); {
+		runeValue, size := utf8.DecodeRune(data[i:])
+		if !unicode.IsPrint(runeValue) {
+			return true
+		}
+		i += size
+	}
+	return false
+}
+
+func HasInvisibleString(str string) bool {
+	for _, runeValue := range str {
+		if !unicode.IsPrint(runeValue) {
+			fmt.Printf("%d\n", runeValue)
+			return true
+		}
+	}
+	return false
+}

+ 99 - 0
src/utils/version.go

@@ -0,0 +1,99 @@
+package utils
+
+import (
+	"fmt"
+	"runtime"
+	"strconv"
+	"strings"
+)
+
+func GetGoVersion() (int64, int64, int64, error) {
+	version := runtime.Version()
+	v := version
+
+	if len(v) < 2 {
+		return 0, 0, 0, fmt.Errorf("invalid version: %q", version)
+	}
+
+	if strings.HasPrefix(v, "go") {
+		v = v[2:]
+	}
+
+	if len(v) == 0 {
+		return 0, 0, 0, fmt.Errorf("invalid version: %q", version)
+	}
+
+	vLstStr := strings.Split(v, ".")
+	vLst := make([]int64, len(vLstStr))
+
+	for i, j := range vLstStr {
+		var err error
+		vLst[i], err = strconv.ParseInt(j, 10, 64)
+		if err != nil {
+			return 0, 0, 0, fmt.Errorf("invalid version: %q", version)
+		}
+	}
+
+	if len(vLst) == 0 {
+		return 0, 0, 0, fmt.Errorf("invalid version: %q", version)
+	} else if len(vLst) == 1 {
+		return vLst[0], 0, 0, nil
+	} else if len(vLst) == 2 {
+		return vLst[0], vLst[1], 0, nil
+	} else if len(vLst) == 3 {
+		return vLst[0], vLst[1], vLst[2], nil
+	} else {
+		return 0, 0, 0, fmt.Errorf("invalid version: %q", version)
+	}
+}
+
+func GetGoVersionMajor() (int64, error) {
+	major, _, _, err := GetGoVersion()
+	if err != nil {
+		return 0, err
+	}
+
+	return major, nil
+}
+
+func GetGoVersionMinor() (int64, error) {
+	_, minor, _, err := GetGoVersion()
+	if err != nil {
+		return 0, err
+	}
+
+	return minor, nil
+}
+
+func GetGoVersionPatch() (int64, error) {
+	_, _, patch, err := GetGoVersion()
+	if err != nil {
+		return 0, err
+	}
+
+	return patch, nil
+}
+
+func GetGoVersionMajorMust() int64 {
+	major, err := GetGoVersionMajor()
+	if err != nil {
+		panic(err)
+	}
+	return major
+}
+
+func GetGoVersionMinorMust() int64 {
+	minor, err := GetGoVersionMinor()
+	if err != nil {
+		panic(err)
+	}
+	return minor
+}
+
+func GetGoVersionPatchMust() int64 {
+	patch, err := GetGoVersionPatch()
+	if err != nil {
+		panic(err)
+	}
+	return patch
+}