├── .husky └── hooks │ ├── post-checkout │ ├── commit-msg │ └── pre-commit ├── main.go ├── pkg ├── net │ ├── https │ │ ├── embed.go │ │ └── certs │ │ │ ├── CA.crt │ │ │ ├── server.key │ │ │ └── server.pem │ ├── net.go │ └── http │ │ ├── reponsecode.go │ │ ├── response.go │ │ ├── negotiate.go │ │ ├── routegroup.go │ │ └── fs.go ├── env │ └── env.go ├── tools │ └── debug.go ├── configs │ ├── redis.go │ ├── database.go │ ├── app.go │ └── config.go ├── os │ └── file.go ├── log │ └── log.go └── io │ └── io.go ├── middlewares ├── gempty.go ├── gheader.go ├── gbasicauth.go ├── greferer.go ├── ggzip.go ├── gcors.go ├── gconfigs.go ├── glog.go └── glimiter.go ├── docker ├── alpine │ ├── docker-entrypoint.sh │ ├── Dockerfile │ └── .dockerignore └── debian │ ├── docker-entrypoint.sh │ ├── Dockerfile │ └── .dockerignore ├── .github ├── workflows │ ├── updater.yml │ ├── cache.yml │ ├── description.yml │ ├── goreleaser.yml │ ├── debian.yml │ └── alpine.yml └── buildkitd.toml ├── .vscode ├── settings.json ├── extensions.json ├── launch.json └── tasks.json ├── LICENSE ├── cmd └── server │ ├── version.go │ ├── env.go │ └── aserver.go ├── .commitlint.yaml ├── .air.toml ├── go.mod ├── .goreleaser.yaml ├── .gitignore ├── README.md └── go.sum /.husky/hooks/post-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # install 4 | go mod tidy -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/snowdreamtech/gserver/cmd/server" 5 | ) 6 | 7 | func main() { 8 | server.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/net/https/embed.go: -------------------------------------------------------------------------------- 1 | package https 2 | 3 | import ( 4 | "embed" 5 | ) 6 | 7 | //go:embed certs 8 | var certs embed.FS 9 | 10 | // GetTLSCerts GetTLSCerts 11 | func GetTLSCerts() embed.FS { 12 | return certs 13 | } 14 | -------------------------------------------------------------------------------- /.husky/hooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if ! type commitlint >/dev/null 2>/dev/null; then 4 | echo "" 5 | echo "commitlint could not be found" 6 | echo "try again after installing commitlint or add commitlint to PATH" 7 | echo "" 8 | exit 2; 9 | fi 10 | 11 | commitlint lint --message $1 12 | 13 | -------------------------------------------------------------------------------- /.husky/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # verify 4 | go mod verify 5 | 6 | # lint 7 | golint ./... 8 | 9 | # vet 10 | go vet ./... 11 | 12 | # imports 13 | goimports -l -w . 14 | 15 | # format 16 | go fmt ./... 17 | 18 | # test 19 | go test -v ./... 20 | 21 | # benchmark 22 | go test -v -benchmem -bench . ./... 23 | 24 | -------------------------------------------------------------------------------- /middlewares/gempty.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/snowdreamtech/gserver/pkg/tools" 6 | ) 7 | 8 | // Empty Empty 9 | func Empty() gin.HandlerFunc { 10 | tools.DebugPrintF("[INFO] Starting Middleware %s", "Empty") 11 | 12 | return func(c *gin.Context) { 13 | c.Next() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docker/alpine/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # exec commands 5 | if [ -n "$*" ]; then 6 | sh -c "$*" 7 | fi 8 | 9 | # keep the docker container running 10 | # https://github.com/docker/compose/issues/1926#issuecomment-422351028 11 | if [ "${KEEPALIVE}" -eq 1 ]; then 12 | trap : TERM INT 13 | tail -f /dev/null & 14 | wait 15 | # sleep infinity & wait 16 | fi 17 | -------------------------------------------------------------------------------- /docker/debian/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # exec commands 5 | if [ -n "$*" ]; then 6 | sh -c "$*" 7 | fi 8 | 9 | # keep the docker container running 10 | # https://github.com/docker/compose/issues/1926#issuecomment-422351028 11 | if [ "${KEEPALIVE}" -eq 1 ]; then 12 | trap : TERM INT 13 | tail -f /dev/null & 14 | wait 15 | # sleep infinity & wait 16 | fi 17 | -------------------------------------------------------------------------------- /middlewares/gheader.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/snowdreamtech/gserver/pkg/tools" 6 | ) 7 | 8 | // Header Header 9 | func Header() gin.HandlerFunc { 10 | tools.DebugPrintF("[INFO] Starting Middleware %s", "Header") 11 | 12 | return func(c *gin.Context) { 13 | c.Writer.Header().Set("server", "SnowdreamTech Static HTTP Server/0.1") 14 | 15 | c.Next() 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | var ( 4 | //ProjectName Project Name 5 | ProjectName string 6 | 7 | //Author Author 8 | Author string 9 | 10 | //OSArch System OSArch 11 | OSArch string 12 | 13 | //BuildTime Build Time 14 | BuildTime string 15 | 16 | //GoVersion Go Versione 17 | GoVersion string 18 | 19 | //GitTag Git Tag 20 | GitTag string 21 | 22 | //CommitHash Commit Hash 23 | CommitHash string 24 | 25 | //CommitHashFull Commit Hash 26 | CommitHashFull string 27 | 28 | //COPYRIGHT COPYRIGHT 29 | COPYRIGHT string 30 | 31 | //LICENSE LICENSE 32 | LICENSE string 33 | ) 34 | -------------------------------------------------------------------------------- /.github/workflows/updater.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Version Updater 2 | 3 | # Controls when the action will run. 4 | on: 5 | schedule: 6 | # Automatically run on every Day 7 | - cron: "02 19 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4.2.2 17 | with: 18 | # [Required] Access token with `workflow` scope. 19 | token: ${{ secrets.WORKFLOW_SECRET }} 20 | - name: Run GitHub Actions Version Updater 21 | uses: saadmk11/github-actions-version-updater@v0.8.1 22 | with: 23 | # [Required] Access token with `workflow` scope. 24 | token: ${{ secrets.WORKFLOW_SECRET }} 25 | -------------------------------------------------------------------------------- /middlewares/gbasicauth.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/snowdreamtech/gserver/pkg/configs" 8 | "github.com/snowdreamtech/gserver/pkg/tools" 9 | ) 10 | 11 | // Accounts user credential in basic auth. 12 | var accounts = gin.Accounts{ 13 | "admin": "admin", 14 | } 15 | 16 | // BasicAuth BasicAuth 17 | // BasicAuth 18 | func BasicAuth() gin.HandlerFunc { 19 | tools.DebugPrintF("[INFO] Starting Middleware %s", "BasicAuth") 20 | 21 | app := configs.GetAppConfig() 22 | 23 | if !app.Basic || app.User == "" { 24 | return Empty() 25 | } 26 | 27 | arr := strings.SplitN(app.User, ":", 2) 28 | 29 | if arr == nil || len(arr) != 2 { 30 | return Empty() 31 | } 32 | 33 | accounts = gin.Accounts{ 34 | arr[0]: arr[1], 35 | } 36 | 37 | return gin.BasicAuth(accounts) 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.env.windows": { 3 | "GOPROXY": "https://proxy.golang.com.cn,direct" 4 | }, 5 | "terminal.integrated.env.linux": { 6 | "GOPROXY": "https://proxy.golang.com.cn,direct" 7 | }, 8 | "terminal.integrated.env.osx": { 9 | "GOPROXY": "https://proxy.golang.com.cn,direct" 10 | }, 11 | "go.lintOnSave": "workspace", 12 | "go.vetOnSave": "workspace", 13 | "go.buildTags": "", 14 | "go.buildFlags": [], 15 | "go.lintTool": "golint", 16 | "go.lintFlags": [], 17 | "go.vetFlags": [], 18 | "go.testOnSave": false, 19 | "go.coverOnSave": false, 20 | "editor.formatOnSave": true, 21 | "go.formatTool": "goimports", 22 | "go.formatFlags": [], 23 | "go.goroot": "", 24 | "go.gopath": "", 25 | "go.inferGopath": true, 26 | "go.testFlags": [ 27 | "-v" 28 | ], 29 | "editor.formatOnPaste": true 30 | } 31 | -------------------------------------------------------------------------------- /middlewares/greferer.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/snowdreamtech/gserver/pkg/configs" 9 | "github.com/snowdreamtech/gserver/pkg/tools" 10 | ) 11 | 12 | // Referer Referer 13 | func Referer() gin.HandlerFunc { 14 | tools.DebugPrintF("[INFO] Starting Middleware %s", "Referer") 15 | 16 | app := configs.GetAppConfig() 17 | 18 | if !app.RefererLimiter { 19 | return Empty() 20 | } 21 | 22 | return func(c *gin.Context) { 23 | referer := c.Request.Referer() 24 | 25 | if referer == "" || localhostRegex.MatchString(referer) { 26 | c.Next() 27 | 28 | return 29 | } 30 | 31 | if strings.HasPrefix(referer, "http://"+c.Request.Host) || strings.HasPrefix(referer, "https://"+c.Request.Host) { 32 | c.Next() 33 | 34 | return 35 | } 36 | 37 | c.AbortWithStatus(http.StatusForbidden) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/tools/debug.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | // DefaultGinWriter Log for gin app 13 | var DefaultGinWriter io.Writer = os.Stdout 14 | 15 | // DefaultAccessWriter Log for gin app 16 | var DefaultAccessWriter io.Writer = os.Stdout 17 | 18 | // DefaultErrorWriter Log for gin app 19 | var DefaultErrorWriter io.Writer = os.Stderr 20 | 21 | // DebugPrintF use it to print formatted logs 22 | func DebugPrintF(format string, values ...any) { 23 | if gin.IsDebugging() { 24 | if !strings.HasSuffix(format, "\n") { 25 | format += "\n" 26 | } 27 | fmt.Fprintf(DefaultGinWriter, "[GIN-debug] "+format, values...) 28 | } 29 | } 30 | 31 | // DebugPrint use it to print logs 32 | func DebugPrint(values ...any) { 33 | if gin.IsDebugging() { 34 | fmt.Fprint(DefaultGinWriter, "[GIN-debug] ", values) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/buildkitd.toml: -------------------------------------------------------------------------------- 1 | # debug enables additional debug logging 2 | debug = true 3 | # trace enables additional trace logging (very verbose, with potential performance impacts) 4 | trace = true 5 | 6 | 7 | [registry."docker.io"] 8 | mirrors = ["https://docker.sn0wdr1am.com"] 9 | 10 | [registry."quay.io"] 11 | mirrors = ["https://quay.sn0wdr1am.com"] 12 | 13 | [registry."gcr.io"] 14 | mirrors = ["https://gcr.sn0wdr1am.com"] 15 | 16 | [registry."k8s.gcr.io"] 17 | mirrors = ["https://k8s-gcr.sn0wdr1am.com"] 18 | 19 | [registry."k8s.io"] 20 | mirrors = ["https://k8s.sn0wdr1am.com"] 21 | 22 | [registry."ghcr.io"] 23 | mirrors = ["https://ghcr.sn0wdr1am.com"] 24 | 25 | [registry."cloudsmith.io"] 26 | mirrors = ["https://cloudsmith.sn0wdr1am.com"] 27 | 28 | [registry."ecr.aws"] 29 | mirrors = ["https://ecr.sn0wdr1am.com"] 30 | 31 | [worker.oci] 32 | max-parallelism = 3 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-docker", 4 | "golang.go", 5 | "ms-vscode.makefile-tools", 6 | "axetroy.vscode-whatchanged", 7 | "mhutchie.git-graph", 8 | "redhat.vscode-yaml", 9 | "be5invis.toml", 10 | "iceyer.toml-formatter", 11 | "davidanson.vscode-markdownlint", 12 | "shd101wyy.markdown-preview-enhanced", 13 | "ms-vscode-remote.remote-ssh", 14 | "ms-vscode-remote.remote-ssh-edit", 15 | "ms-vscode.remote-explorer", 16 | "timonwong.shellcheck", 17 | "foxundermoon.shell-format", 18 | "wmaurer.change-case", 19 | "ms-ceintl.vscode-language-pack-zh-hans", 20 | "ms-ceintl.vscode-language-pack-zh-hant", 21 | "github.vscode-github-actions", 22 | "ultram4rine.vscode-choosealicense", 23 | "minherz.copyright-inserter", 24 | "wdhongtw.gpg-indicator" 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [{ 7 | "name": "Debug", 8 | "type": "go", 9 | "request": "launch", 10 | "mode": "debug", 11 | "program": "${workspaceFolder}/main.go", 12 | "env": { 13 | "GIN_MODE": "debug", 14 | "PORT": "8080", 15 | }, 16 | "args": ["--enable-https","--basic","--user=admin:admin"], 17 | "showLog": true 18 | }, 19 | { 20 | "name": "Launch", 21 | "type": "go", 22 | "request": "launch", 23 | "mode": "debug", 24 | "program": "${workspaceFolder}/main.go", 25 | "env": { 26 | "GIN_MODE": "release", 27 | "PORT": "8080", 28 | }, 29 | "args": ["--enable-https"], 30 | "showLog": true 31 | }] 32 | } -------------------------------------------------------------------------------- /middlewares/ggzip.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/gin-contrib/gzip" 5 | "github.com/gin-gonic/gin" 6 | "github.com/snowdreamtech/gserver/pkg/configs" 7 | "github.com/snowdreamtech/gserver/pkg/tools" 8 | ) 9 | 10 | var excludedExtentions = []string{ 11 | ".png", ".gif", ".jpeg", ".jpg", ".bmp", ".webp", 12 | ".mp3", ".ogg", ".wav", ".wma", 13 | ".3gp", ".avi", ".flv", ".mkv", ".mov", ".mp4", ".rmvb", ".vob", ".webm", ".wmv", 14 | ".exe", ".msi", ".apk", ".pkg", ".pkg", ".dmg", ".ipa", ".deb", ".rpm", ".flatpak", ".snap", ".appimage", 15 | ".rar", ".zip", ".tar", ".gz", ".7z", ".xz", ".bz2", ".iso", ".jar", 16 | } 17 | 18 | // Gzip Gzip 19 | func Gzip() gin.HandlerFunc { 20 | tools.DebugPrintF("[INFO] Starting Middleware %s", "Gzip") 21 | 22 | app := configs.GetAppConfig() 23 | 24 | if !app.Gzip { 25 | return Empty() 26 | } 27 | 28 | return gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions(excludedExtentions)) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/configs/redis.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | // RedisConfig Redis Configg 6 | type RedisConfig struct { 7 | Host string `mapstructure:"host"` 8 | Port int `mapstructure:"port"` 9 | Password string `mapstructure:"password"` 10 | } 11 | 12 | var defaultRedisConfig = RedisConfig{ 13 | Host: "localhost", 14 | Port: 6379, 15 | Password: "", 16 | } 17 | 18 | // GetRedisConfigWithContext Get RedisConfig from context 19 | func GetRedisConfigWithContext(c *gin.Context) (redisConfig *RedisConfig) { 20 | value, exists := c.Get(ConfigKey) 21 | 22 | if !exists { 23 | return nil 24 | } 25 | 26 | confs, ok := value.(*Configs) 27 | 28 | if !ok { 29 | return nil 30 | } 31 | 32 | return &confs.Redis 33 | } 34 | 35 | // GetRedisConfig Get RedisConfig from context 36 | func GetRedisConfig() (redisConfig *RedisConfig) { 37 | if c == nil { 38 | return &defaultRedisConfig 39 | } 40 | 41 | return &c.Redis 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/cache.yml: -------------------------------------------------------------------------------- 1 | name: Github Cache Cleanup 2 | 3 | on: 4 | schedule: 5 | # Automatically run on every Day 6 | - cron: "02 16 * * 0" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | cleanup: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Cleanup 14 | run: | 15 | gh extension install actions/gh-actions-cache 16 | 17 | echo "Fetching list of cache key" 18 | cacheKeys=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 --sort created-at --order desc | cut -f 1 ) 19 | 20 | ## Setting this to not fail the workflow while deleting cache keys. 21 | set +e 22 | echo "Deleting caches..." 23 | for cacheKey in $cacheKeys 24 | do 25 | gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm 26 | done 27 | echo "Done" 28 | env: 29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | REPO: ${{ github.repository }} 31 | BRANCH: ${{ github.ref }} 32 | -------------------------------------------------------------------------------- /pkg/os/file.go: -------------------------------------------------------------------------------- 1 | package os 2 | 3 | import "os" 4 | 5 | // IsExists Check whether the file is Exist 6 | func IsExists(path string) bool { 7 | _, err := os.Stat(path) //os.Stat获取文件信息 8 | if err != nil { 9 | if os.IsExist(err) { 10 | return true 11 | } 12 | return false 13 | } 14 | return true 15 | } 16 | 17 | // IsDir Check whether the file is Dir 18 | func IsDir(path string) bool { 19 | s, err := os.Stat(path) 20 | if err != nil { 21 | return false 22 | } 23 | return s.IsDir() 24 | } 25 | 26 | // IsExistDir Check whether the file is Exist Dir 27 | func IsExistDir(path string) bool { 28 | if !IsExists(path) { 29 | return false 30 | } 31 | 32 | s, err := os.Stat(path) 33 | if err != nil { 34 | return false 35 | } 36 | return s.IsDir() 37 | } 38 | 39 | // IsFile Check whether the file is File 40 | func IsFile(path string) bool { 41 | return !IsDir(path) 42 | } 43 | 44 | // IsExistFile Check whether the file is Exist File 45 | func IsExistFile(path string) bool { 46 | return IsExists(path) && !IsDir(path) 47 | } 48 | -------------------------------------------------------------------------------- /middlewares/gcors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "regexp" 5 | "time" 6 | 7 | "github.com/gin-contrib/cors" 8 | "github.com/gin-gonic/gin" 9 | "github.com/snowdreamtech/gserver/pkg/tools" 10 | ) 11 | 12 | const ( 13 | localhostRegexString = "[http://|https://]?[localhost|127\\.0\\.0\\.1]:?(6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{0,3}|0)?/?$" 14 | ) 15 | 16 | var ( 17 | localhostRegex = regexp.MustCompile(localhostRegexString) 18 | ) 19 | 20 | // Cors cors 21 | func Cors() gin.HandlerFunc { 22 | tools.DebugPrintF("[INFO] Starting Middleware %s", "Cors") 23 | 24 | return cors.New(cors.Config{ 25 | AllowOrigins: []string{}, 26 | AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}, 27 | AllowHeaders: []string{"Origin", "Content-Type", " Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"}, 28 | ExposeHeaders: []string{"Content-Length"}, 29 | AllowCredentials: true, 30 | AllowOriginFunc: func(origin string) bool { 31 | return localhostRegex.MatchString(origin) 32 | }, 33 | MaxAge: 24 * time.Hour, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2023-present SnowdreamTech Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /middlewares/gconfigs.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/snowdreamtech/gserver/pkg/configs" 6 | "github.com/snowdreamtech/gserver/pkg/tools" 7 | ) 8 | 9 | // Configs Configs with Viper 10 | // Viper is a complete configuration solution for Go applications including 12-Factor apps. 11 | // It is designed to work within an application, and can handle all types of configuration needs and formats. It supports: 12 | // setting defaults 13 | // reading from JSON, TOML, YAML, HCL, envfile and Java properties config files 14 | // live watching and re-reading of config files (optional) 15 | // reading from environment variables 16 | // reading from remote config systems (etcd or Consul), and watching changes 17 | // reading from command line flags 18 | // reading from buffer 19 | // setting explicit values 20 | // Viper can be thought of as a registry for all of your applications configuration needs. 21 | func Configs(conf *configs.Configs) gin.HandlerFunc { 22 | tools.DebugPrintF("[INFO] Starting Middleware %s", "Configs") 23 | 24 | return func(c *gin.Context) { 25 | c.Set(configs.ConfigKey, conf) 26 | c.Next() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cmd/server/version.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | 8 | "github.com/snowdreamtech/gserver/pkg/env" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func init() { 13 | rootCmd.AddCommand(versionCmd) 14 | } 15 | 16 | var versionCmd = &cobra.Command{ 17 | Use: "version", 18 | Short: "Print the version number of " + env.ProjectName, 19 | Long: "All software has versions. This is " + env.ProjectName + "'s", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | OSArch := runtime.GOOS + "/" + runtime.GOARCH 22 | BuildVersion := fmt.Sprintf("%s version %s-%s %s\n", env.ProjectName, env.GitTag, env.CommitHash, OSArch) 23 | CopyrightDetail := fmt.Sprintf("%s\n", env.COPYRIGHT) 24 | LicenseDetail := fmt.Sprintf("License: %s\n", env.LICENSE) 25 | AuthorDetail := fmt.Sprintf("Written by %s", env.Author) 26 | BuildDetail := fmt.Sprintf("Built at %s", env.BuildTime) 27 | 28 | var builder strings.Builder 29 | builder.WriteString(BuildVersion) 30 | builder.WriteString(CopyrightDetail) 31 | builder.WriteString(LicenseDetail) 32 | 33 | builder.WriteString("\n") 34 | builder.WriteString(AuthorDetail) 35 | builder.WriteString("\n") 36 | builder.WriteString(BuildDetail) 37 | 38 | fmt.Println(builder.String()) 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/description.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Description Updater 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - README.md 9 | - .github/workflows/description.yml 10 | 11 | jobs: 12 | dockerHubDescription: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4.2.2 17 | with: 18 | # [Required] Access token with `workflow` scope. 19 | token: ${{ secrets.WORKFLOW_SECRET }} 20 | - name: Docker Hub Description 21 | uses: christian-korneck/update-container-description-action@v1 22 | env: 23 | DOCKER_USER: ${{ secrets.DOCKER_HUB_USERNAME }} 24 | DOCKER_PASS: ${{ secrets.DOCKER_HUB_TOKEN }} 25 | with: 26 | destination_container_repo: snowdreamtech/gserver 27 | provider: dockerhub 28 | short_description: ${{ github.event.repository.description }} 29 | readme_file: "README.md" 30 | - name: Quay.io Description 31 | uses: christian-korneck/update-container-description-action@v1 32 | env: 33 | DOCKER_APIKEY: ${{ secrets.QUAY_API_TOKEN }} 34 | with: 35 | destination_container_repo: quay.io/snowdreamtech/gserver 36 | provider: quay 37 | readme_file: "README.md" 38 | -------------------------------------------------------------------------------- /pkg/net/net.go: -------------------------------------------------------------------------------- 1 | package net 2 | 3 | import ( 4 | "net" 5 | "strconv" 6 | 7 | "github.com/snowdreamtech/gserver/pkg/tools" 8 | ) 9 | 10 | // IsPortAvailable Is Port Available 11 | func IsPortAvailable(port string) bool { 12 | listener, err := net.Listen("tcp", ":"+port) 13 | 14 | if err == nil { 15 | listener.Close() 16 | return true 17 | } 18 | 19 | return false 20 | } 21 | 22 | // GetAvailablePort Get Available Port 23 | func GetAvailablePort(startPort int) string { 24 | port := "" 25 | 26 | // Get the free port from startPort 27 | for i := startPort; i < 65535; i++ { 28 | listener, err := net.Listen("tcp", ":"+strconv.Itoa(i)) 29 | 30 | if err == nil { 31 | listener.Close() 32 | port = strconv.Itoa(i) 33 | break 34 | } 35 | } 36 | 37 | return port 38 | } 39 | 40 | // GetAvailableIPS Get Available IP 41 | func GetAvailableIPS() []string { 42 | ips := []string{} 43 | 44 | // get list of available addresses 45 | addr, err := net.InterfaceAddrs() 46 | if err != nil { 47 | tools.DebugPrintF("[ERROR] " + err.Error()) 48 | return ips 49 | } 50 | 51 | for _, addr := range addr { 52 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 53 | // check if IPv4 or IPv6 is not nil 54 | if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { 55 | // print available addresses 56 | ips = append(ips, ipnet.IP.String()) 57 | } 58 | } 59 | } 60 | 61 | return ips 62 | } 63 | -------------------------------------------------------------------------------- /pkg/configs/database.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // DatabaseConfig Database Config 10 | type DatabaseConfig struct { 11 | Type string `mapstructure:"type"` 12 | DSN string `mapstructure:"dsn"` 13 | Host string `mapstructure:"host"` 14 | Port int `mapstructure:"port"` 15 | Dbname string `mapstructure:"dbname"` 16 | User string `mapstructure:"user"` 17 | Password string `mapstructure:"password"` 18 | TimeZone string `mapstructure:"timezone"` 19 | } 20 | 21 | var defaultDatabaseConfig = DatabaseConfig{ 22 | Type: "pg", 23 | Host: "localhost", 24 | Port: 5432, 25 | Dbname: "postgres", 26 | User: os.Getenv("DB_USER"), 27 | Password: os.Getenv("DB_PASSWORD"), 28 | TimeZone: "Asia/Shanghai", 29 | DSN: "", 30 | } 31 | 32 | // GetDatabaseConfigWithContext Get DatabaseConfig from context 33 | func GetDatabaseConfigWithContext(c *gin.Context) (databaseConfig *DatabaseConfig) { 34 | value, exists := c.Get(ConfigKey) 35 | 36 | if !exists { 37 | return nil 38 | } 39 | 40 | confs, ok := value.(*Configs) 41 | 42 | if !ok { 43 | return nil 44 | } 45 | 46 | return &confs.Database 47 | } 48 | 49 | // GetDatabaseConfig Get DatabaseConfig from context 50 | func GetDatabaseConfig() (databaseConfig *DatabaseConfig) { 51 | if c == nil { 52 | return &defaultDatabaseConfig 53 | } 54 | 55 | return &c.Database 56 | } 57 | -------------------------------------------------------------------------------- /.commitlint.yaml: -------------------------------------------------------------------------------- 1 | version: v0.10.1 2 | formatter: default 3 | rules: 4 | - header-min-length 5 | - header-max-length 6 | - body-max-line-length 7 | - footer-max-line-length 8 | - type-enum 9 | severity: 10 | default: error 11 | settings: 12 | body-max-length: 13 | argument: -1 14 | body-max-line-length: 15 | argument: 72 16 | body-min-length: 17 | argument: 0 18 | description-max-length: 19 | argument: -1 20 | description-min-length: 21 | argument: 0 22 | footer-enum: 23 | argument: [] 24 | footer-max-length: 25 | argument: -1 26 | footer-max-line-length: 27 | argument: 72 28 | footer-min-length: 29 | argument: 0 30 | footer-type-enum: 31 | argument: [] 32 | header-max-length: 33 | argument: 72 34 | header-min-length: 35 | argument: 10 36 | scope-charset: 37 | argument: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/, 38 | scope-enum: 39 | argument: [] 40 | flags: 41 | allow-empty: true 42 | scope-max-length: 43 | argument: -1 44 | scope-min-length: 45 | argument: 0 46 | type-charset: 47 | argument: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 48 | type-enum: 49 | argument: 50 | - feat 51 | - fix 52 | - docs 53 | - style 54 | - refactor 55 | - perf 56 | - test 57 | - build 58 | - ci 59 | - chore 60 | - revert 61 | type-max-length: 62 | argument: -1 63 | type-min-length: 64 | argument: 0 65 | -------------------------------------------------------------------------------- /middlewares/glog.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "gopkg.in/natefinch/lumberjack.v2" 11 | "github.com/snowdreamtech/gserver/pkg/configs" 12 | "github.com/snowdreamtech/gserver/pkg/tools" 13 | ) 14 | 15 | // LoggerWithFormatter instance a Logger middleware with the specified log format function. 16 | func LoggerWithFormatter() gin.HandlerFunc { 17 | tools.DebugPrintF("[INFO] Starting Middleware %s", "Logger") 18 | 19 | r := configs.GetAppConfig() 20 | logDir := r.LogDir 21 | 22 | // Set access.log 23 | accesslog := &lumberjack.Logger{ 24 | Filename: logDir + "/access.log", 25 | MaxSize: 500, // megabytes 26 | MaxBackups: 3, 27 | MaxAge: 28, //days 28 | Compress: true, // disabled by default 29 | } 30 | 31 | tools.DefaultAccessWriter = io.MultiWriter(accesslog, os.Stdout) 32 | 33 | // Set access.log middleware 34 | accessLogFormatter := func(param gin.LogFormatterParams) string { 35 | 36 | // your custom format 37 | return fmt.Sprintf("%s - [%s] %s %s %s %s %d %s \"%s\" %s \n", 38 | param.ClientIP, 39 | param.TimeStamp.Format(time.RFC1123), 40 | param.Request.Header.Get("X-Request-ID"), 41 | param.Method, 42 | param.Path, 43 | param.Request.Proto, 44 | param.StatusCode, 45 | param.Latency, 46 | param.Request.UserAgent(), 47 | param.ErrorMessage, 48 | ) 49 | } 50 | 51 | accessLogConfig := gin.LoggerConfig{ 52 | Formatter: accessLogFormatter, 53 | Output: tools.DefaultAccessWriter, 54 | } 55 | 56 | return gin.LoggerWithConfig(accessLogConfig) 57 | } 58 | -------------------------------------------------------------------------------- /cmd/server/env.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strings" 7 | 8 | "github.com/snowdreamtech/gserver/pkg/env" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func init() { 13 | rootCmd.AddCommand(envCmd) 14 | } 15 | 16 | var envCmd = &cobra.Command{ 17 | Use: "env", 18 | Short: "Print " + env.ProjectName + " version and environment info", 19 | Long: "All software has versions. This is " + env.ProjectName + "'s", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | ProjectName := fmt.Sprintf("ProjectName=%s\n", env.ProjectName) 22 | GOOS := fmt.Sprintf("GOOS=%s\n", runtime.GOOS) 23 | GOARCH := fmt.Sprintf("GOARCH=%s\n", runtime.GOARCH) 24 | GOVERSION := fmt.Sprintf("GOVERSION=%s\n", runtime.Version()) 25 | Author := fmt.Sprintf("Author=%s\n", env.Author) 26 | BuildTime := fmt.Sprintf("BuildTime=%s\n", env.BuildTime) 27 | GitTag := fmt.Sprintf("GitTag=%s\n", env.GitTag) 28 | CommitHash := fmt.Sprintf("CommitHash=%s\n", env.CommitHash) 29 | CommitHashFull := fmt.Sprintf("CommitHashFull=%s\n", env.CommitHashFull) 30 | COPYRIGHT := fmt.Sprintf("Copyright=%s\n", env.COPYRIGHT) 31 | LICENSE := fmt.Sprintf("LICENSE=%s\n", env.LICENSE) 32 | 33 | var builder strings.Builder 34 | builder.WriteString(ProjectName) 35 | builder.WriteString(Author) 36 | builder.WriteString(BuildTime) 37 | builder.WriteString(GitTag) 38 | builder.WriteString(CommitHash) 39 | builder.WriteString(CommitHashFull) 40 | builder.WriteString(GOOS) 41 | builder.WriteString(GOARCH) 42 | builder.WriteString(GOVERSION) 43 | builder.WriteString(COPYRIGHT) 44 | builder.WriteString(LICENSE) 45 | 46 | fmt.Println(builder.String()) 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./build/bin/gserver ." 11 | # Binary file yields from `cmd`. 12 | bin = "GIN_MODE=debug ./build/bin/gserver" 13 | # Customize binary. 14 | full_bin = "GIN_MODE=debug ./build/bin/gserver" 15 | # Watch these filename extensions. 16 | include_ext = ["go", "tpl", "tmpl", "html", "vue", "toml", "env", "json", "xml", "js", "css"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules", "bin", "release", "build"] 19 | # Watch these directories if you specified. 20 | include_dir = ["cmd", "configs", "middlewares", "pkg", "routers"] 21 | # Exclude files. 22 | exclude_file = [] 23 | # This log file places in your tmp_dir. 24 | log = "air.log" 25 | # It's not necessary to trigger build each time file changes if it's too frequent. 26 | delay = 1000 # ms 27 | # Stop running old binary when build errors occur. 28 | stop_on_error = true 29 | # Send Interrupt signal before killing process (windows does not support this feature) 30 | send_interrupt = false 31 | # Delay after sending Interrupt signal 32 | kill_delay = 500 # ms 33 | 34 | [log] 35 | # Show log time 36 | time = true 37 | 38 | [color] 39 | # Customize each part's color. If no color found, use the raw app log. 40 | build = "yellow" 41 | main = "magenta" 42 | runner = "green" 43 | watcher = "cyan" 44 | 45 | [misc] 46 | # Delete tmp directory on exit 47 | clean_on_exit = true 48 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions GoReleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Free Disk Space (Ubuntu) 16 | uses: jlumbroso/free-disk-space@v1.3.1 17 | with: 18 | # this might remove tools that are actually needed, 19 | # if set to "true" but frees about 6 GB 20 | tool-cache: false 21 | 22 | # all of these default to true, but feel free to set to 23 | # "false" if necessary for your workflow 24 | android: true 25 | dotnet: true 26 | haskell: true 27 | large-packages: true 28 | docker-images: false 29 | swap-storage: false 30 | - name: Checkout 31 | uses: actions/checkout@v4.2.2 32 | with: 33 | # [Required] Access token with `workflow` scope. 34 | token: ${{ secrets.WORKFLOW_SECRET }} 35 | fetch-depth: 0 36 | - name: Set up Go 37 | uses: actions/setup-go@v5.5.0 38 | - name: Run GoReleaser 39 | uses: goreleaser/goreleaser-action@v6.3.0 40 | if: startsWith(github.ref, 'refs/tags/') 41 | with: 42 | # either 'goreleaser' (default) or 'goreleaser-pro' 43 | distribution: goreleaser 44 | # 'latest', 'nightly', or a semver 45 | version: "~> v2" 46 | args: release --clean 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution 50 | # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} 51 | -------------------------------------------------------------------------------- /pkg/net/http/reponsecode.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | const ( 4 | //Success Success 5 | Success = "Success" 6 | 7 | //Failure Failure 8 | Failure = "Failure" 9 | 10 | //StatusUnauthorized Status Unauthorized 11 | StatusUnauthorized = "StatusUnauthorized" 12 | 13 | //TooManyRequests Too Many Requests 14 | TooManyRequests = "TooManyRequests" 15 | 16 | //InvalidParameter Invalid Parameter 17 | InvalidParameter = "InvalidParameter" 18 | 19 | //ParamError Param Error 20 | ParamError = "ParamError" 21 | 22 | //VerificationCodeExpired Verification Code Expired 23 | VerificationCodeExpired = "VerificationCodeExpired" 24 | 25 | //VerificationCodeError Verification Code Error 26 | VerificationCodeError = "VerificationCodeError" 27 | 28 | //VerificationCodeRequired Verification Code Required 29 | VerificationCodeRequired = "VerificationCodeRequired" 30 | 31 | //VerificationCodeLimit Verification Code Limit 32 | VerificationCodeLimit = "VerificationCodeLimit" 33 | 34 | //VerificationCodeGenerateError Verification Code Generate Error 35 | VerificationCodeGenerateError = "VerificationCodeGenerateError" 36 | 37 | //VerificationCodeWaiting Verification Code Waiting 38 | VerificationCodeWaiting = "VerificationCodeWaiting" 39 | 40 | //VerificationCodeWaitingAfter Verification Code Waiting After 41 | VerificationCodeWaitingAfter = "VerificationCodeWaitingAfter" 42 | 43 | //EmailAlreadyExists Email Already Exists 44 | EmailAlreadyExists = "EmailAlreadyExists" 45 | 46 | //EmailDoesNotExist Email Does Not Exist 47 | EmailDoesNotExist = "EmailDoesNotExist" 48 | 49 | //MobileAlreadyExists Mobile Already Exists 50 | MobileAlreadyExists = "MobileAlreadyExists" 51 | 52 | //MobileDoesNotExist Mobile Does Not Exist 53 | MobileDoesNotExist = "MobileDoesNotExist" 54 | ) 55 | -------------------------------------------------------------------------------- /pkg/net/https/certs/CA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIExzCCAy+gAwIBAgIQBJ3CL4o3WuwzRIaCPEtRnDANBgkqhkiG9w0BAQsFADCB 3 | pzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT4wPAYDVQQLDDVERVNL 4 | VE9QLUNQTjIxVk9cc25vd2RyZWFtQERFU0tUT1AtQ1BOMjFWTyAoc25vd2RyZWFt 5 | KTFFMEMGA1UEAww8bWtjZXJ0IERFU0tUT1AtQ1BOMjFWT1xzbm93ZHJlYW1AREVT 6 | S1RPUC1DUE4yMVZPIChzbm93ZHJlYW0pMB4XDTIzMDUxODAyMzU0M1oXDTI1MDgx 7 | ODAyMzU0M1owaTEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmlj 8 | YXRlMT4wPAYDVQQLDDVERVNLVE9QLUNQTjIxVk9cc25vd2RyZWFtQERFU0tUT1At 9 | Q1BOMjFWTyAoc25vd2RyZWFtKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 10 | ggEBAMAY1k8trAfHifpfl+YSzbtRNuVVyCR/rQVWTqFsrx+cGv+mH/46ZiNTqXuM 11 | Ch9ZlvMxu70mXUeawuM1L3Hq3HT5pw3tnDVdPfd/m7cXJyOyo7+1T4ByQ9HqxCQm 12 | YW5XJTYdusebWPQDt0hnP2KK3oS5LQgGK4wVtxQyCWh0slIyafKeQRaUimVEg1V0 13 | +9yK/hjCCraKbHe0yfooaLwpw3zD9+sWVpCkcmT21K6ALoiiF0WKFs28NCi/ffKH 14 | ZR5aMsPbCB5gzaBD0EnuES+DNKBk/e1EEf9IDnlxZMBaQghCcGc64JF2BLWeRWDG 15 | OXRtypmeHuuDNY2IBUYiVKQJ0FsCAwEAAaOBqzCBqDAOBgNVHQ8BAf8EBAMCBaAw 16 | EwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAU06X2xiEPhqyuzW6WtjuC 17 | viIkFJcwYAYDVR0RBFkwV4ILZXhhbXBsZS5jb22CDSouZXhhbXBsZS5jb22CDGV4 18 | YW1wbGUudGVzdIIJbG9jYWxob3N0gggtaW5zdGFsbIcEfwAAAYcQAAAAAAAAAAAA 19 | AAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAYEAFih0RaaE9FZsix/TAyYsZg2XDSwT 20 | 9PYBFn+LRWV3QRobKNE2ZQVXSVvGzKpbPLL8uWaJ+YtH/RBXSrI/kT5Mub+PCK2U 21 | J2oDE/HPFTE8o/hRuRhuJPkZye+q/km8I8GGc3gNj8p/4Xvh+OZPNreSqGHoJDwE 22 | dluJZzU8xH7qQY/mP0/XdlDQdKJgzBFu0BnzmRQPI9G/D0fZ+6U0CvDDOEW/RS5l 23 | BgZigpvzNgkgtYtaiqxxX36AqXbIJUQVpZBjKMioSc0Q8F0bo1aA39SeLUDCy7Oh 24 | 9/fNQbXhHNAOvidyupeLVxmM0CbPNi45V5JRN2tKWig4Rd2qlRWD/UQajqjWjsbi 25 | HTvV31ISrsLZKYkJdl7zjs6e648z2Y3KYDb5Sf1Msf3hVYJZYLBm6M1fLXEwmk9e 26 | mZO618PeSfrVXo203lC+2BWvuGBDZTZAQhr3RBIm7MolyUwCo1ArJvhnaqOqVqw4 27 | csN4iiBRtUAuyyTVgZZUXT5dpaAfuxbhXLRa 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /pkg/net/https/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDAGNZPLawHx4n6 3 | X5fmEs27UTblVcgkf60FVk6hbK8fnBr/ph/+OmYjU6l7jAofWZbzMbu9Jl1HmsLj 4 | NS9x6tx0+acN7Zw1XT33f5u3FycjsqO/tU+AckPR6sQkJmFuVyU2HbrHm1j0A7dI 5 | Zz9iit6EuS0IBiuMFbcUMglodLJSMmnynkEWlIplRINVdPvciv4Ywgq2imx3tMn6 6 | KGi8KcN8w/frFlaQpHJk9tSugC6IohdFihbNvDQov33yh2UeWjLD2wgeYM2gQ9BJ 7 | 7hEvgzSgZP3tRBH/SA55cWTAWkIIQnBnOuCRdgS1nkVgxjl0bcqZnh7rgzWNiAVG 8 | IlSkCdBbAgMBAAECggEBAJYQkRLHrhVhtxNFrEdKtzc3Cbh8m96Pcx2xP7nxSplZ 9 | /Olr4KBdBL9iMwtV6e51sWuIlY39M2+locuXF9pZTCGBihRiY954WdlH8eBFEQld 10 | G3i+7j18hiitys4mwYRORb6R2QDoBcSSRpMkPSsBKzXp5xjWoRxNNnlrYVgdmdSF 11 | O+yHuWHnWt8QXZ5YWAE9Muct+/C5ea8jgBu5m6wqUG+UzyqCp7xPFUfKdWeQTxK9 12 | rx5gt/bLWxXWWS+BQogcQQ1olPCW6BQHyML5ZQSSTLdWr3TgqtBNV4cpSnzkkwox 13 | z2M2MO4A9hNKHru6YAWH1wdTYWML9oykW28amPO+uakCgYEA8Xy1T65UrlXSRukI 14 | vLyDFflDw7GeCSuNAiSp+NQ1NCZecHZOn/No6GOG67ildlGKIx0aft5UqSZ4Tzzv 15 | Ln8ae1A2o73JktHKh+AaQfPqPbOgF0BtOjHiAeCzL/Fg4OmL7Iulfqv/s+Zjgi6t 16 | rMO4U/pk3c76CVv4Ve+2whUVQUcCgYEAy6RCaJ7+dPeaiPRXSym0l1TWQxDByxhy 17 | SepbRejlOyNY7f2Wy1NZYgTWse6ZspWqUrF0asUcQlqKguPYVkHPOq0VFPtXMAf+ 18 | KWSNIWH+QQk5Sz5EYsz2e8fYvp2bHoMhLGR70LEdWZ0e4XfdTB84lAhpqyphQJd+ 19 | Ii1QCR0YYk0CgYAUxVo/CUqlWCJnEQI8YaaQzEjk2zExrrpJNegcy/vriWVUHQj0 20 | PxaF7pGbUt2AiNDt5kHyKaN0jSeKAT69bBdWBfc9LETbSQo3l7IKfDVoqzuY5GTA 21 | vaun57Dh7cwcbFoptXU9qQOscKOcffFYS9ld98qXPYdKKMv6cEzy8qdKXwKBgQDK 22 | WVjHnp0X2uVKiwimnjVdXiZdk6wNRChkfCPfzrDg15nJOf954ENj24z+VymQQ5lv 23 | +/bDFUOkR1V1BDP2WgQxuMTHCYy0JG0P8mMuNbIwK5srwh6v0hxmGMmtduwpIIF5 24 | HAvP7f5NCowh3DogJrHQnp9pPn5OcXobOc1akPemzQKBgQDpjvGbbcTZNuS46DtH 25 | jBEiYvazb6Gp1/2iG8X17oTpAjPiEVQ5+CWP4ep0r1Cia+g+oBfAXfEQ9jTzDDS9 26 | FtxsD056OlDSHA5WRQKKqf3kJaKWe8w+TmWcW8WRigdsI1EfKhmfJPifXPo6VaMZ 27 | JoRG4bP8IWxwShzAYz4YaK2oUw== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /pkg/net/https/certs/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIExzCCAy+gAwIBAgIQBJ3CL4o3WuwzRIaCPEtRnDANBgkqhkiG9w0BAQsFADCB 3 | pzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT4wPAYDVQQLDDVERVNL 4 | VE9QLUNQTjIxVk9cc25vd2RyZWFtQERFU0tUT1AtQ1BOMjFWTyAoc25vd2RyZWFt 5 | KTFFMEMGA1UEAww8bWtjZXJ0IERFU0tUT1AtQ1BOMjFWT1xzbm93ZHJlYW1AREVT 6 | S1RPUC1DUE4yMVZPIChzbm93ZHJlYW0pMB4XDTIzMDUxODAyMzU0M1oXDTI1MDgx 7 | ODAyMzU0M1owaTEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmlj 8 | YXRlMT4wPAYDVQQLDDVERVNLVE9QLUNQTjIxVk9cc25vd2RyZWFtQERFU0tUT1At 9 | Q1BOMjFWTyAoc25vd2RyZWFtKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 10 | ggEBAMAY1k8trAfHifpfl+YSzbtRNuVVyCR/rQVWTqFsrx+cGv+mH/46ZiNTqXuM 11 | Ch9ZlvMxu70mXUeawuM1L3Hq3HT5pw3tnDVdPfd/m7cXJyOyo7+1T4ByQ9HqxCQm 12 | YW5XJTYdusebWPQDt0hnP2KK3oS5LQgGK4wVtxQyCWh0slIyafKeQRaUimVEg1V0 13 | +9yK/hjCCraKbHe0yfooaLwpw3zD9+sWVpCkcmT21K6ALoiiF0WKFs28NCi/ffKH 14 | ZR5aMsPbCB5gzaBD0EnuES+DNKBk/e1EEf9IDnlxZMBaQghCcGc64JF2BLWeRWDG 15 | OXRtypmeHuuDNY2IBUYiVKQJ0FsCAwEAAaOBqzCBqDAOBgNVHQ8BAf8EBAMCBaAw 16 | EwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAU06X2xiEPhqyuzW6WtjuC 17 | viIkFJcwYAYDVR0RBFkwV4ILZXhhbXBsZS5jb22CDSouZXhhbXBsZS5jb22CDGV4 18 | YW1wbGUudGVzdIIJbG9jYWxob3N0gggtaW5zdGFsbIcEfwAAAYcQAAAAAAAAAAAA 19 | AAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAYEAFih0RaaE9FZsix/TAyYsZg2XDSwT 20 | 9PYBFn+LRWV3QRobKNE2ZQVXSVvGzKpbPLL8uWaJ+YtH/RBXSrI/kT5Mub+PCK2U 21 | J2oDE/HPFTE8o/hRuRhuJPkZye+q/km8I8GGc3gNj8p/4Xvh+OZPNreSqGHoJDwE 22 | dluJZzU8xH7qQY/mP0/XdlDQdKJgzBFu0BnzmRQPI9G/D0fZ+6U0CvDDOEW/RS5l 23 | BgZigpvzNgkgtYtaiqxxX36AqXbIJUQVpZBjKMioSc0Q8F0bo1aA39SeLUDCy7Oh 24 | 9/fNQbXhHNAOvidyupeLVxmM0CbPNi45V5JRN2tKWig4Rd2qlRWD/UQajqjWjsbi 25 | HTvV31ISrsLZKYkJdl7zjs6e648z2Y3KYDb5Sf1Msf3hVYJZYLBm6M1fLXEwmk9e 26 | mZO618PeSfrVXo203lC+2BWvuGBDZTZAQhr3RBIm7MolyUwCo1ArJvhnaqOqVqw4 27 | csN4iiBRtUAuyyTVgZZUXT5dpaAfuxbhXLRa 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /pkg/net/http/response.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // Response Response 10 | type Response struct { 11 | Version string `json:"version" xml:"version" yaml:"version" schema:"version"` 12 | Code string `json:"code" xml:"code" yaml:"code" schema:"code"` 13 | Message string `json:"message" xml:"message" yaml:"message" schema:"message"` 14 | Data any `json:"data" xml:"data" yaml:"data" schema:"data"` 15 | Timestamp int64 `json:"timestamp" xml:"timestamp" yaml:"timestamp" schema:"timestamp"` 16 | RequestID string `json:"requestid" xml:"requestid" yaml:"requestid" schema:"requestid"` 17 | } 18 | 19 | // NewResponse NewResponse 20 | func NewResponse(code string, message string, data any) Response { 21 | if nil == data { 22 | return Response{ 23 | Version: "0.1", 24 | Code: code, 25 | Message: message, 26 | Data: struct{}{}, 27 | Timestamp: time.Now().Unix(), 28 | } 29 | } 30 | 31 | return Response{ 32 | Version: "0.1", 33 | Code: code, 34 | Message: message, 35 | Data: data, 36 | Timestamp: time.Now().Unix(), 37 | } 38 | } 39 | 40 | // ResponseSuccess ResponseSuccess 41 | func ResponseSuccess(c *gin.Context) Response { 42 | return NewResponse("SUCCESS", "SUCCESS", nil) 43 | } 44 | 45 | // ResponseSuccessWithData ResponseSuccessWithData 46 | func ResponseSuccessWithData(c *gin.Context, data any) Response { 47 | return NewResponse("SUCCESS", "SUCCESS", data) 48 | } 49 | 50 | // ResponseFailure ResponseFailure 51 | func ResponseFailure(c *gin.Context) Response { 52 | return NewResponse("FAILURE", "FAILURE", nil) 53 | } 54 | 55 | // ResponseFailureWithData ResponseFailureWithData 56 | func ResponseFailureWithData(c *gin.Context, data any) Response { 57 | return NewResponse("FAILURE", "FAILURE", data) 58 | } 59 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "os" 7 | 8 | "github.com/gin-gonic/gin" 9 | "gopkg.in/natefinch/lumberjack.v2" 10 | "github.com/snowdreamtech/gserver/pkg/configs" 11 | "github.com/snowdreamtech/gserver/pkg/tools" 12 | ) 13 | 14 | // InitLoggerConfig Init Logger Config 15 | func InitLoggerConfig() { 16 | r := configs.GetAppConfig() 17 | logDir := r.LogDir 18 | 19 | if _, err := os.Stat(logDir); err != nil { 20 | err = os.MkdirAll(logDir, 0640) 21 | 22 | if err != nil { 23 | return 24 | } 25 | } 26 | 27 | // Set gin.log 28 | ginlog := &lumberjack.Logger{ 29 | Filename: logDir + "/gin.log", 30 | MaxSize: 500, // megabytes 31 | MaxBackups: 3, 32 | MaxAge: 28, //days 33 | Compress: true, // disabled by default 34 | } 35 | 36 | tools.DefaultGinWriter = io.MultiWriter(ginlog, os.Stdout) 37 | log.SetOutput(tools.DefaultGinWriter) 38 | log.SetPrefix("[LOG-debug] ") 39 | 40 | // gin.DefaultWriter = io.MultiWriter(accesslog) 41 | // Use the following code if you need to write the logs to file and console at the same time. 42 | gin.DefaultWriter = tools.DefaultGinWriter 43 | 44 | // Set error.log 45 | errorlog := &lumberjack.Logger{ 46 | Filename: logDir + "/error.log", 47 | MaxSize: 500, // megabytes 48 | MaxBackups: 3, 49 | MaxAge: 28, //days 50 | Compress: true, // disabled by default 51 | } 52 | 53 | // gin.DefaultErrorWriter = io.MultiWriter(errorlog) 54 | // Use the following code if you need to write the logs to file and console at the same time. 55 | gin.DefaultErrorWriter = io.MultiWriter(errorlog, os.Stderr) 56 | 57 | gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) { 58 | tools.DebugPrintF("[INFO] %-6s %-25s --> %s (%d handlers)", httpMethod, absolutePath, handlerName, nuHandlers) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /pkg/net/http/negotiate.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "github.com/gin-contrib/requestid" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | // OFFEREDJSON []string{gin.MIMEJSON} 9 | var OFFEREDJSON = []string{gin.MIMEJSON} 10 | 11 | // OFFEREDHTML []string{gin.MIMEHTML} 12 | var OFFEREDHTML = []string{gin.MIMEHTML} 13 | 14 | // OFFEREDXML []string{gin.MIMEXML} 15 | var OFFEREDXML = []string{gin.MIMEXML} 16 | 17 | // OFFEREDYAML = []string{gin.MIMEYAML} 18 | var OFFEREDYAML = []string{gin.MIMEYAML} 19 | 20 | // OFFEREDBACKEND []string{gin.MIMEJSON, gin.MIMEXML, gin.MIMEYAML} 21 | var OFFEREDBACKEND = []string{gin.MIMEJSON, gin.MIMEXML, gin.MIMEYAML} 22 | 23 | // OFFEREDALL []string{gin.MIMEHTML, gin.MIMEJSON, gin.MIMEXML, gin.MIMEYAML} 24 | var OFFEREDALL = []string{gin.MIMEHTML, gin.MIMEJSON, gin.MIMEXML, gin.MIMEYAML} 25 | 26 | // Negotiate calls different Render according acceptable Accept format. 27 | func Negotiate(c *gin.Context, statuscode int, config gin.Negotiate) { 28 | negotiate := gin.Negotiate{ 29 | Offered: config.Offered, 30 | HTMLName: config.HTMLName, 31 | HTMLData: config.HTMLData, 32 | JSONData: ResponseSuccessWithData(c, config.JSONData), 33 | XMLData: ResponseSuccessWithData(c, config.XMLData), 34 | YAMLData: ResponseSuccessWithData(c, config.YAMLData), 35 | Data: config.Data, 36 | } 37 | 38 | if negotiate.HTMLName == "" { 39 | negotiate.Data = ResponseSuccessWithData(c, config.Data) 40 | } 41 | 42 | c.Negotiate(statuscode, negotiate) 43 | } 44 | 45 | // NegotiateData calls different Render according acceptable Accept format. 46 | func NegotiateData(c *gin.Context, statuscode int, code string, message string, data any) { 47 | offered := OFFEREDBACKEND 48 | 49 | response := NewResponse(code, message, data) 50 | 51 | response.RequestID = requestid.Get(c) 52 | 53 | negotiate := gin.Negotiate{Offered: offered, Data: response} 54 | 55 | c.Negotiate(statuscode, negotiate) 56 | } 57 | 58 | // NegotiateResponse calls different Render according acceptable Accept format. 59 | func NegotiateResponse(c *gin.Context, statuscode int, response Response) { 60 | offered := OFFEREDBACKEND 61 | 62 | response.RequestID = requestid.Get(c) 63 | 64 | negotiate := gin.Negotiate{Offered: offered, Data: response} 65 | 66 | c.Negotiate(statuscode, negotiate) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/configs/app.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | // AppConfig App Config 6 | type AppConfig struct { 7 | Host string `mapstructure:"host"` 8 | Port string `mapstructure:"port"` 9 | Basic bool `mapstructure:"basic"` 10 | Gzip bool `mapstructure:"gzip"` 11 | User string `mapstructure:"user"` 12 | LogDir string `mapstructure:"logdir"` 13 | RateLimiter string `mapstructure:"ratelimiter"` 14 | ReadTimeout int64 `mapstructure:"readtimeout"` 15 | WriteTimeout int64 `mapstructure:"writetimeout"` 16 | WwwRoot string `mapstructure:"wwwroot"` 17 | AutoIndexTimeFormat string `mapstructure:"autoindextimeformat"` 18 | AutoIndexExactSize bool `mapstructure:"autoindexexactsize"` 19 | PreviewHTML bool `mapstructure:"previewhtml"` 20 | EnableHTTPS bool `mapstructure:"enablehttps"` 21 | HTTPSPort string `mapstructure:"httpsport"` 22 | HTTPSCertFile string `mapstructure:"httpscertfile"` 23 | HTTPSKeyFile string `mapstructure:"httpskeyfile"` 24 | HTTPSCertsDir string `mapstructure:"httpscertsdir"` 25 | HTTPSDomains []string `mapstructure:"httpsdomains"` 26 | ContactEmail string `mapstructure:"contactemail"` 27 | SpeedLimiter int64 `mapstructure:"speedlimiter"` 28 | RefererLimiter bool `mapstructure:"refererlimiter"` 29 | } 30 | 31 | var defaultAppConfig = AppConfig{ 32 | Host: "", 33 | Port: "", 34 | Basic: false, 35 | Gzip: true, 36 | User: "admin:admin", 37 | LogDir: ".", 38 | RateLimiter: "", 39 | ReadTimeout: 10, 40 | WriteTimeout: 10, 41 | WwwRoot: "", 42 | AutoIndexTimeFormat: "2006-01-02 15:04:05", 43 | AutoIndexExactSize: false, 44 | PreviewHTML: true, 45 | EnableHTTPS: false, 46 | HTTPSPort: "", 47 | HTTPSCertFile: "", 48 | HTTPSKeyFile: "", 49 | HTTPSCertsDir: "certs", 50 | HTTPSDomains: nil, 51 | ContactEmail: "", 52 | SpeedLimiter: 0, 53 | RefererLimiter: false, 54 | } 55 | 56 | // GetAppConfigWithContext Get AppConfig from context 57 | func GetAppConfigWithContext(c *gin.Context) (appconfig *AppConfig) { 58 | value, exists := c.Get(ConfigKey) 59 | 60 | if !exists { 61 | return nil 62 | } 63 | 64 | confs, ok := value.(*Configs) 65 | 66 | if !ok { 67 | return nil 68 | } 69 | 70 | return &confs.App 71 | } 72 | 73 | // GetAppConfig Get AppConfig from context 74 | func GetAppConfig() (appconfig *AppConfig) { 75 | if c == nil { 76 | return &defaultAppConfig 77 | } 78 | 79 | return &c.App 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/snowdreamtech/gserver 2 | 3 | go 1.23.5 4 | 5 | require ( 6 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be 7 | github.com/docker/go-units v0.5.0 8 | github.com/fsnotify/fsnotify v1.8.0 9 | github.com/gin-contrib/cors v1.7.3 10 | github.com/gin-contrib/gzip v1.2.2 11 | github.com/gin-contrib/requestid v1.0.4 12 | github.com/gin-gonic/gin v1.10.0 13 | github.com/juju/ratelimit v1.0.2 14 | github.com/redis/go-redis/v9 v9.7.3 15 | github.com/spf13/cobra v1.8.1 16 | github.com/spf13/viper v1.19.0 17 | github.com/ulule/limiter/v3 v3.11.2 18 | go.uber.org/automaxprocs v1.6.0 19 | golang.org/x/crypto v0.35.0 20 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 21 | ) 22 | 23 | require ( 24 | github.com/bytedance/sonic v1.12.7 // indirect 25 | github.com/bytedance/sonic/loader v0.2.2 // indirect 26 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 27 | github.com/cloudwego/base64x v0.1.4 // indirect 28 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 29 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 30 | github.com/gin-contrib/sse v1.0.0 // indirect 31 | github.com/go-playground/locales v0.14.1 // indirect 32 | github.com/go-playground/universal-translator v0.18.1 // indirect 33 | github.com/go-playground/validator/v10 v10.24.0 // indirect 34 | github.com/goccy/go-json v0.10.4 // indirect 35 | github.com/google/uuid v1.6.0 // indirect 36 | github.com/hashicorp/hcl v1.0.0 // indirect 37 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect 40 | github.com/leodido/go-urn v1.4.0 // indirect 41 | github.com/magiconair/properties v1.8.7 // indirect 42 | github.com/mattn/go-isatty v0.0.20 // indirect 43 | github.com/mitchellh/mapstructure v1.5.0 // indirect 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 45 | github.com/modern-go/reflect2 v1.0.2 // indirect 46 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 47 | github.com/pkg/errors v0.9.1 // indirect 48 | github.com/sagikazarmark/locafero v0.4.0 // indirect 49 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 50 | github.com/sourcegraph/conc v0.3.0 // indirect 51 | github.com/spf13/afero v1.11.0 // indirect 52 | github.com/spf13/cast v1.6.0 // indirect 53 | github.com/spf13/pflag v1.0.5 // indirect 54 | github.com/subosito/gotenv v1.6.0 // indirect 55 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 56 | github.com/ugorji/go/codec v1.2.12 // indirect 57 | go.uber.org/atomic v1.9.0 // indirect 58 | go.uber.org/multierr v1.9.0 // indirect 59 | golang.org/x/arch v0.13.0 // indirect 60 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 61 | golang.org/x/net v0.36.0 // indirect 62 | golang.org/x/sys v0.30.0 // indirect 63 | golang.org/x/text v0.22.0 // indirect 64 | google.golang.org/protobuf v1.36.2 // indirect 65 | gopkg.in/ini.v1 v1.67.0 // indirect 66 | gopkg.in/yaml.v3 v3.0.1 // indirect 67 | ) 68 | -------------------------------------------------------------------------------- /middlewares/glimiter.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | libredis "github.com/redis/go-redis/v9" 9 | "github.com/snowdreamtech/gserver/pkg/configs" 10 | ghttp "github.com/snowdreamtech/gserver/pkg/net/http" 11 | "github.com/snowdreamtech/gserver/pkg/tools" 12 | 13 | limiter "github.com/ulule/limiter/v3" 14 | mgin "github.com/ulule/limiter/v3/drivers/middleware/gin" 15 | memory "github.com/ulule/limiter/v3/drivers/store/memory" 16 | sredis "github.com/ulule/limiter/v3/drivers/store/redis" 17 | ) 18 | 19 | // CustomLimitReachedHandler is the Custom LimitReachedHandler used by a new Middleware. 20 | func CustomLimitReachedHandler(c *gin.Context) { 21 | ghttp.NegotiateResponse(c, http.StatusTooManyRequests, ghttp.NewResponse(ghttp.TooManyRequests, "Too many requests, Please try again later.", nil)) 22 | 23 | c.Abort() 24 | } 25 | 26 | // CustomErrorHandler is the Custom ErrorHandler used by a new Middleware. 27 | func CustomErrorHandler(c *gin.Context, err error) { 28 | panic(err) 29 | } 30 | 31 | // RateLimiter RateLimiter 32 | func RateLimiter() gin.HandlerFunc { 33 | tools.DebugPrintF("[INFO] Starting Middleware %s", "RateLimiter") 34 | 35 | app := configs.GetAppConfig() 36 | 37 | if app.RateLimiter == "" { 38 | return Empty() 39 | } 40 | 41 | var store limiter.Store 42 | 43 | // Define a limit rate to 4 requests per hour. 44 | // You can also use the simplified format "-"", with the given 45 | // periods: 46 | // 47 | // * "S": second 48 | // * "M": minute 49 | // * "H": hour 50 | // * "D": day 51 | // 52 | // Examples: 53 | // 54 | // * 5 reqs/second: "5-S" 55 | // * 10 reqs/minute: "10-M" 56 | // * 1000 reqs/hour: "1000-H" 57 | // * 2000 reqs/day: "2000-D" 58 | // 59 | rate, err := limiter.NewRateFromFormatted(app.RateLimiter) 60 | 61 | if err != nil { 62 | tools.DebugPrintF(err.Error()) 63 | return Empty() 64 | } 65 | 66 | store = limiterRedisStore() 67 | 68 | if store == nil { 69 | store = limiterInmemoryStore() 70 | } 71 | 72 | // Create a new middleware with the limiter instance. 73 | middleware := mgin.NewMiddleware(limiter.New(store, rate), mgin.WithErrorHandler(CustomErrorHandler), mgin.WithLimitReachedHandler(CustomLimitReachedHandler)) 74 | 75 | return middleware 76 | } 77 | 78 | func limiterRedisStore() limiter.Store { 79 | r := configs.GetRedisConfig() 80 | 81 | // Create a redis client. 82 | host := r.Host + ":" + strconv.Itoa(r.Port) 83 | 84 | // Create a redis option. 85 | option := &libredis.Options{ 86 | Addr: host, 87 | Password: r.Password, 88 | DB: 1, 89 | } 90 | 91 | // Create a redis client. 92 | client := libredis.NewClient(option) 93 | 94 | // Create a store with the redis client. 95 | store, err := sredis.NewStoreWithOptions(client, limiter.StoreOptions{ 96 | Prefix: "RateLimiter", 97 | MaxRetry: 3, 98 | }) 99 | 100 | if err != nil { 101 | return nil 102 | } 103 | 104 | return store 105 | } 106 | 107 | func limiterInmemoryStore() limiter.Store { 108 | store := memory.NewStore() 109 | 110 | return store 111 | } 112 | -------------------------------------------------------------------------------- /pkg/io/io.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | 7 | "github.com/juju/ratelimit" 8 | ) 9 | 10 | type reader struct { 11 | r *bufio.Reader 12 | bucket *ratelimit.Bucket 13 | } 14 | 15 | // Reader returns a reader that is rate limited by 16 | // the given token bucket. Each token in the bucket 17 | // represents one byte. 18 | func Reader(r io.Reader, bucket *ratelimit.Bucket) io.Reader { 19 | return &reader{ 20 | r: bufio.NewReader(r), 21 | bucket: bucket, 22 | } 23 | } 24 | 25 | func (r *reader) Read(buf []byte) (int, error) { 26 | n, err := r.r.Read(buf) 27 | if n <= 0 { 28 | return n, err 29 | } 30 | r.bucket.Wait(int64(n)) 31 | return n, err 32 | } 33 | 34 | type writer struct { 35 | w *bufio.Writer 36 | bucket *ratelimit.Bucket 37 | } 38 | 39 | // Writer returns a reader that is rate limited by 40 | // the given token bucket. Each token in the bucket 41 | // represents one byte. 42 | func Writer(w io.Writer, bucket *ratelimit.Bucket) io.Writer { 43 | return &writer{ 44 | w: bufio.NewWriter(w), 45 | bucket: bucket, 46 | } 47 | } 48 | 49 | func (w *writer) Write(buf []byte) (int, error) { 50 | w.bucket.Wait(int64(len(buf))) 51 | return w.w.Write(buf) 52 | } 53 | 54 | type seeker struct { 55 | s io.Seeker 56 | bucket *ratelimit.Bucket 57 | } 58 | 59 | // Seeker returns a Seeker that is rate limited by 60 | // the given token bucket. Each token in the bucket 61 | // represents one byte. 62 | func Seeker(s io.Seeker, bucket *ratelimit.Bucket) io.Seeker { 63 | return &seeker{ 64 | s: s, 65 | bucket: bucket, 66 | } 67 | } 68 | 69 | func (s *seeker) Seek(offset int64, whence int) (int64, error) { 70 | return s.s.Seek(offset, whence) 71 | } 72 | 73 | // // ReadWriter is the interface that groups the basic Read and Write methods. 74 | // type ReadWriter interface { 75 | // Reader 76 | // Writer 77 | // } 78 | 79 | // // ReadCloser is the interface that groups the basic Read and Close methods. 80 | // type ReadCloser interface { 81 | // Reader 82 | // Closer 83 | // } 84 | 85 | // // WriteCloser is the interface that groups the basic Write and Close methods. 86 | // type WriteCloser interface { 87 | // Writer 88 | // Closer 89 | // } 90 | 91 | // // ReadWriteCloser is the interface that groups the basic Read, Write and Close methods. 92 | // type ReadWriteCloser interface { 93 | // Reader 94 | // Writer 95 | // Closer 96 | // } 97 | 98 | type readseeker struct { 99 | r reader 100 | s seeker 101 | } 102 | 103 | // ReadSeeker is the interface that groups the basic Read and Seek methods. 104 | func ReadSeeker(r io.Reader, s io.Seeker, bucket *ratelimit.Bucket) io.ReadSeeker { 105 | return &readseeker{ 106 | r: reader{ 107 | r: bufio.NewReader(r), 108 | bucket: bucket, 109 | }, 110 | s: seeker{ 111 | s: s, 112 | bucket: bucket, 113 | }, 114 | } 115 | } 116 | 117 | func (rs *readseeker) Read(buf []byte) (int, error) { 118 | return rs.r.Read(buf) 119 | } 120 | 121 | func (rs *readseeker) Seek(offset int64, whence int) (int64, error) { 122 | return rs.s.Seek(offset, whence) 123 | } 124 | 125 | // // ReadSeekCloser is the interface that groups the basic Read, Seek and Close 126 | // // methods. 127 | // type ReadSeekCloser interface { 128 | // Reader 129 | // Seeker 130 | // Closer 131 | // } 132 | 133 | // // WriteSeeker is the interface that groups the basic Write and Seek methods. 134 | // type WriteSeeker interface { 135 | // Writer 136 | // Seeker 137 | // } 138 | 139 | // // ReadWriteSeeker is the interface that groups the basic Read, Write and Seek methods. 140 | // type ReadWriteSeeker interface { 141 | // Reader 142 | // Writer 143 | // Seeker 144 | // } 145 | -------------------------------------------------------------------------------- /pkg/configs/config.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "github.com/fsnotify/fsnotify" 5 | "github.com/gin-gonic/gin" 6 | "github.com/spf13/viper" 7 | "github.com/snowdreamtech/gserver/pkg/env" 8 | "github.com/snowdreamtech/gserver/pkg/os" 9 | "github.com/snowdreamtech/gserver/pkg/tools" 10 | ) 11 | 12 | // default config names 13 | const ( 14 | // DebugConfigName debug config name 15 | DebugConfigName = "development" 16 | // ReleaseConfigName release config name 17 | ReleaseConfigName = "production" 18 | // TestConfigName test config name 19 | TestConfigName = "test" 20 | ) 21 | 22 | var ( 23 | // Used for flags. 24 | configFile string 25 | 26 | // Config Types 27 | configTypes = []string{"json", "env", "ini", "yaml", "toml", "hcl", "properties"} 28 | 29 | // Config Paths 30 | configPaths = []string{"./", "./configs/", "/etc/" + env.ProjectName, "$HOME/." + env.ProjectName} 31 | ) 32 | 33 | const ( 34 | // ConfigKey ConfigKey 35 | ConfigKey = "github.com/snowdreamtech/gserver/pkg/configs/configkey" 36 | ) 37 | 38 | // Configs Configs 39 | type Configs struct { 40 | Version string `mapstructure:"version"` 41 | App AppConfig `mapstructure:"app"` 42 | Database DatabaseConfig `mapstructure:"database"` 43 | Redis RedisConfig `mapstructure:"redis"` 44 | } 45 | 46 | var c *Configs = &Configs{ 47 | App: defaultAppConfig, 48 | Database: defaultDatabaseConfig, 49 | Redis: defaultRedisConfig, 50 | } 51 | 52 | // InitConfig init config 53 | func InitConfig() (conf *Configs) { 54 | if configFile != "" { 55 | if os.IsExistFile(configFile) { 56 | // tools.DebugPrintF("[WARNING] %s does not exist or is Not a os.", configFile) 57 | return nil 58 | } 59 | 60 | // Use config file from the flag. 61 | viper.SetConfigFile(configFile) 62 | } else { 63 | configName := "" 64 | switch gin.Mode() { 65 | case gin.DebugMode: 66 | configName = DebugConfigName 67 | case gin.ReleaseMode: 68 | configName = ReleaseConfigName 69 | case gin.TestMode: 70 | configName = TestConfigName 71 | } 72 | 73 | configFile := "" 74 | configPath := "" 75 | configType := "" 76 | isFinded := false 77 | 78 | for i := 0; i < len(configPaths); i++ { 79 | configPath = configPaths[i] 80 | 81 | for j := 0; j < len(configTypes); j++ { 82 | configType = configTypes[j] 83 | configFile = configPath + configName + "." + configType 84 | 85 | if os.IsExistFile(configFile) { 86 | isFinded = true 87 | break 88 | } 89 | } 90 | 91 | if isFinded { 92 | viper.SetConfigFile(configFile) 93 | break 94 | } 95 | } 96 | 97 | if configFile == "" || !os.IsExistFile(configFile) { 98 | // tools.DebugPrintF("[WARNING] %s.(json/env/ini/yaml/toml/hcl/properties) does not exist or is Not a os.", configName) 99 | 100 | return nil 101 | } 102 | } 103 | 104 | viper.AutomaticEnv() 105 | 106 | err := viper.ReadInConfig() 107 | 108 | if err != nil { 109 | tools.DebugPrintF("[WARNING] Failed to read the config file %s,\n Error:\n %s .", viper.ConfigFileUsed(), err) 110 | return c 111 | } 112 | 113 | tools.DebugPrintF("[INFO] The config file %s has been used.", viper.ConfigFileUsed()) 114 | 115 | err = viper.Unmarshal(&c) 116 | 117 | if err != nil { 118 | tools.DebugPrintF("[WARNING] Failed to unmarshal the config file %s,\n Error:\n %s .", viper.ConfigFileUsed(), err) 119 | return c 120 | } 121 | 122 | tools.DebugPrintF("[INFO] The config file %s has been Unmarshalled.", viper.ConfigFileUsed()) 123 | 124 | viper.WatchConfig() 125 | 126 | viper.OnConfigChange(func(e fsnotify.Event) { 127 | tools.DebugPrintF("[INFO] The config file %s has been changed.", e.Name) 128 | 129 | if err := viper.Unmarshal(&c); err != nil { 130 | tools.DebugPrintF("[WARNING] Unmarshal conf failed, err:%s ", err) 131 | } 132 | }) 133 | 134 | return c 135 | } 136 | 137 | // ConfigFile config file path 138 | func ConfigFile() *string { 139 | return &configFile 140 | } 141 | 142 | // GetConfigs Get Configs 143 | func GetConfigs() *Configs { 144 | return c 145 | } 146 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | 4 | # The lines below are called `modelines`. See `:help modeline` 5 | # Feel free to remove those if you don't want/need to use them. 6 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 7 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 8 | 9 | version: 2 10 | 11 | before: 12 | hooks: 13 | # You may remove this if you don't use go modules. 14 | - go mod tidy 15 | # you may remove this if you don't need go generate 16 | # - go generate ./... 17 | 18 | builds: 19 | - env: 20 | - CGO_ENABLED=0 21 | flags: 22 | - -trimpath 23 | - -v 24 | ldflags: 25 | - -s -w 26 | - -X 'github.com/snowdreamtech/gserver/pkg/env.ProjectName=gserver' 27 | - -X 'github.com/snowdreamtech/gserver/pkg/env.Author=Snowdream Tech ' 28 | - -X github.com/snowdreamtech/gserver/pkg/env.BuildTime={{.Date}} 29 | - -X github.com/snowdreamtech/gserver/pkg/env.CommitHash={{.ShortCommit}} 30 | - -X github.com/snowdreamtech/gserver/pkg/env.CommitHashFull={{.FullCommit}} 31 | - -X github.com/snowdreamtech/gserver/pkg/env.GitTag={{.Tag}} 32 | - -X 'github.com/snowdreamtech/gserver/pkg/env.COPYRIGHT=Copyright (c) 2023-present SnowdreamTech Inc.' 33 | - -X 'github.com/snowdreamtech/gserver/pkg/env.LICENSE=MIT ' 34 | goos: 35 | # - aix 36 | # - android 37 | - darwin 38 | # - dragonfly 39 | - freebsd 40 | # - illumos 41 | # - ios 42 | # - js 43 | - linux 44 | - netbsd 45 | - openbsd 46 | # - plan9 47 | - solaris 48 | # - wasip1 49 | - windows 50 | goarch: 51 | - 386 52 | - amd64 53 | - arm 54 | - arm64 55 | - loong64 56 | - mips 57 | - mips64 58 | - mips64le 59 | - mipsle 60 | - ppc64 61 | - ppc64le 62 | - riscv64 63 | - s390x 64 | # - wasm 65 | # goamd64: 66 | # - v2 67 | # - v3 68 | goarm: 69 | - 5 70 | - 6 71 | - 7 72 | # goarm64: 73 | # - v8.0 74 | # gomips: 75 | # - hardfloat 76 | # - softfloat 77 | # go386: 78 | # - sse2 79 | # - softfloat 80 | # goppc64: 81 | # - power8 82 | # - power9 83 | # goriscv64: 84 | # - rva22u64 85 | ignore: 86 | - goos: plan9 87 | goarch: 386 88 | archives: 89 | - formats: ["tar.gz"] 90 | # this name template makes the OS and Arch compatible with the results of `uname`. 91 | name_template: >- 92 | {{ .ProjectName }}_ 93 | {{- title .Os }}_ 94 | {{- if eq .Arch "amd64" }}x86_64 95 | {{- else if eq .Arch "386" }}i386 96 | {{- else }}{{ .Arch }}{{ end }} 97 | {{- if .Arm }}v{{ .Arm }}{{ end }} 98 | # use zip for windows archives 99 | format_overrides: 100 | - goos: windows 101 | formats: ["zip"] 102 | files: 103 | - LICENSE 104 | - README.md 105 | 106 | changelog: 107 | sort: asc 108 | use: git 109 | filters: 110 | exclude: 111 | - "^test:" 112 | - "^chore" 113 | - "^ci" 114 | - "^build" 115 | - "^style" 116 | - "merge conflict" 117 | - Merge pull request 118 | - Merge remote-tracking branch 119 | - Merge branch 120 | - go mod tidy 121 | groups: 122 | - title: "New Features" 123 | regexp: "^.*feat[(\\w)]*:+.*$" 124 | order: 100 125 | - title: "New Refactorings" 126 | regexp: "^.*refactor[(\\w)]*:+.*$" 127 | order: 150 128 | - title: "Bug Fixes" 129 | regexp: "^.*fix[(\\w)]*:+.*$" 130 | order: 200 131 | - title: "Dependency Updates" 132 | regexp: "^.*(feat|fix)\\(deps\\)*:+.*$" 133 | order: 300 134 | - title: "Documentation Updates" 135 | regexp: "^.*docs[(\\w)]*:+.*$" 136 | order: 400 137 | - title: Other work 138 | order: 9999 139 | -------------------------------------------------------------------------------- /docker/alpine/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM snowdreamtech/golang:1.23.7-alpine3.21 AS builder 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | # Switch to the user 7 | USER root 8 | 9 | # Set the workdir 10 | WORKDIR /root 11 | 12 | ENV ProjectName="gserver" \ 13 | Author="Snowdream Tech " \ 14 | COPYRIGHT="Copyright (c) 2023-present SnowdreamTech Inc." \ 15 | LICENSE="MIT " \ 16 | CmdPath="github.com/snowdreamtech/gserver/pkg/env" \ 17 | BuildTime=N/A \ 18 | CommitHash=N/A \ 19 | CommitHashFull=N/A \ 20 | GoVersion=N/A \ 21 | GitTag=N/A \ 22 | Debug=false 23 | 24 | COPY . . 25 | 26 | RUN apk add --no-cache \ 27 | git 28 | 29 | RUN go mod tidy \ 30 | && BuildTime=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ 31 | && GoVersion=$(go version | awk '{print $3,$4}') \ 32 | && CommitHash=$(git log -1 --pretty=format:%h 2>/dev/null || echo 'N/A') \ 33 | && CommitHashFull=$(git log -1 --pretty=format:%H 2>/dev/null || echo 'N/A') \ 34 | && GitTag=$(git describe --tags --abbrev=0 2>/dev/null|| echo 'N/A') \ 35 | && LDGOFLAGS="-X '${CmdPath}.ProjectName=${ProjectName}'" \ 36 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.Author=${Author}'" \ 37 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.BuildTime=${BuildTime}'" \ 38 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.CommitHash=${CommitHash}'" \ 39 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.CommitHashFull=${CommitHashFull}'" \ 40 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.GoVersion=${GoVersion}'" \ 41 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.GitTag=${GitTag}'" \ 42 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.COPYRIGHT=${COPYRIGHT}'" \ 43 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.LICENSE=${LICENSE}'" \ 44 | && CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags "${LDGOFLAGS} -s -w" -v -o gserver 45 | 46 | 47 | 48 | 49 | 50 | FROM snowdreamtech/alpine:3.21.3 51 | 52 | # OCI annotations to image 53 | LABEL org.opencontainers.image.authors="Snowdream Tech" \ 54 | org.opencontainers.image.title="GServer Image Based On Alpine" \ 55 | org.opencontainers.image.description="Docker Images for GServer on Alpine. (i386, amd64, arm32v6, arm32v7, arm64, ppc64le,riscv64, s390x)" \ 56 | org.opencontainers.image.documentation="https://hub.docker.com/r/snowdreamtech/gserver" \ 57 | org.opencontainers.image.base.name="snowdreamtech/gserver:alpine" \ 58 | org.opencontainers.image.licenses="MIT" \ 59 | org.opencontainers.image.source="https://github.com/snowdreamtech/gserver" \ 60 | org.opencontainers.image.vendor="Snowdream Tech" \ 61 | org.opencontainers.image.version="0.0.5" \ 62 | org.opencontainers.image.url="https://github.com/snowdreamtech/gserver" 63 | 64 | # Switch to the user 65 | USER root 66 | 67 | # Set the workdir 68 | WORKDIR /root 69 | 70 | # keep the docker container running 71 | ENV KEEPALIVE=1 \ 72 | # The cap_net_bind_service capability in Linux allows a process to bind a socket to Internet domain privileged ports, 73 | # which are port numbers less than 1024. 74 | CAP_NET_BIND_SERVICE=0 75 | 76 | ARG GID=1000 \ 77 | UID=1000 \ 78 | USER=gserver \ 79 | WORKDIR=/var/lib/gserver 80 | 81 | # Create a user with UID and GID 82 | RUN if [ "${USER}" != "root" ]; then \ 83 | addgroup -g ${GID} ${USER}; \ 84 | adduser -H -u ${UID} -g ${USER} -G ${USER} -s /sbin/nologin -D ${USER}; \ 85 | # sed -i "/%sudo/c ${USER} ALL=(ALL:ALL) NOPASSWD:ALL" /etc/sudoers; \ 86 | fi 87 | 88 | # Enable CAP_NET_BIND_SERVICE 89 | RUN if [ "${USER}" != "root" ] && [ "${CAP_NET_BIND_SERVICE}" -eq 1 ]; then \ 90 | apk add --no-cache libcap; \ 91 | # setcap 'cap_net_bind_service=+ep' `which nginx`; \ 92 | fi 93 | 94 | COPY --from=builder /root/gserver /usr/local/bin 95 | 96 | RUN apk add --no-cache \ 97 | vim \ 98 | && mkdir -p ${WORKDIR} \ 99 | && mkdir -p /var/log/gserver \ 100 | && find / -name "*gserver*" -exec chown -Rv ${USER}:${USER} {} \; 101 | 102 | # Switch to the user 103 | USER ${USER} 104 | 105 | # Set the workdir 106 | WORKDIR ${WORKDIR} 107 | 108 | COPY docker/alpine/docker-entrypoint.sh /usr/local/bin/ 109 | 110 | ENTRYPOINT ["docker-entrypoint.sh"] 111 | 112 | CMD [ "GIN_MODE=release","/usr/local/bin/gserver", "--wwwroot", "/var/lib/gserver", "--log-dir", "/var/log/gserver"] -------------------------------------------------------------------------------- /pkg/net/http/routegroup.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Manu Martinez-Almeida. All rights reserved. 2 | // Use of this source code is governed by a MIT style 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | "path" 11 | "path/filepath" 12 | "strings" 13 | 14 | "github.com/gin-gonic/gin" 15 | "github.com/snowdreamtech/gserver/pkg/env" 16 | ) 17 | 18 | // Static serves files from the given file system root. 19 | // Internally a http.FileServer is used, therefore http.NotFound is used instead 20 | // of the Router's NotFound handler. 21 | // To use the operating system's file system implementation, 22 | // use : 23 | // 24 | // router.Static("/static", "/var/www") 25 | func Static(group *gin.RouterGroup, relativePath, root string) gin.IRoutes { 26 | return group.StaticFS(relativePath, gin.Dir(root, false)) 27 | } 28 | 29 | // StaticFS works just like `Static()` but a custom `FileSystem` can be used instead. 30 | // Gin by default uses: gin.Dir() 31 | func StaticFS(group *gin.RouterGroup, relativePath string, fs http.FileSystem) gin.IRoutes { 32 | if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") { 33 | panic("URL parameters can not be used when serving ad static folder") 34 | } 35 | handler := createStaticHandler(group, relativePath, fs) 36 | urlPattern := path.Join(relativePath, "/*filepath") 37 | 38 | // Register GET and HEAD handlers 39 | group.GET(urlPattern, handler) 40 | group.HEAD(urlPattern, handler) 41 | return nil 42 | } 43 | 44 | func createStaticHandler(group *gin.RouterGroup, relativePath string, fs http.FileSystem) gin.HandlerFunc { 45 | absolutePath := calculateAbsolutePath(group, relativePath) 46 | fileServer := http.StripPrefix(absolutePath, FileServer(fs)) 47 | 48 | return func(c *gin.Context) { 49 | file := c.Param("filepath") 50 | 51 | // Check if file exists and/or if we have permission to access it 52 | f, err := fs.Open(filepath.Clean(file)) 53 | if err != nil { 54 | c.Writer.WriteHeader(http.StatusNotFound) 55 | 56 | c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") 57 | 58 | w := c.Writer 59 | title := "404 Not Found" 60 | 61 | fmt.Fprintf(w, "\n") 62 | 63 | fmt.Fprintf(w, "\n") 64 | fmt.Fprintf(w, "\n") 65 | fmt.Fprintf(w, "%s\n", title) 66 | fmt.Fprintf(w, "\n") 67 | fmt.Fprintf(w, "\n") 68 | 69 | fmt.Fprintf(w, "\n") 78 | 79 | fmt.Fprintf(w, "\n") 80 | fmt.Fprintf(w, "
\n") 81 | fmt.Fprintf(w, "
\n") 82 | fmt.Fprintf(w, "

\n") 83 | fmt.Fprintf(w, "%s\n", title) 84 | fmt.Fprintf(w, "

\n") 85 | fmt.Fprintf(w, "
\n") 86 | fmt.Fprintf(w, "
\n") 87 | fmt.Fprintf(w, "
\n") 88 | fmt.Fprintf(w, "
\n") 89 | fmt.Fprintf(w, "
\n") 90 | 91 | fmt.Fprintf(w, "
\n") 92 | fmt.Fprintf(w, "%s/%s\n", env.ProjectName, env.GitTag) 93 | fmt.Fprintf(w, "
\n") 94 | fmt.Fprintf(w, "
\n") 95 | fmt.Fprintf(w, "
\n") 96 | fmt.Fprintf(w, "Powered by") 97 | fmt.Fprintf(w, "%s\n", "https://github.com/snowdreamtech/gserver", "SnowdreamTech Static HTTP Server") 98 | fmt.Fprintf(w, "
\n") 99 | fmt.Fprintf(w, "\n") 100 | fmt.Fprintf(w, "\n") 101 | // c.handlers = group.engine.noRoute 102 | // // Reset index 103 | // c.index = -1 104 | return 105 | } 106 | f.Close() 107 | 108 | fileServer.ServeHTTP(c.Writer, c.Request) 109 | } 110 | } 111 | 112 | func calculateAbsolutePath(group *gin.RouterGroup, relativePath string) string { 113 | return joinPaths(group.BasePath(), relativePath) 114 | } 115 | 116 | func joinPaths(absolutePath, relativePath string) string { 117 | if relativePath == "" { 118 | return absolutePath 119 | } 120 | 121 | finalPath := path.Join(absolutePath, relativePath) 122 | if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' { 123 | return finalPath + "/" 124 | } 125 | return finalPath 126 | } 127 | 128 | func lastChar(str string) uint8 { 129 | if str == "" { 130 | panic("The length of the string can't be 0") 131 | } 132 | return str[len(str)-1] 133 | } 134 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "cwd": "${workspaceFolder}", 4 | "tasks": [ 5 | { 6 | "label": "Go: Install/Update Tools", 7 | "group": "build", 8 | "type": "shell", 9 | "dependsOrder": "parallel", 10 | "dependsOn": [ 11 | "Go: Install/Update goplay", 12 | "Go: Install/Update gotests", 13 | "Go: Install/Update dlv", 14 | "Go: Install/Update gomodifytags", 15 | "Go: Install/Update golint", 16 | "Go: Install/Update impl", 17 | "Go: Install/Update gopls", 18 | "Go: Install/Update goimports", 19 | "Go: Install/Update husky", 20 | "Go: Install/Update commitlint" 21 | ] 22 | }, 23 | { 24 | "label": "Go: Install/Update goplay", 25 | "command": "go", 26 | "args": ["install", "github.com/haya14busa/goplay/cmd/goplay@v1.0.0"], 27 | "group": "build", 28 | "type": "shell" 29 | }, 30 | { 31 | "label": "Go: Install/Update gotests", 32 | "command": "go", 33 | "args": ["install", "github.com/cweill/gotests/gotests@v1.6.0"], 34 | "group": "build", 35 | "type": "shell" 36 | }, 37 | { 38 | "label": "Go: Install/Update dlv", 39 | "command": "go", 40 | "args": ["install", "github.com/go-delve/delve/cmd/dlv@latest"], 41 | "group": "build", 42 | "type": "shell" 43 | }, 44 | { 45 | "label": "Go: Install/Update gomodifytags", 46 | "command": "go", 47 | "args": ["install", "github.com/fatih/gomodifytags@latest"], 48 | "group": "build", 49 | "type": "shell" 50 | }, 51 | { 52 | "label": "Go: Install/Update golint", 53 | "command": "go", 54 | "args": ["install", "golang.org/x/lint/golint@latest"], 55 | "group": "build", 56 | "type": "shell" 57 | }, 58 | { 59 | "label": "Go: Install/Update impl", 60 | "command": "go", 61 | "args": ["install", "github.com/josharian/impl@latest"], 62 | "group": "build", 63 | "type": "shell" 64 | }, 65 | { 66 | "label": "Go: Install/Update gopls", 67 | "command": "go", 68 | "args": ["install", "golang.org/x/tools/gopls@latest"], 69 | "group": "build", 70 | "type": "shell" 71 | }, 72 | { 73 | "label": "Go: Install/Update goimports", 74 | "command": "go", 75 | "args": ["install", "golang.org/x/tools/cmd/goimports@latest"], 76 | "group": "build", 77 | "type": "shell" 78 | }, 79 | { 80 | "label": "Go: Install/Update husky", 81 | "command": "go", 82 | "args": ["install", "github.com/automation-co/husky@latest"], 83 | "group": "build", 84 | "type": "shell" 85 | }, 86 | { 87 | "label": "Go: Install/Update commitlint", 88 | "command": "go", 89 | "args": ["install", "github.com/conventionalcommit/commitlint@latest"], 90 | "group": "build", 91 | "type": "shell" 92 | }, 93 | { 94 | "label": "Go: Init", 95 | "command": "go", 96 | "args": ["mod", "init"], 97 | "group": "build", 98 | "type": "shell" 99 | }, 100 | { 101 | "label": "Go: Tidy", 102 | "command": "go", 103 | "args": ["mod", "tidy"], 104 | "group": "build", 105 | "type": "shell" 106 | }, 107 | { 108 | "label": "Go: Get", 109 | "command": "go", 110 | "args": ["get", "./..."], 111 | "group": "build", 112 | "type": "shell" 113 | }, 114 | { 115 | "label": "Go: Interface{} -> Any", 116 | "command": "gofmt", 117 | "args": ["-w", "-r", "interface{} -> any", "../."], 118 | "group": "build", 119 | "type": "shell" 120 | }, 121 | { 122 | "label": "Go: Upgrade", 123 | "command": "go", 124 | "args": ["get", "-u", "./..."], 125 | "group": "build", 126 | "type": "shell" 127 | }, 128 | { 129 | "label": "Go: Install", 130 | "command": "go", 131 | "args": ["install", "-v", "./..."], 132 | "group": "build", 133 | "type": "shell" 134 | }, 135 | { 136 | "label": "Go: Run", 137 | "command": "go", 138 | "args": ["run", "main.go"], 139 | "group": "build", 140 | "type": "shell" 141 | }, 142 | { 143 | "label": "Go: Test", 144 | "command": "go", 145 | "args": ["test", "-v", "./..."], 146 | "group": "test", 147 | "type": "shell" 148 | }, 149 | { 150 | "label": "Go: Bench", 151 | "command": "go", 152 | "args": ["test", "-v", "-benchmem", "-bench", ".", "./..."], 153 | "group": "test", 154 | "type": "shell" 155 | }, 156 | { 157 | "label": "Go: test env", 158 | "command": "echo ${MSYS_NO_PATHCONV}", 159 | "args": [""], 160 | "group": "build", 161 | "type": "shell" 162 | }, 163 | { 164 | "label": "Go: Generate self-signed ssl certificate (interactive)", 165 | "command": "openssl req -x509 -newkey rsa:4096 -keyout ./pkg/net/https/certs/server.key -out ./pkg/net/https/certs/server.pem -sha256 -days 36500 -nodes", 166 | "args": [""], 167 | "group": "build", 168 | "type": "shell" 169 | }, 170 | { 171 | "label": "Go: Generate self-signed ssl certificate (non-interactive)", 172 | "command": "openssl req -x509 -newkey rsa:4096 -keyout ./pkg/net/https/certs/server.key -out ./pkg/net/https/certs/server.pem -sha256 -days 36500 -nodes -subj \"/C=CN/ST=JX/L=GA/O=SNOWDREAMTECH/OU=R&D Department/CN=localhost\"", 173 | "args": [""], 174 | "group": "build", 175 | "type": "shell" 176 | } 177 | ] 178 | } 179 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos,vim,visualstudiocode,jetbrains,go 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos,vim,visualstudiocode,jetbrains,go 3 | 4 | ### Go ### 5 | # Binaries for programs and plugins 6 | *.exe 7 | *.exe~ 8 | *.dll 9 | *.so 10 | *.dylib 11 | 12 | # Test binary, built with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | ### Go Patch ### 22 | /vendor/ 23 | /Godeps/ 24 | 25 | ### JetBrains ### 26 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 27 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 28 | 29 | # User-specific stuff 30 | .idea/**/workspace.xml 31 | .idea/**/tasks.xml 32 | .idea/**/usage.statistics.xml 33 | .idea/**/dictionaries 34 | .idea/**/shelf 35 | 36 | # Generated files 37 | .idea/**/contentModel.xml 38 | 39 | # Sensitive or high-churn files 40 | .idea/**/dataSources/ 41 | .idea/**/dataSources.ids 42 | .idea/**/dataSources.local.xml 43 | .idea/**/sqlDataSources.xml 44 | .idea/**/dynamic.xml 45 | .idea/**/uiDesigner.xml 46 | .idea/**/dbnavigator.xml 47 | 48 | # Gradle 49 | .idea/**/gradle.xml 50 | .idea/**/libraries 51 | 52 | # Gradle and Maven with auto-import 53 | # When using Gradle or Maven with auto-import, you should exclude module files, 54 | # since they will be recreated, and may cause churn. Uncomment if using 55 | # auto-import. 56 | # .idea/artifacts 57 | # .idea/compiler.xml 58 | # .idea/jarRepositories.xml 59 | # .idea/modules.xml 60 | # .idea/*.iml 61 | # .idea/modules 62 | # *.iml 63 | # *.ipr 64 | 65 | # CMake 66 | cmake-build-*/ 67 | 68 | # Mongo Explorer plugin 69 | .idea/**/mongoSettings.xml 70 | 71 | # File-based project format 72 | *.iws 73 | 74 | # IntelliJ 75 | out/ 76 | 77 | # mpeltonen/sbt-idea plugin 78 | .idea_modules/ 79 | 80 | # JIRA plugin 81 | atlassian-ide-plugin.xml 82 | 83 | # Cursive Clojure plugin 84 | .idea/replstate.xml 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | fabric.properties 91 | 92 | # Editor-based Rest Client 93 | .idea/httpRequests 94 | 95 | # Android studio 3.1+ serialized cache file 96 | .idea/caches/build_file_checksums.ser 97 | 98 | ### JetBrains Patch ### 99 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 100 | 101 | # *.iml 102 | # modules.xml 103 | # .idea/misc.xml 104 | # *.ipr 105 | 106 | # Sonarlint plugin 107 | # https://plugins.jetbrains.com/plugin/7973-sonarlint 108 | .idea/**/sonarlint/ 109 | 110 | # SonarQube Plugin 111 | # https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin 112 | .idea/**/sonarIssues.xml 113 | 114 | # Markdown Navigator plugin 115 | # https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced 116 | .idea/**/markdown-navigator.xml 117 | .idea/**/markdown-navigator-enh.xml 118 | .idea/**/markdown-navigator/ 119 | 120 | # Cache file creation bug 121 | # See https://youtrack.jetbrains.com/issue/JBR-2257 122 | .idea/$CACHE_FILE$ 123 | 124 | # CodeStream plugin 125 | # https://plugins.jetbrains.com/plugin/12206-codestream 126 | .idea/codestream.xml 127 | 128 | ### Linux ### 129 | *~ 130 | 131 | # temporary files which can be created if a process still has a handle open of a deleted file 132 | .fuse_hidden* 133 | 134 | # KDE directory preferences 135 | .directory 136 | 137 | # Linux trash folder which might appear on any partition or disk 138 | .Trash-* 139 | 140 | # .nfs files are created when an open file is removed but is still being accessed 141 | .nfs* 142 | 143 | ### macOS ### 144 | # General 145 | .DS_Store 146 | .AppleDouble 147 | .LSOverride 148 | 149 | # Icon must end with two \r 150 | Icon 151 | 152 | # Thumbnails 153 | ._* 154 | 155 | # Files that might appear in the root of a volume 156 | .DocumentRevisions-V100 157 | .fseventsd 158 | .Spotlight-V100 159 | .TemporaryItems 160 | .Trashes 161 | .VolumeIcon.icns 162 | .com.apple.timemachine.donotpresent 163 | 164 | # Directories potentially created on remote AFP share 165 | .AppleDB 166 | .AppleDesktop 167 | Network Trash Folder 168 | Temporary Items 169 | .apdisk 170 | 171 | ### Vim ### 172 | # Swap 173 | [._]*.s[a-v][a-z] 174 | !*.svg # comment out if you don't need vector files 175 | [._]*.sw[a-p] 176 | [._]s[a-rt-v][a-z] 177 | [._]ss[a-gi-z] 178 | [._]sw[a-p] 179 | 180 | # Session 181 | Session.vim 182 | Sessionx.vim 183 | 184 | # Temporary 185 | .netrwhist 186 | # Auto-generated tag files 187 | tags 188 | # Persistent undo 189 | [._]*.un~ 190 | 191 | ### VisualStudioCode ### 192 | .vscode/* 193 | !.vscode/settings.json 194 | !.vscode/tasks.json 195 | !.vscode/launch.json 196 | !.vscode/extensions.json 197 | *.code-workspace 198 | 199 | ### VisualStudioCode Patch ### 200 | # Ignore all local history of files 201 | .history 202 | 203 | ### Windows ### 204 | # Windows thumbnail cache files 205 | Thumbs.db 206 | Thumbs.db:encryptable 207 | ehthumbs.db 208 | ehthumbs_vista.db 209 | 210 | # Dump file 211 | *.stackdump 212 | 213 | # Folder config file 214 | [Dd]esktop.ini 215 | 216 | # Recycle Bin used on file shares 217 | $RECYCLE.BIN/ 218 | 219 | # Windows Installer files 220 | *.cab 221 | *.msi 222 | *.msix 223 | *.msm 224 | *.msp 225 | 226 | # Windows shortcuts 227 | *.lnk 228 | 229 | # Log 230 | *.log 231 | 232 | # End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,vim,visualstudiocode,jetbrains,go 233 | 234 | __debug_bin 235 | /build/bin 236 | build/release 237 | /tmp 238 | *.db 239 | 240 | PKCS1PrivateKey.pem 241 | PKCS1PublicKey.pem 242 | PKCS8PrivateKey.pem 243 | PKIXPublicKey.pem 244 | 245 | dist/ 246 | -------------------------------------------------------------------------------- /docker/debian/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM snowdreamtech/golang:1.23.5-bookworm AS builder 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | # Switch to the user 7 | USER root 8 | 9 | # Set the workdir 10 | WORKDIR /root 11 | 12 | ENV ProjectName="gserver" \ 13 | Author="Snowdream Tech " \ 14 | COPYRIGHT="Copyright (c) 2023-present SnowdreamTech Inc." \ 15 | LICENSE="MIT " \ 16 | CmdPath="github.com/snowdreamtech/gserver/pkg/env" \ 17 | BuildTime=N/A \ 18 | CommitHash=N/A \ 19 | CommitHashFull=N/A \ 20 | GoVersion=N/A \ 21 | GitTag=N/A \ 22 | Debug=false 23 | 24 | COPY . . 25 | 26 | RUN set -eux \ 27 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy update \ 28 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy install --no-install-recommends \ 29 | git \ 30 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy --purge autoremove \ 31 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy clean \ 32 | && rm -rf /var/lib/apt/lists/* \ 33 | && rm -rf /tmp/* \ 34 | && rm -rf /var/tmp/* 35 | 36 | RUN go mod tidy \ 37 | && BuildTime=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \ 38 | && GoVersion=$(go version | awk '{print $3,$4}') \ 39 | && CommitHash=$(git log -1 --pretty=format:%h 2>/dev/null || echo 'N/A') \ 40 | && CommitHashFull=$(git log -1 --pretty=format:%H 2>/dev/null || echo 'N/A') \ 41 | && GitTag=$(git describe --tags --abbrev=0 2>/dev/null|| echo 'N/A') \ 42 | && LDGOFLAGS="-X '${CmdPath}.ProjectName=${ProjectName}'" \ 43 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.Author=${Author}'" \ 44 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.BuildTime=${BuildTime}'" \ 45 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.CommitHash=${CommitHash}'" \ 46 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.CommitHashFull=${CommitHashFull}'" \ 47 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.GoVersion=${GoVersion}'" \ 48 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.GitTag=${GitTag}'" \ 49 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.COPYRIGHT=${COPYRIGHT}'" \ 50 | && LDGOFLAGS="${LDGOFLAGS} -X '${CmdPath}.LICENSE=${LICENSE}'" \ 51 | && CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -trimpath -ldflags "${LDGOFLAGS} -s -w" -v -o gserver 52 | 53 | 54 | 55 | 56 | 57 | 58 | FROM snowdreamtech/debian:12.10.0 59 | 60 | # OCI annotations to image 61 | LABEL org.opencontainers.image.authors="Snowdream Tech" \ 62 | org.opencontainers.image.title="GServer Image Based On Debian" \ 63 | org.opencontainers.image.description="Docker Images for GServer on Debian. (i386,amd64,arm32v5,arm32v7,arm64,mips64le,ppc64le,s390x)" \ 64 | org.opencontainers.image.documentation="https://hub.docker.com/r/snowdreamtech/gserver" \ 65 | org.opencontainers.image.base.name="snowdreamtech/gserver:debian" \ 66 | org.opencontainers.image.licenses="MIT" \ 67 | org.opencontainers.image.source="https://github.com/snowdreamtech/gserver" \ 68 | org.opencontainers.image.vendor="Snowdream Tech" \ 69 | org.opencontainers.image.version="12.8" \ 70 | org.opencontainers.image.url="https://github.com/snowdreamtech/gserver" 71 | 72 | # Switch to the user 73 | USER root 74 | 75 | # Set the workdir 76 | WORKDIR /root 77 | 78 | # keep the docker container running 79 | ENV KEEPALIVE=1 \ 80 | # The cap_net_bind_service capability in Linux allows a process to bind a socket to Internet domain privileged ports, 81 | # which are port numbers less than 1024. 82 | CAP_NET_BIND_SERVICE=0 83 | 84 | ARG GID=1000 \ 85 | UID=1000 \ 86 | USER=gserver \ 87 | WORKDIR=/var/lib/gserver 88 | 89 | # Create a user with UID and GID 90 | RUN set -eux \ 91 | && if [ "${USER}" != "root" ]; then \ 92 | addgroup --gid ${GID} ${USER}; \ 93 | adduser --no-create-home --uid ${UID} --gid ${GID} --gecos ${USER} --disabled-login --disabled-password ${USER}; \ 94 | # sed -i "/%sudo/c ${USER} ALL=(ALL:ALL) NOPASSWD:ALL" /etc/sudoers; \ 95 | fi \ 96 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy --purge autoremove \ 97 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy clean \ 98 | && rm -rf /var/lib/apt/lists/* \ 99 | && rm -rf /tmp/* \ 100 | && rm -rf /var/tmp/* 101 | 102 | # Enable CAP_NET_BIND_SERVICE 103 | RUN set -eux \ 104 | && if [ "${USER}" != "root" ] && [ "${CAP_NET_BIND_SERVICE}" -eq 1 ]; then \ 105 | DEBIAN_FRONTEND=noninteractive apt-get -qqy update; \ 106 | DEBIAN_FRONTEND=noninteractive apt-get -qqy install --no-install-recommends libcap2-bin; \ 107 | # setcap 'cap_net_bind_service=+ep' `which nginx`; \ 108 | fi \ 109 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy --purge autoremove \ 110 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy clean \ 111 | && rm -rf /var/lib/apt/lists/* \ 112 | && rm -rf /tmp/* \ 113 | && rm -rf /var/tmp/* 114 | 115 | COPY --from=builder /root/gserver /usr/local/bin 116 | 117 | RUN set -eux \ 118 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy update \ 119 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy install --no-install-recommends \ 120 | vim \ 121 | && mkdir -p ${WORKDIR} \ 122 | && mkdir -p /var/log/gserver \ 123 | && find / -name "*gserver*" -exec chown -Rv ${USER}:${USER} {} \; \ 124 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy --purge autoremove \ 125 | && DEBIAN_FRONTEND=noninteractive apt-get -qqy clean \ 126 | && rm -rf /var/lib/apt/lists/* \ 127 | && rm -rf /tmp/* \ 128 | && rm -rf /var/tmp/* 129 | 130 | # Switch to the user 131 | USER ${USER} 132 | 133 | # Set the workdir 134 | WORKDIR ${WORKDIR} 135 | 136 | COPY docker/debian/docker-entrypoint.sh /usr/local/bin/ 137 | 138 | ENTRYPOINT ["docker-entrypoint.sh"] 139 | 140 | CMD [ "GIN_MODE=release","/usr/local/bin/gserver", "--wwwroot", "/var/lib/gserver", "--log-dir", "/var/log/gserver"] -------------------------------------------------------------------------------- /.github/workflows/debian.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Continuous Delivery (Debian) 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | - "dev" 8 | - "feat/**" 9 | - "feature/**" 10 | - "fix/**" 11 | - "pr/**" 12 | tags: 13 | - "[0-9]+.[0-9]+.[0-9]+" 14 | - "v[0-9]+.[0-9]+.[0-9]+" 15 | - "V[0-9]+.[0-9]+.[0-9]+" 16 | - "debian-[0-9]+.[0-9]+.[0-9]+" 17 | - "[0-9]+.[0-9]+" 18 | - "v[0-9]+.[0-9]+" 19 | - "V[0-9]+.[0-9]+" 20 | - "debian-[0-9]+.[0-9]+" 21 | - "[0-9]+" 22 | - "v[0-9]+" 23 | - "V[0-9]+" 24 | - "debian-[0-9]+" 25 | pull_request: 26 | branches: 27 | - "main" 28 | - "dev" 29 | - "feat/**" 30 | - "feature/**" 31 | - "fix/**" 32 | - "pr/**" 33 | schedule: 34 | # Automatically run on every Day 35 | - cron: "0 19 * * *" 36 | workflow_dispatch: 37 | 38 | jobs: 39 | buildx: 40 | runs-on: ubuntu-latest 41 | steps: 42 | # - name: Bypass Cloudflare for Github Action Pro 43 | # uses: snowdreamtech/bypass-cloudflare-for-github-action@v0.0.4 44 | # with: 45 | # mode: "list" 46 | # cf_account_id: ${{ secrets.CF_ACCOUNT_ID }} 47 | # cf_api_token: ${{ secrets.CF_API_TOKEN }} 48 | # cf_zone_id: ${{ secrets.CF_ZONE_ID }} 49 | # github_api_token: ${{ secrets.GITHUB_TOKEN }} 50 | - name: Free Disk Space (Ubuntu) 51 | uses: jlumbroso/free-disk-space@v1.3.1 52 | with: 53 | # this might remove tools that are actually needed, 54 | # if set to "true" but frees about 6 GB 55 | tool-cache: false 56 | 57 | # all of these default to true, but feel free to set to 58 | # "false" if necessary for your workflow 59 | android: true 60 | dotnet: true 61 | haskell: true 62 | large-packages: true 63 | docker-images: false 64 | swap-storage: false 65 | - name: Checkout 66 | uses: actions/checkout@v4.2.2 67 | with: 68 | # [Required] Access token with `workflow` scope. 69 | token: ${{ secrets.WORKFLOW_SECRET }} 70 | - name: Set env variables 71 | run: | 72 | echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV 73 | echo "http_proxy=${http_proxy}" >> $GITHUB_ENV 74 | echo "no_proxy=${no_proxy}" >> $GITHUB_ENV 75 | - # Add support for more platforms with QEMU (optional) 76 | # https://github.com/docker/setup-qemu-action 77 | name: Set up QEMU 78 | uses: docker/setup-qemu-action@v3.6.0 79 | - # https://github.com/docker/setup-buildx-action/issues/57#issuecomment-1059657292 80 | # https://github.com/docker/buildx/issues/136#issuecomment-550205439 81 | # docker buildx create --driver-opt env.http_proxy=$http_proxy --driver-opt env.https_proxy=$https_proxy --driver-opt '"env.no_proxy='$no_proxy'"' 82 | name: Set up Docker Buildx 83 | uses: docker/setup-buildx-action@v3.11.1 84 | with: 85 | buildkitd-config: .github/buildkitd.toml 86 | driver-opts: | 87 | env.http_proxy=${{ env.http_proxy }} 88 | env.https_proxy=${{ env.http_proxy }} 89 | "env.no_proxy='${{ env.no_proxy}}'" 90 | - name: Login to DockerHub 91 | uses: docker/login-action@v3.4.0 92 | with: 93 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 94 | password: ${{ secrets.DOCKER_HUB_TOKEN }} 95 | - name: Login to Quay.io 96 | uses: docker/login-action@v3.4.0 97 | with: 98 | registry: quay.io 99 | username: ${{ secrets.QUAY_USERNAME }} 100 | password: ${{ secrets.QUAY_ROBOT_TOKEN }} 101 | - name: Login to GitHub Container Registry 102 | uses: docker/login-action@v3.4.0 103 | with: 104 | registry: ghcr.io 105 | username: ${{ github.repository_owner }} 106 | password: ${{ secrets.GITHUB_TOKEN }} 107 | - name: Docker meta 108 | id: meta 109 | uses: docker/metadata-action@v5.7.0 110 | with: 111 | images: | 112 | name=snowdreamtech/gserver,enable=true 113 | name=ghcr.io/snowdreamtech/gserver,enable=true 114 | name=quay.io/snowdreamtech/gserver,enable=true 115 | flavor: | 116 | latest=false 117 | prefix= 118 | suffix= 119 | tags: | 120 | type=ref,enable=${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' && github.event_name != 'schedule' }},priority=600,prefix=,suffix=-debian,event=branch 121 | type=edge,enable=true,priority=700,prefix=,suffix=-debian,branch=dev 122 | type=raw,enable=${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') && github.event_name != 'schedule' }},priority=200,prefix=,suffix=,value=debian 123 | type=raw,enable=${{ startsWith(github.ref, 'refs/tags/') }},priority=200,prefix=,suffix=,value=debian 124 | type=schedule,enable=true,priority=1000,prefix=,suffix=-debian,pattern=nightly 125 | type=match,enable=true,priority=800,prefix=,suffix=-debian,pattern=\d+.\d+.\d+,group=0,value= 126 | type=match,enable=true,priority=800,prefix=,suffix=-debian,pattern=\d+.\d+,group=0,value= 127 | type=match,enable=${{ !startsWith(github.ref, 'refs/tags/0.') && !startsWith(github.ref, 'refs/tags/v0.') && !startsWith(github.ref, 'refs/tags/V0.') && !startsWith(github.ref, 'refs/tags/debian-0.') && !startsWith(github.ref, 'refs/tags/debian-v0.') && !startsWith(github.ref, 'refs/tags/debian-V0.') }},priority=800,prefix=,suffix=-debian,pattern=\d+,group=0,value= 128 | type=ref,enable=${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' && github.event_name != 'schedule' }},priority=600,prefix=,suffix=-bookworm,event=branch 129 | type=edge,enable=true,priority=700,prefix=,suffix=-bookworm,branch=dev 130 | type=raw,enable=${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') && github.event_name != 'schedule' }},priority=200,prefix=,suffix=,value=bookworm 131 | type=raw,enable=${{ startsWith(github.ref, 'refs/tags/') }},priority=200,prefix=,suffix=,value=bookworm 132 | type=schedule,enable=true,priority=1000,prefix=,suffix=-bookworm,pattern=nightly 133 | type=match,enable=true,priority=800,prefix=,suffix=-bookworm,pattern=\d+.\d+.\d+,group=0,value= 134 | type=match,enable=true,priority=800,prefix=,suffix=-bookworm,pattern=\d+.\d+,group=0,value= 135 | type=match,enable=${{ !startsWith(github.ref, 'refs/tags/0.') && !startsWith(github.ref, 'refs/tags/v0.') && !startsWith(github.ref, 'refs/tags/V0.') && !startsWith(github.ref, 'refs/tags/debian-0.') && !startsWith(github.ref, 'refs/tags/debian-v0.') && !startsWith(github.ref, 'refs/tags/debian-V0.') }},priority=800,prefix=,suffix=-bookworm,pattern=\d+,group=0,value= 136 | env: 137 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 138 | - name: Build and push 139 | uses: docker/build-push-action@v6.18.0 140 | with: 141 | context: . 142 | file: docker/debian/Dockerfile 143 | build-args: | 144 | "http_proxy=${{ env.http_proxy }}" 145 | "https_proxy=${{ env.http_proxy }}" 146 | BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 147 | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 148 | REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 149 | platforms: linux/386,linux/amd64,linux/arm/v5,linux/arm/v7,linux/arm64,linux/mips64le,linux/ppc64le,linux/s390x 150 | push: ${{ github.event_name != 'pull_request' && !startsWith(github.ref, 'refs/heads/feat/') && !startsWith(github.ref, 'refs/heads/feature/') && !startsWith(github.ref, 'refs/heads/fix/') && !startsWith(github.ref, 'refs/heads/pr/') }} 151 | tags: ${{ steps.meta.outputs.tags }} 152 | labels: ${{ steps.meta.outputs.labels }} 153 | annotations: ${{ steps.meta.outputs.annotations }} 154 | cache-from: type=gha 155 | cache-to: type=gha,mode=max 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gserver 2 | 3 | [![dockeri.co](https://dockerico.blankenship.io/image/snowdreamtech/gserver)](https://hub.docker.com/r/snowdreamtech/gserver) 4 | 5 | A Simple Static HTTP Server built with gin and golang. cross platform, zero configuation 6 | 7 | ## Installation 8 | 9 | ### Binary 10 | 11 | [Releases](https://github.com/snowdreamtech/gserver/releases/latest) 12 | 13 | ### CLI 14 | 15 | ```bash 16 | go install github.com/snowdreamtech/gserver@latest 17 | ``` 18 | 19 | ### Docker CLI 20 | 21 | #### [Dockerhub](https://hub.docker.com/r/snowdreamtech/gserver) 22 | 23 | ```bash 24 | docker pull snowdreamtech/gserver:latest 25 | # snowdreamtech/gserver:alpine 26 | # snowdreamtech/gserver:debian 27 | ``` 28 | 29 | #### [GitHub Container Registry](https://github.com/snowdreamtech/gserver/pkgs/container/gserver) 30 | 31 | ```bash 32 | docker pull ghcr.io/snowdreamtech/gserver:latest 33 | # ghcr.io/snowdreamtech/gserver:alpine 34 | # ghcr.io/snowdreamtech/gserver:debian 35 | ``` 36 | 37 | #### [Quay.io](https://quay.io/repository/snowdreamtech/gserver) 38 | 39 | ```bash 40 | docker pull quay.io/snowdreamtech/gserver:latest 41 | # quay.io/snowdreamtech/gserver:alpine 42 | # quay.io/snowdreamtech/gserver:debian 43 | ``` 44 | 45 | ## Usage 46 | 47 | ### CLI 48 | 49 | ```bash 50 | $ gserver --help 51 | A Simple Static HTTP Server built with gin and golang. 52 | 53 | Usage: 54 | [flags] 55 | [command] 56 | 57 | Available Commands: 58 | completion Generate the autocompletion script for the specified shell 59 | env Print version and environment info 60 | help Help about any command 61 | version Print the version number of 62 | 63 | Flags: 64 | --autoindex-exact-size For the HTML format, specifies whether exact file sizes should be output in the directory listing, 65 | or rather rounded to kilobytes, megabytes, and gigabytes. 66 | --autoindex-time-format string this is the AutoIndex Time Format. (default "2006-01-02 15:04:05") 67 | --basic If it is set, we will use HTTP Basic authentication. 68 | 69 | Used together with -u, --user . 70 | 71 | Providing --basic multiple times has no extra effect. 72 | 73 | Example:-u name:password --basic https://example.com 74 | -c, --config string If it is not set, we will try with development.(json/env/ini/yaml/toml/hcl/properties 75 | --contact-email string HTTPS Contact Email 76 | --enable-https If it is set, we will enable https. 77 | -g, --gzip If it is set, we will compress with gzip. (default true) 78 | -h, --help help for this command 79 | -H, --host string Host optionally specifies the Http Address for the server to listen on, 80 | in the form "host:port". If empty, "host:" (host localhost) is used. 81 | --https-cert-dir string HTTPS Cert Directory (default "certs") 82 | --https-cert-file string HTTPS Cert File 83 | --https-domains stringArray HTTPS Domains 84 | --https-key-file string HTTPS Key File 85 | --https-port string HTTPS PORT 86 | --log-dir string The Log Directory which store access.log, error.log etc. (default ".") 87 | -P, --port string Port optionally specifies the TCP Port for the server to listen on, 88 | in the form "host:port". If empty, ":port" (port 8080) is used. 89 | --preview-html For static web files, Whether preview them. (default true) 90 | --rate-limiter string Define a limit rate to several requests per hour. 91 | You can also use the simplified format "-"", with the given 92 | periods: 93 | 94 | * "S": second 95 | * "M": minute 96 | * "H": hour 97 | * "D": day 98 | 99 | Examples: 100 | 101 | * 5 reqs/second: "5-S" 102 | * 10 reqs/minute: "10-M" 103 | * 1000 reqs/hour: "1000-H" 104 | * 2000 reqs/day: "2000-D" 105 | (default "5-S") 106 | --read-timeout int ReadTimeout is the maximum duration for reading the entire 107 | request, including the body. A zero or negative value means 108 | there will be no timeout. 109 | 110 | Because ReadTimeout does not let Handlers make per-request 111 | decisions on each request body's acceptable deadline or 112 | upload rate, most users will prefer to use 113 | ReadHeaderTimeout. It is valid to use them both. (default 10) 114 | --referer-limiter Limit by referer 115 | --speed-limiter int Specify the maximum transfer rate you want curl to use - for 116 | downloads. 117 | The given speed is measured in bytes/second, 118 | -u, --user string Specify the user name and password to use for server authentication. 119 | 120 | The user name and passwords are split up on the first colon, 121 | which makes it impossible to use a colon in the user name with 122 | this option. The password can, still. (default "admin:admin") 123 | --write-timeout int WriteTimeout is the maximum duration before timing out 124 | writes of the response. It is reset whenever a new 125 | request's header is read. Like ReadTimeout, it does not 126 | let Handlers make decisions on a per-request basis. 127 | A zero or negative value means there will be no timeout. (default 10) 128 | -w, --wwwroot string By default, the wwwroot folder is treated as a web root folder. 129 | Static files can be stored in any folder under the web root and accessed with a relative path to that root. 130 | 131 | Use " [command] --help" for more information about a command. 132 | 133 | ``` 134 | 135 | ### Docker CLI 136 | 137 | ```bash 138 | docker run -d \ 139 | --name=gserver \ 140 | -e TZ=Etc/UTC \ 141 | -p 8080:8080 \ 142 | -p 8443:8443 \ 143 | -v /path/to/gserver:/var/lib/gserver \ 144 | --restart unless-stopped \ 145 | snowdreamtech/gserver:latest 146 | # snowdreamtech/gserver:alpine 147 | # snowdreamtech/gserver:debian 148 | ``` 149 | 150 | ## License 151 | 152 | ```bash 153 | (The MIT License) 154 | 155 | Copyright (c) 2023-present SnowdreamTech Inc. 156 | 157 | Permission is hereby granted, free of charge, to any person obtaining 158 | a copy of this software and associated documentation files (the 159 | 'Software'), to deal in the Software without restriction, including 160 | without limitation the rights to use, copy, modify, merge, publish, 161 | distribute, sublicense, and/or sell copies of the Software, and to 162 | permit persons to whom the Software is furnished to do so, subject to 163 | the following conditions: 164 | 165 | The above copyright notice and this permission notice shall be 166 | included in all copies or substantial portions of the Software. 167 | 168 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 169 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 170 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 171 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 172 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 173 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 174 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 175 | ``` 176 | -------------------------------------------------------------------------------- /.github/workflows/alpine.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Actions Continuous Delivery (Alpine) 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | - "dev" 8 | - "feat/**" 9 | - "feature/**" 10 | - "fix/**" 11 | - "pr/**" 12 | tags: 13 | - "[0-9]+.[0-9]+.[0-9]+" 14 | - "v[0-9]+.[0-9]+.[0-9]+" 15 | - "V[0-9]+.[0-9]+.[0-9]+" 16 | - "alpine-[0-9]+.[0-9]+.[0-9]+" 17 | - "[0-9]+.[0-9]+" 18 | - "v[0-9]+.[0-9]+" 19 | - "V[0-9]+.[0-9]+" 20 | - "alpine-[0-9]+.[0-9]+" 21 | - "[0-9]+" 22 | - "v[0-9]+" 23 | - "V[0-9]+" 24 | - "alpine-[0-9]+" 25 | pull_request: 26 | branches: 27 | - "main" 28 | - "dev" 29 | - "feat/**" 30 | - "feature/**" 31 | - "fix/**" 32 | - "pr/**" 33 | schedule: 34 | # Automatically run on every Day 35 | - cron: "0 19 * * *" 36 | workflow_dispatch: 37 | 38 | jobs: 39 | buildx: 40 | runs-on: ubuntu-latest 41 | steps: 42 | # - name: Bypass Cloudflare for Github Action Pro 43 | # uses: snowdreamtech/bypass-cloudflare-for-github-action@v0.0.4 44 | # with: 45 | # mode: "list" 46 | # cf_account_id: ${{ secrets.CF_ACCOUNT_ID }} 47 | # cf_api_token: ${{ secrets.CF_API_TOKEN }} 48 | # cf_zone_id: ${{ secrets.CF_ZONE_ID }} 49 | # github_api_token: ${{ secrets.GITHUB_TOKEN }} 50 | - name: Free Disk Space (Ubuntu) 51 | uses: jlumbroso/free-disk-space@v1.3.1 52 | with: 53 | # this might remove tools that are actually needed, 54 | # if set to "true" but frees about 6 GB 55 | tool-cache: false 56 | 57 | # all of these default to true, but feel free to set to 58 | # "false" if necessary for your workflow 59 | android: true 60 | dotnet: true 61 | haskell: true 62 | large-packages: true 63 | docker-images: false 64 | swap-storage: false 65 | - name: Checkout 66 | uses: actions/checkout@v4.2.2 67 | with: 68 | # [Required] Access token with `workflow` scope. 69 | token: ${{ secrets.WORKFLOW_SECRET }} 70 | - name: Set env variables 71 | run: | 72 | echo "BRANCH=${GITHUB_REF##*/}" >> $GITHUB_ENV 73 | echo "http_proxy=${http_proxy}" >> $GITHUB_ENV 74 | echo "no_proxy=${no_proxy}" >> $GITHUB_ENV 75 | - # Add support for more platforms with QEMU (optional) 76 | # https://github.com/docker/setup-qemu-action 77 | name: Set up QEMU 78 | uses: docker/setup-qemu-action@v3.6.0 79 | - # https://github.com/docker/setup-buildx-action/issues/57#issuecomment-1059657292 80 | # https://github.com/docker/buildx/issues/136#issuecomment-550205439 81 | # docker buildx create --driver-opt env.http_proxy=$http_proxy --driver-opt env.https_proxy=$https_proxy --driver-opt '"env.no_proxy='$no_proxy'"' 82 | name: Set up Docker Buildx 83 | uses: docker/setup-buildx-action@v3.11.1 84 | with: 85 | buildkitd-config: .github/buildkitd.toml 86 | driver-opts: | 87 | env.http_proxy=${{ env.http_proxy }} 88 | env.https_proxy=${{ env.http_proxy }} 89 | "env.no_proxy='${{ env.no_proxy}}'" 90 | - name: Login to DockerHub 91 | uses: docker/login-action@v3.4.0 92 | with: 93 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 94 | password: ${{ secrets.DOCKER_HUB_TOKEN }} 95 | - name: Login to Quay.io 96 | uses: docker/login-action@v3.4.0 97 | with: 98 | registry: quay.io 99 | username: ${{ secrets.QUAY_USERNAME }} 100 | password: ${{ secrets.QUAY_ROBOT_TOKEN }} 101 | - name: Login to GitHub Container Registry 102 | uses: docker/login-action@v3.4.0 103 | with: 104 | registry: ghcr.io 105 | username: ${{ github.repository_owner }} 106 | password: ${{ secrets.GITHUB_TOKEN }} 107 | - name: Docker meta 108 | id: meta 109 | uses: docker/metadata-action@v5.7.0 110 | with: 111 | images: | 112 | name=snowdreamtech/gserver,enable=true 113 | name=ghcr.io/snowdreamtech/gserver,enable=true 114 | name=quay.io/snowdreamtech/gserver,enable=true 115 | flavor: | 116 | latest=false 117 | prefix= 118 | suffix= 119 | tags: | 120 | type=ref,enable=${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' && github.event_name != 'schedule' }},priority=600,prefix=,suffix=,event=branch 121 | type=edge,enable=true,priority=700,prefix=,suffix=,branch=dev 122 | type=raw,enable=${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') && github.event_name != 'schedule' }},priority=200,prefix=,suffix=,value=latest 123 | type=raw,enable=${{ startsWith(github.ref, 'refs/tags/') }},priority=200,prefix=,suffix=,value=latest 124 | type=schedule,enable=true,priority=1000,prefix=,suffix=,pattern=nightly 125 | type=match,enable=true,priority=800,prefix=,suffix=,pattern=\d+.\d+.\d+,group=0,value= 126 | type=match,enable=true,priority=800,prefix=,suffix=,pattern=\d+.\d+,group=0,value= 127 | type=match,enable=${{ !startsWith(github.ref, 'refs/tags/0.') && !startsWith(github.ref, 'refs/tags/v0.') && !startsWith(github.ref, 'refs/tags/V0.') && !startsWith(github.ref, 'refs/tags/alpine-0.') && !startsWith(github.ref, 'refs/tags/alpine-v0.') && !startsWith(github.ref, 'refs/tags/alpine-V0.') }},priority=800,prefix=,suffix=,pattern=\d+,group=0,value= 128 | type=ref,enable=${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' && github.event_name != 'schedule' }},priority=600,prefix=,suffix=-alpine,event=branch 129 | type=edge,enable=true,priority=700,prefix=,suffix=-alpine,branch=dev 130 | type=raw,enable=${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') && github.event_name != 'schedule' }},priority=200,prefix=,suffix=,value=alpine 131 | type=raw,enable=${{ startsWith(github.ref, 'refs/tags/') }},priority=200,prefix=,suffix=,value=alpine 132 | type=schedule,enable=true,priority=1000,prefix=,suffix=-alpine,pattern=nightly 133 | type=match,enable=true,priority=800,prefix=,suffix=-alpine,pattern=\d+.\d+.\d+,group=0,value= 134 | type=match,enable=true,priority=800,prefix=,suffix=-alpine,pattern=\d+.\d+,group=0,value= 135 | type=match,enable=${{ !startsWith(github.ref, 'refs/tags/0.') && !startsWith(github.ref, 'refs/tags/v0.') && !startsWith(github.ref, 'refs/tags/V0.') && !startsWith(github.ref, 'refs/tags/alpine-0.') && !startsWith(github.ref, 'refs/tags/alpine-v0.') && !startsWith(github.ref, 'refs/tags/alpine-V0.') }},priority=800,prefix=,suffix=-alpine,pattern=\d+,group=0,value= 136 | type=ref,enable=${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master' && github.event_name != 'schedule' }},priority=600,prefix=,suffix=-alpine3.21,event=branch 137 | type=edge,enable=true,priority=700,prefix=,suffix=-alpine3.21,branch=dev 138 | type=raw,enable=${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') && github.event_name != 'schedule' }},priority=200,prefix=,suffix=,value=alpine3.21 139 | type=raw,enable=${{ startsWith(github.ref, 'refs/tags/') }},priority=200,prefix=,suffix=,value=alpine3.21 140 | type=schedule,enable=true,priority=1000,prefix=,suffix=-alpine3.21,pattern=nightly 141 | type=match,enable=true,priority=800,prefix=,suffix=-alpine3.21,pattern=\d+.\d+.\d+,group=0,value= 142 | type=match,enable=true,priority=800,prefix=,suffix=-alpine3.21,pattern=\d+.\d+,group=0,value= 143 | type=match,enable=${{ !startsWith(github.ref, 'refs/tags/0.') && !startsWith(github.ref, 'refs/tags/v0.') && !startsWith(github.ref, 'refs/tags/V0.') && !startsWith(github.ref, 'refs/tags/alpine-0.') && !startsWith(github.ref, 'refs/tags/alpine-v0.') && !startsWith(github.ref, 'refs/tags/alpine-V0.') }},priority=800,prefix=,suffix=-alpine3.21,pattern=\d+,group=0,value= 144 | env: 145 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 146 | - name: Build and push 147 | uses: docker/build-push-action@v6.18.0 148 | with: 149 | context: . 150 | file: docker/alpine/Dockerfile 151 | build-args: | 152 | "http_proxy=${{ env.http_proxy }}" 153 | "https_proxy=${{ env.http_proxy }}" 154 | BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 155 | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 156 | REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 157 | platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64 158 | # platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/riscv64,linux/s390x 159 | push: ${{ github.event_name != 'pull_request' && !startsWith(github.ref, 'refs/heads/feat/') && !startsWith(github.ref, 'refs/heads/feature/') && !startsWith(github.ref, 'refs/heads/fix/') && !startsWith(github.ref, 'refs/heads/pr/') }} 160 | tags: ${{ steps.meta.outputs.tags }} 161 | labels: ${{ steps.meta.outputs.labels }} 162 | annotations: ${{ steps.meta.outputs.annotations }} 163 | cache-from: type=gha 164 | cache-to: type=gha,mode=max 165 | -------------------------------------------------------------------------------- /pkg/net/http/fs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // HTTP file system request handler 6 | 7 | package http 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "io/fs" 13 | "net/http" 14 | "net/url" 15 | "html" 16 | "path" 17 | "path/filepath" 18 | "sort" 19 | "strings" 20 | "time" 21 | 22 | "github.com/docker/go-units" 23 | "github.com/juju/ratelimit" 24 | "github.com/snowdreamtech/gserver/pkg/configs" 25 | "github.com/snowdreamtech/gserver/pkg/io" 26 | ) 27 | 28 | // condResult is the result of an HTTP request precondition check. 29 | // See https://tools.ietf.org/html/rfc7232 section 3. 30 | type condResult int 31 | 32 | const ( 33 | condNone condResult = iota 34 | condTrue 35 | condFalse 36 | ) 37 | 38 | type fileHandler struct { 39 | root http.FileSystem 40 | } 41 | 42 | type anyDirs interface { 43 | len() int 44 | name(i int) string 45 | isDir(i int) bool 46 | size(i int) int64 47 | modtime(i int) time.Time 48 | } 49 | 50 | type fileInfoDirs []fs.FileInfo 51 | 52 | func (d fileInfoDirs) len() int { return len(d) } 53 | func (d fileInfoDirs) isDir(i int) bool { return d[i].IsDir() } 54 | func (d fileInfoDirs) name(i int) string { return d[i].Name() } 55 | func (d fileInfoDirs) size(i int) int64 { return d[i].Size() } 56 | func (d fileInfoDirs) modtime(i int) time.Time { return d[i].ModTime() } 57 | 58 | type dirEntryDirs []fs.DirEntry 59 | 60 | func (d dirEntryDirs) len() int { return len(d) } 61 | func (d dirEntryDirs) isDir(i int) bool { return d[i].IsDir() } 62 | func (d dirEntryDirs) name(i int) string { return d[i].Name() } 63 | func (d dirEntryDirs) size(i int) int64 { 64 | if d.isDir(i) { 65 | return -1 66 | } 67 | 68 | fileinfo, err := d[i].Info() 69 | 70 | if err != nil { 71 | return 0 72 | } 73 | 74 | return fileinfo.Size() 75 | } 76 | 77 | func (d dirEntryDirs) modtime(i int) time.Time { 78 | fileinfo, err := d[i].Info() 79 | if err != nil { 80 | return time.Time{} 81 | } 82 | 83 | return fileinfo.ModTime() 84 | } 85 | 86 | // FileServer returns a handler that serves HTTP requests 87 | // with the contents of the file system rooted at root. 88 | // 89 | // As a special case, the returned file server redirects any request 90 | // ending in "/index.html" to the same path, without the final 91 | // "index.html". 92 | // 93 | // To use the operating system's file system implementation, 94 | // use http.Dir: 95 | // 96 | // http.Handle("/", http.FileServer(http.Dir("/tmp"))) 97 | // 98 | // To use an fs.FS implementation, use http.FS to convert it: 99 | // 100 | // http.Handle("/", http.FileServer(http.FS(fsys))) 101 | func FileServer(root http.FileSystem) http.Handler { 102 | return &fileHandler{root} 103 | } 104 | 105 | func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 106 | const options = http.MethodOptions + ", " + http.MethodGet + ", " + http.MethodHead 107 | 108 | switch r.Method { 109 | case http.MethodGet, http.MethodHead: 110 | if !strings.HasPrefix(r.URL.Path, "/") { 111 | r.URL.Path = "/" + r.URL.Path 112 | } 113 | serveFile(w, r, f.root, path.Clean(r.URL.Path), true) 114 | 115 | case http.MethodOptions: 116 | w.Header().Set("Allow", options) 117 | 118 | default: 119 | w.Header().Set("Allow", options) 120 | http.Error(w, "read-only", http.StatusMethodNotAllowed) 121 | } 122 | } 123 | 124 | // name is '/'-separated, not filepath.Separator. 125 | func serveFile(w http.ResponseWriter, r *http.Request, fs http.FileSystem, name string, redirect bool) { 126 | indexPage := "/index.html" 127 | 128 | app := configs.GetAppConfig() 129 | 130 | if !app.PreviewHTML { 131 | indexPage = "/index.html123456789" 132 | } 133 | 134 | // redirect .../index.html to .../ 135 | // can't use Redirect() because that would make the path absolute, 136 | // which would be a problem running under StripPrefix 137 | if strings.HasSuffix(r.URL.Path, indexPage) { 138 | localRedirect(w, r, "./") 139 | return 140 | } 141 | 142 | f, err := fs.Open(filepath.Clean(name)) 143 | if err != nil { 144 | msg, code := toHTTPError(err) 145 | http.Error(w, msg, code) 146 | return 147 | } 148 | defer f.Close() 149 | 150 | d, err := f.Stat() 151 | if err != nil { 152 | msg, code := toHTTPError(err) 153 | http.Error(w, msg, code) 154 | return 155 | } 156 | 157 | if redirect { 158 | // redirect to canonical path: / at end of directory url 159 | // r.URL.Path always begins with / 160 | url := r.URL.Path 161 | if d.IsDir() { 162 | if url[len(url)-1] != '/' { 163 | localRedirect(w, r, path.Base(url)+"/") 164 | return 165 | } 166 | } else { 167 | if url[len(url)-1] == '/' { 168 | localRedirect(w, r, "../"+path.Base(url)) 169 | return 170 | } 171 | } 172 | } 173 | 174 | if d.IsDir() { 175 | url := r.URL.Path 176 | // redirect if the directory name doesn't end in a slash 177 | if url == "" || url[len(url)-1] != '/' { 178 | localRedirect(w, r, path.Base(url)+"/") 179 | return 180 | } 181 | 182 | // use contents of index.html for directory, if present 183 | index := strings.TrimSuffix(name, "/") + indexPage 184 | ff, err := fs.Open(filepath.Clean(index)) 185 | if err == nil { 186 | defer ff.Close() 187 | dd, err := ff.Stat() 188 | if err == nil { 189 | d = dd 190 | f = ff 191 | } 192 | } 193 | } 194 | 195 | // Still a directory? (we didn't find an index.html file) 196 | if d.IsDir() { 197 | if checkIfModifiedSince(r, d.ModTime()) == condFalse { 198 | writeNotModified(w) 199 | return 200 | } 201 | setLastModified(w, d.ModTime()) 202 | dirList(w, r, f) 203 | return 204 | } 205 | 206 | if app.SpeedLimiter <= 0 { 207 | http.ServeContent(w, r, d.Name(), d.ModTime(), f) 208 | } else { 209 | bucket := ratelimit.NewBucketWithRate(float64(app.SpeedLimiter), app.SpeedLimiter) 210 | readseeker := io.ReadSeeker(f, f, bucket) 211 | http.ServeContent(w, r, d.Name(), d.ModTime(), readseeker) 212 | } 213 | } 214 | 215 | func dirList(w http.ResponseWriter, r *http.Request, f http.File) { 216 | // Prefer to use ReadDir instead of Readdir, 217 | // because the former doesn't require calling 218 | // Stat on every entry of a directory on Unix. 219 | var dirs anyDirs 220 | var err error 221 | if d, ok := f.(fs.ReadDirFile); ok { 222 | var list dirEntryDirs 223 | list, err = d.ReadDir(-1) 224 | dirs = list 225 | } else { 226 | var list fileInfoDirs 227 | list, err = f.Readdir(-1) 228 | dirs = list 229 | } 230 | 231 | if err != nil { 232 | //logf(r, "http: error reading directory: %v", err) 233 | http.Error(w, "http.Error reading directory", http.StatusInternalServerError) 234 | return 235 | } 236 | sort.Slice(dirs, func(i, j int) bool { return dirs.name(i) < dirs.name(j) }) 237 | 238 | app := configs.GetAppConfig() 239 | timeformat := "2006-01-02 15:04:05" 240 | 241 | if app.AutoIndexTimeFormat != "" { 242 | timeformat = app.AutoIndexTimeFormat 243 | } 244 | 245 | title := fmt.Sprintf("Index of %s", html.EscapeString(r.URL.String())) 246 | 247 | w.Header().Set("Content-Type", "text/html; charset=utf-8") 248 | 249 | fmt.Fprintf(w, "\n") 250 | 251 | fmt.Fprintf(w, "\n") 252 | fmt.Fprintf(w, "\n") 253 | fmt.Fprintf(w, "%s\n", title) 254 | fmt.Fprintf(w, "\n") 255 | fmt.Fprintf(w, "\n") 256 | 257 | fmt.Fprintf(w, "\n") 269 | 270 | fmt.Fprintf(w, "\n") 271 | fmt.Fprintf(w, "
\n") 272 | fmt.Fprintf(w, "

\n") 273 | fmt.Fprintf(w, "%s\n", title) 274 | fmt.Fprintf(w, "

\n") 275 | fmt.Fprintf(w, "
\n") 276 | 277 | fmt.Fprintf(w, "
\n") 278 | fmt.Fprintf(w, "
\n") 279 | 280 | // fmt.Fprintf(w, "
\n")
281 | 
282 | 	fmt.Fprintf(w, "\n")
283 | 	fmt.Fprintf(w, "")
284 | 	fmt.Fprintf(w, "../\n", "../")
285 | 	fmt.Fprintf(w, "\n")
286 | 	fmt.Fprintf(w, "\n")
287 | 
288 | 	for i, n := 0, dirs.len(); i < n; i++ {
289 | 		fmt.Fprintf(w, "\n")
290 | 
291 | 		name := dirs.name(i)
292 | 		if dirs.isDir(i) {
293 | 			name += "/"
294 | 		}
295 | 		// name may contain '?' or '#', which must be escaped to remain
296 | 		// part of the URL path, and not indicate the start of a query
297 | 		// string or fragment.
298 | 		url := url.URL{Path: name}
299 | 		if dirs.isDir(i) {
300 | 			fmt.Fprintf(w, "%s%s%s\n", url.String(), htmlReplacer.Replace(name), dirs.modtime(i).Format(timeformat), "-")
301 | 		} else {
302 | 			if app.AutoIndexExactSize {
303 | 				fmt.Fprintf(w, "%s%s%d\n", url.String(), htmlReplacer.Replace(name), dirs.modtime(i).Format(timeformat), dirs.size(i))
304 | 			} else {
305 | 				fmt.Fprintf(w, "%s%s%s\n", url.String(), htmlReplacer.Replace(name), dirs.modtime(i).Format(timeformat), units.HumanSize(float64(dirs.size(i))))
306 | 			}
307 | 		}
308 | 
309 | 		fmt.Fprintf(w, "\n")
310 | 	}
311 | 	// fmt.Fprintf(w, "
\n") 312 | fmt.Fprintf(w, "
\n") 313 | fmt.Fprintf(w, "
\n") 314 | fmt.Fprintf(w, "
\n") 315 | fmt.Fprintf(w, "Powered by") 316 | fmt.Fprintf(w, "%s\n", "https://github.com/snowdreamtech/gserver", "SnowdreamTech Static HTTP Server") 317 | fmt.Fprintf(w, "
\n") 318 | fmt.Fprintf(w, "\n") 319 | fmt.Fprintf(w, "\n") 320 | } 321 | 322 | // localRedirect gives a Moved Permanently response. 323 | // It does not convert relative paths to absolute paths like Redirect does. 324 | func localRedirect(w http.ResponseWriter, r *http.Request, newPath string) { 325 | if q := r.URL.RawQuery; q != "" { 326 | newPath += "?" + q 327 | } 328 | w.Header().Set("Location", newPath) 329 | w.WriteHeader(http.StatusMovedPermanently) 330 | } 331 | 332 | // toHTTPError returns a non-specific HTTP error message and status code 333 | // for a given non-nil error value. It's important that toHTTPError does not 334 | // actually return err.http.Error(), since msg and http.Status are returned to users, 335 | // and historically Go's ServeContent always returned just "404 Not Found" for 336 | // all errors. We don't want to start leaking information in error messages. 337 | func toHTTPError(err error) (msg string, httpStatus int) { 338 | if errors.Is(err, fs.ErrNotExist) { 339 | return "404 page not found", http.StatusNotFound 340 | } 341 | if errors.Is(err, fs.ErrPermission) { 342 | return "403 Forbidden", http.StatusForbidden 343 | } 344 | // Default: 345 | return "500 Internal Server http.Error", http.StatusInternalServerError 346 | } 347 | 348 | func checkIfModifiedSince(r *http.Request, modtime time.Time) condResult { 349 | if r.Method != "GET" && r.Method != "HEAD" { 350 | return condNone 351 | } 352 | ims := r.Header.Get("If-Modified-Since") 353 | if ims == "" || isZeroTime(modtime) { 354 | return condNone 355 | } 356 | t, err := http.ParseTime(ims) 357 | if err != nil { 358 | return condNone 359 | } 360 | // The Last-Modified header truncates sub-second precision so 361 | // the modtime needs to be truncated too. 362 | modtime = modtime.Truncate(time.Second) 363 | if ret := modtime.Compare(t); ret <= 0 { 364 | return condFalse 365 | } 366 | return condTrue 367 | } 368 | 369 | func writeNotModified(w http.ResponseWriter) { 370 | // RFC 7232 section 4.1: 371 | // a sender SHOULD NOT generate representation metadata other than the 372 | // above listed fields unless said metadata exists for the purpose of 373 | // guiding cache updates (e.g., Last-Modified might be useful if the 374 | // response does not have an ETag field). 375 | h := w.Header() 376 | delete(h, "Content-Type") 377 | delete(h, "Content-Length") 378 | delete(h, "Content-Encoding") 379 | if h.Get("Etag") != "" { 380 | delete(h, "Last-Modified") 381 | } 382 | w.WriteHeader(http.StatusNotModified) 383 | } 384 | 385 | // TimeFormat is the time format to use when generating times in HTTP 386 | // headers. It is like time.RFC1123 but hard-codes GMT as the time 387 | // zone. The time being formatted must be in UTC for Format to 388 | // generate the correct format. 389 | // 390 | // For parsing this time format, see ParseTime. 391 | const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" 392 | 393 | var unixEpochTime = time.Unix(0, 0) 394 | 395 | // isZeroTime reports whether t is obviously unspecified (either zero or Unix()=0). 396 | func isZeroTime(t time.Time) bool { 397 | return t.IsZero() || t.Equal(unixEpochTime) 398 | } 399 | 400 | func setLastModified(w http.ResponseWriter, modtime time.Time) { 401 | if !isZeroTime(modtime) { 402 | w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat)) 403 | } 404 | } 405 | 406 | var htmlReplacer = strings.NewReplacer( 407 | "&", "&", 408 | "<", "<", 409 | ">", ">", 410 | // """ is shorter than """. 411 | `"`, """, 412 | // "'" is shorter than "'" and apos was not in HTML until HTML5. 413 | "'", "'", 414 | ) 415 | -------------------------------------------------------------------------------- /docker/alpine/.dockerignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos,jetbrains+all,vim,emacs,visualstudio,visualstudiocode,git,svn,mercurial 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos,jetbrains+all,vim,emacs,visualstudio,visualstudiocode,git,svn,mercurial 3 | 4 | Dockerfile 5 | build.sh 6 | README.md 7 | LICENSE 8 | .github 9 | 10 | ### Emacs ### 11 | # -*- mode: gitignore; -*- 12 | *~ 13 | \#*\# 14 | /.emacs.desktop 15 | /.emacs.desktop.lock 16 | *.elc 17 | auto-save-list 18 | tramp 19 | .\#* 20 | 21 | # Org-mode 22 | .org-id-locations 23 | *_archive 24 | 25 | # flymake-mode 26 | *_flymake.* 27 | 28 | # eshell files 29 | /eshell/history 30 | /eshell/lastdir 31 | 32 | # elpa packages 33 | /elpa/ 34 | 35 | # reftex files 36 | *.rel 37 | 38 | # AUCTeX auto folder 39 | /auto/ 40 | 41 | # cask packages 42 | .cask/ 43 | dist/ 44 | 45 | # Flycheck 46 | flycheck_*.el 47 | 48 | # server auth directory 49 | /server/ 50 | 51 | # projectiles files 52 | .projectile 53 | 54 | # directory configuration 55 | .dir-locals.el 56 | 57 | # network security 58 | /network-security.data 59 | 60 | 61 | ### Git ### 62 | # Created by git for backups. To disable backups in Git: 63 | # $ git config --global mergetool.keepBackup false 64 | *.orig 65 | 66 | # Created by git when using merge tools for conflicts 67 | *.BACKUP.* 68 | *.BASE.* 69 | *.LOCAL.* 70 | *.REMOTE.* 71 | *_BACKUP_*.txt 72 | *_BASE_*.txt 73 | *_LOCAL_*.txt 74 | *_REMOTE_*.txt 75 | 76 | ### JetBrains+all ### 77 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 78 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 79 | 80 | # User-specific stuff 81 | .idea/**/workspace.xml 82 | .idea/**/tasks.xml 83 | .idea/**/usage.statistics.xml 84 | .idea/**/dictionaries 85 | .idea/**/shelf 86 | 87 | # AWS User-specific 88 | .idea/**/aws.xml 89 | 90 | # Generated files 91 | .idea/**/contentModel.xml 92 | 93 | # Sensitive or high-churn files 94 | .idea/**/dataSources/ 95 | .idea/**/dataSources.ids 96 | .idea/**/dataSources.local.xml 97 | .idea/**/sqlDataSources.xml 98 | .idea/**/dynamic.xml 99 | .idea/**/uiDesigner.xml 100 | .idea/**/dbnavigator.xml 101 | 102 | # Gradle 103 | .idea/**/gradle.xml 104 | .idea/**/libraries 105 | 106 | # Gradle and Maven with auto-import 107 | # When using Gradle or Maven with auto-import, you should exclude module files, 108 | # since they will be recreated, and may cause churn. Uncomment if using 109 | # auto-import. 110 | # .idea/artifacts 111 | # .idea/compiler.xml 112 | # .idea/jarRepositories.xml 113 | # .idea/modules.xml 114 | # .idea/*.iml 115 | # .idea/modules 116 | # *.iml 117 | # *.ipr 118 | 119 | # CMake 120 | cmake-build-*/ 121 | 122 | # Mongo Explorer plugin 123 | .idea/**/mongoSettings.xml 124 | 125 | # File-based project format 126 | *.iws 127 | 128 | # IntelliJ 129 | out/ 130 | 131 | # mpeltonen/sbt-idea plugin 132 | .idea_modules/ 133 | 134 | # JIRA plugin 135 | atlassian-ide-plugin.xml 136 | 137 | # Cursive Clojure plugin 138 | .idea/replstate.xml 139 | 140 | # SonarLint plugin 141 | .idea/sonarlint/ 142 | 143 | # Crashlytics plugin (for Android Studio and IntelliJ) 144 | com_crashlytics_export_strings.xml 145 | crashlytics.properties 146 | crashlytics-build.properties 147 | fabric.properties 148 | 149 | # Editor-based Rest Client 150 | .idea/httpRequests 151 | 152 | # Android studio 3.1+ serialized cache file 153 | .idea/caches/build_file_checksums.ser 154 | 155 | ### JetBrains+all Patch ### 156 | # Ignore everything but code style settings and run configurations 157 | # that are supposed to be shared within teams. 158 | 159 | .idea/* 160 | 161 | !.idea/codeStyles 162 | !.idea/runConfigurations 163 | 164 | ### Linux ### 165 | 166 | # temporary files which can be created if a process still has a handle open of a deleted file 167 | .fuse_hidden* 168 | 169 | # KDE directory preferences 170 | .directory 171 | 172 | # Linux trash folder which might appear on any partition or disk 173 | .Trash-* 174 | 175 | # .nfs files are created when an open file is removed but is still being accessed 176 | .nfs* 177 | 178 | ### macOS ### 179 | # General 180 | .DS_Store 181 | .AppleDouble 182 | .LSOverride 183 | 184 | # Icon must end with two \r 185 | Icon 186 | 187 | 188 | # Thumbnails 189 | ._* 190 | 191 | # Files that might appear in the root of a volume 192 | .DocumentRevisions-V100 193 | .fseventsd 194 | .Spotlight-V100 195 | .TemporaryItems 196 | .Trashes 197 | .VolumeIcon.icns 198 | .com.apple.timemachine.donotpresent 199 | 200 | # Directories potentially created on remote AFP share 201 | .AppleDB 202 | .AppleDesktop 203 | Network Trash Folder 204 | Temporary Items 205 | .apdisk 206 | 207 | ### macOS Patch ### 208 | # iCloud generated files 209 | *.icloud 210 | 211 | ### Mercurial ### 212 | .hg/ 213 | .hgignore 214 | .hgsigs 215 | .hgsub 216 | .hgsubstate 217 | .hgtags 218 | 219 | ### SVN ### 220 | .svn/ 221 | 222 | ### Vim ### 223 | # Swap 224 | [._]*.s[a-v][a-z] 225 | !*.svg # comment out if you don't need vector files 226 | [._]*.sw[a-p] 227 | [._]s[a-rt-v][a-z] 228 | [._]ss[a-gi-z] 229 | [._]sw[a-p] 230 | 231 | # Session 232 | Session.vim 233 | Sessionx.vim 234 | 235 | # Temporary 236 | .netrwhist 237 | # Auto-generated tag files 238 | tags 239 | # Persistent undo 240 | [._]*.un~ 241 | 242 | ### VisualStudioCode ### 243 | .vscode/* 244 | !.vscode/settings.json 245 | !.vscode/tasks.json 246 | !.vscode/launch.json 247 | !.vscode/extensions.json 248 | !.vscode/*.code-snippets 249 | 250 | # Local History for Visual Studio Code 251 | .history/ 252 | 253 | # Built Visual Studio Code Extensions 254 | *.vsix 255 | 256 | ### VisualStudioCode Patch ### 257 | # Ignore all local history of files 258 | .history 259 | .ionide 260 | 261 | ### Windows ### 262 | # Windows thumbnail cache files 263 | Thumbs.db 264 | Thumbs.db:encryptable 265 | ehthumbs.db 266 | ehthumbs_vista.db 267 | 268 | # Dump file 269 | *.stackdump 270 | 271 | # Folder config file 272 | [Dd]esktop.ini 273 | 274 | # Recycle Bin used on file shares 275 | $RECYCLE.BIN/ 276 | 277 | # Windows Installer files 278 | *.cab 279 | *.msi 280 | *.msix 281 | *.msm 282 | *.msp 283 | 284 | # Windows shortcuts 285 | *.lnk 286 | 287 | ### VisualStudio ### 288 | ## Ignore Visual Studio temporary files, build results, and 289 | ## files generated by popular Visual Studio add-ons. 290 | ## 291 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 292 | 293 | # User-specific files 294 | *.rsuser 295 | *.suo 296 | *.user 297 | *.userosscache 298 | *.sln.docstates 299 | 300 | # User-specific files (MonoDevelop/Xamarin Studio) 301 | *.userprefs 302 | 303 | # Mono auto generated files 304 | mono_crash.* 305 | 306 | # Build results 307 | [Dd]ebug/ 308 | [Dd]ebugPublic/ 309 | [Rr]elease/ 310 | [Rr]eleases/ 311 | x64/ 312 | x86/ 313 | [Ww][Ii][Nn]32/ 314 | [Aa][Rr][Mm]/ 315 | [Aa][Rr][Mm]64/ 316 | bld/ 317 | [Bb]in/ 318 | [Oo]bj/ 319 | [Ll]og/ 320 | [Ll]ogs/ 321 | 322 | # Visual Studio 2015/2017 cache/options directory 323 | .vs/ 324 | # Uncomment if you have tasks that create the project's static files in wwwroot 325 | #wwwroot/ 326 | 327 | # Visual Studio 2017 auto generated files 328 | Generated\ Files/ 329 | 330 | # MSTest test Results 331 | [Tt]est[Rr]esult*/ 332 | [Bb]uild[Ll]og.* 333 | 334 | # NUnit 335 | *.VisualState.xml 336 | TestResult.xml 337 | nunit-*.xml 338 | 339 | # Build Results of an ATL Project 340 | [Dd]ebugPS/ 341 | [Rr]eleasePS/ 342 | dlldata.c 343 | 344 | # Benchmark Results 345 | BenchmarkDotNet.Artifacts/ 346 | 347 | # .NET Core 348 | project.lock.json 349 | project.fragment.lock.json 350 | artifacts/ 351 | 352 | # ASP.NET Scaffolding 353 | ScaffoldingReadMe.txt 354 | 355 | # StyleCop 356 | StyleCopReport.xml 357 | 358 | # Files built by Visual Studio 359 | *_i.c 360 | *_p.c 361 | *_h.h 362 | *.ilk 363 | *.meta 364 | *.obj 365 | *.iobj 366 | *.pch 367 | *.pdb 368 | *.ipdb 369 | *.pgc 370 | *.pgd 371 | *.rsp 372 | *.sbr 373 | *.tlb 374 | *.tli 375 | *.tlh 376 | *.tmp 377 | *.tmp_proj 378 | *_wpftmp.csproj 379 | *.log 380 | *.tlog 381 | *.vspscc 382 | *.vssscc 383 | .builds 384 | *.pidb 385 | *.svclog 386 | *.scc 387 | 388 | # Chutzpah Test files 389 | _Chutzpah* 390 | 391 | # Visual C++ cache files 392 | ipch/ 393 | *.aps 394 | *.ncb 395 | *.opendb 396 | *.opensdf 397 | *.sdf 398 | *.cachefile 399 | *.VC.db 400 | *.VC.VC.opendb 401 | 402 | # Visual Studio profiler 403 | *.psess 404 | *.vsp 405 | *.vspx 406 | *.sap 407 | 408 | # Visual Studio Trace Files 409 | *.e2e 410 | 411 | # TFS 2012 Local Workspace 412 | $tf/ 413 | 414 | # Guidance Automation Toolkit 415 | *.gpState 416 | 417 | # ReSharper is a .NET coding add-in 418 | _ReSharper*/ 419 | *.[Rr]e[Ss]harper 420 | *.DotSettings.user 421 | 422 | # TeamCity is a build add-in 423 | _TeamCity* 424 | 425 | # DotCover is a Code Coverage Tool 426 | *.dotCover 427 | 428 | # AxoCover is a Code Coverage Tool 429 | .axoCover/* 430 | !.axoCover/settings.json 431 | 432 | # Coverlet is a free, cross platform Code Coverage Tool 433 | coverage*.json 434 | coverage*.xml 435 | coverage*.info 436 | 437 | # Visual Studio code coverage results 438 | *.coverage 439 | *.coveragexml 440 | 441 | # NCrunch 442 | _NCrunch_* 443 | .*crunch*.local.xml 444 | nCrunchTemp_* 445 | 446 | # MightyMoose 447 | *.mm.* 448 | AutoTest.Net/ 449 | 450 | # Web workbench (sass) 451 | .sass-cache/ 452 | 453 | # Installshield output folder 454 | [Ee]xpress/ 455 | 456 | # DocProject is a documentation generator add-in 457 | DocProject/buildhelp/ 458 | DocProject/Help/*.HxT 459 | DocProject/Help/*.HxC 460 | DocProject/Help/*.hhc 461 | DocProject/Help/*.hhk 462 | DocProject/Help/*.hhp 463 | DocProject/Help/Html2 464 | DocProject/Help/html 465 | 466 | # Click-Once directory 467 | publish/ 468 | 469 | # Publish Web Output 470 | *.[Pp]ublish.xml 471 | *.azurePubxml 472 | # Note: Comment the next line if you want to checkin your web deploy settings, 473 | # but database connection strings (with potential passwords) will be unencrypted 474 | *.pubxml 475 | *.publishproj 476 | 477 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 478 | # checkin your Azure Web App publish settings, but sensitive information contained 479 | # in these scripts will be unencrypted 480 | PublishScripts/ 481 | 482 | # NuGet Packages 483 | *.nupkg 484 | # NuGet Symbol Packages 485 | *.snupkg 486 | # The packages folder can be ignored because of Package Restore 487 | **/[Pp]ackages/* 488 | # except build/, which is used as an MSBuild target. 489 | !**/[Pp]ackages/build/ 490 | # Uncomment if necessary however generally it will be regenerated when needed 491 | #!**/[Pp]ackages/repositories.config 492 | # NuGet v3's project.json files produces more ignorable files 493 | *.nuget.props 494 | *.nuget.targets 495 | 496 | # Microsoft Azure Build Output 497 | csx/ 498 | *.build.csdef 499 | 500 | # Microsoft Azure Emulator 501 | ecf/ 502 | rcf/ 503 | 504 | # Windows Store app package directories and files 505 | AppPackages/ 506 | BundleArtifacts/ 507 | Package.StoreAssociation.xml 508 | _pkginfo.txt 509 | *.appx 510 | *.appxbundle 511 | *.appxupload 512 | 513 | # Visual Studio cache files 514 | # files ending in .cache can be ignored 515 | *.[Cc]ache 516 | # but keep track of directories ending in .cache 517 | !?*.[Cc]ache/ 518 | 519 | # Others 520 | ClientBin/ 521 | ~$* 522 | *.dbmdl 523 | *.dbproj.schemaview 524 | *.jfm 525 | *.pfx 526 | *.publishsettings 527 | orleans.codegen.cs 528 | 529 | # Including strong name files can present a security risk 530 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 531 | #*.snk 532 | 533 | # Since there are multiple workflows, uncomment next line to ignore bower_components 534 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 535 | #bower_components/ 536 | 537 | # RIA/Silverlight projects 538 | Generated_Code/ 539 | 540 | # Backup & report files from converting an old project file 541 | # to a newer Visual Studio version. Backup files are not needed, 542 | # because we have git ;-) 543 | _UpgradeReport_Files/ 544 | Backup*/ 545 | UpgradeLog*.XML 546 | UpgradeLog*.htm 547 | ServiceFabricBackup/ 548 | *.rptproj.bak 549 | 550 | # SQL Server files 551 | *.mdf 552 | *.ldf 553 | *.ndf 554 | 555 | # Business Intelligence projects 556 | *.rdl.data 557 | *.bim.layout 558 | *.bim_*.settings 559 | *.rptproj.rsuser 560 | *- [Bb]ackup.rdl 561 | *- [Bb]ackup ([0-9]).rdl 562 | *- [Bb]ackup ([0-9][0-9]).rdl 563 | 564 | # Microsoft Fakes 565 | FakesAssemblies/ 566 | 567 | # GhostDoc plugin setting file 568 | *.GhostDoc.xml 569 | 570 | # Node.js Tools for Visual Studio 571 | .ntvs_analysis.dat 572 | node_modules/ 573 | 574 | # Visual Studio 6 build log 575 | *.plg 576 | 577 | # Visual Studio 6 workspace options file 578 | *.opt 579 | 580 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 581 | *.vbw 582 | 583 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 584 | *.vbp 585 | 586 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 587 | *.dsw 588 | *.dsp 589 | 590 | # Visual Studio 6 technical files 591 | 592 | # Visual Studio LightSwitch build output 593 | **/*.HTMLClient/GeneratedArtifacts 594 | **/*.DesktopClient/GeneratedArtifacts 595 | **/*.DesktopClient/ModelManifest.xml 596 | **/*.Server/GeneratedArtifacts 597 | **/*.Server/ModelManifest.xml 598 | _Pvt_Extensions 599 | 600 | # Paket dependency manager 601 | .paket/paket.exe 602 | paket-files/ 603 | 604 | # FAKE - F# Make 605 | .fake/ 606 | 607 | # CodeRush personal settings 608 | .cr/personal 609 | 610 | # Python Tools for Visual Studio (PTVS) 611 | __pycache__/ 612 | *.pyc 613 | 614 | # Cake - Uncomment if you are using it 615 | # tools/** 616 | # !tools/packages.config 617 | 618 | # Tabs Studio 619 | *.tss 620 | 621 | # Telerik's JustMock configuration file 622 | *.jmconfig 623 | 624 | # BizTalk build output 625 | *.btp.cs 626 | *.btm.cs 627 | *.odx.cs 628 | *.xsd.cs 629 | 630 | # OpenCover UI analysis results 631 | OpenCover/ 632 | 633 | # Azure Stream Analytics local run output 634 | ASALocalRun/ 635 | 636 | # MSBuild Binary and Structured Log 637 | *.binlog 638 | 639 | # NVidia Nsight GPU debugger configuration file 640 | *.nvuser 641 | 642 | # MFractors (Xamarin productivity tool) working folder 643 | .mfractor/ 644 | 645 | # Local History for Visual Studio 646 | .localhistory/ 647 | 648 | # Visual Studio History (VSHistory) files 649 | .vshistory/ 650 | 651 | # BeatPulse healthcheck temp database 652 | healthchecksdb 653 | 654 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 655 | MigrationBackup/ 656 | 657 | # Ionide (cross platform F# VS Code tools) working folder 658 | .ionide/ 659 | 660 | # Fody - auto-generated XML schema 661 | FodyWeavers.xsd 662 | 663 | # VS Code files for those working on multiple tools 664 | *.code-workspace 665 | 666 | # Local History for Visual Studio Code 667 | 668 | # Windows Installer files from build outputs 669 | 670 | # JetBrains Rider 671 | *.sln.iml 672 | 673 | ### VisualStudio Patch ### 674 | # Additional files built by Visual Studio 675 | 676 | # End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,jetbrains+all,vim,emacs,visualstudio,visualstudiocode,git,svn,mercurial 677 | 678 | dist/ 679 | -------------------------------------------------------------------------------- /docker/debian/.dockerignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/windows,linux,macos,jetbrains+all,vim,emacs,visualstudio,visualstudiocode,git,svn,mercurial 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows,linux,macos,jetbrains+all,vim,emacs,visualstudio,visualstudiocode,git,svn,mercurial 3 | 4 | Dockerfile 5 | build.sh 6 | README.md 7 | LICENSE 8 | .github 9 | 10 | ### Emacs ### 11 | # -*- mode: gitignore; -*- 12 | *~ 13 | \#*\# 14 | /.emacs.desktop 15 | /.emacs.desktop.lock 16 | *.elc 17 | auto-save-list 18 | tramp 19 | .\#* 20 | 21 | # Org-mode 22 | .org-id-locations 23 | *_archive 24 | 25 | # flymake-mode 26 | *_flymake.* 27 | 28 | # eshell files 29 | /eshell/history 30 | /eshell/lastdir 31 | 32 | # elpa packages 33 | /elpa/ 34 | 35 | # reftex files 36 | *.rel 37 | 38 | # AUCTeX auto folder 39 | /auto/ 40 | 41 | # cask packages 42 | .cask/ 43 | dist/ 44 | 45 | # Flycheck 46 | flycheck_*.el 47 | 48 | # server auth directory 49 | /server/ 50 | 51 | # projectiles files 52 | .projectile 53 | 54 | # directory configuration 55 | .dir-locals.el 56 | 57 | # network security 58 | /network-security.data 59 | 60 | 61 | ### Git ### 62 | # Created by git for backups. To disable backups in Git: 63 | # $ git config --global mergetool.keepBackup false 64 | *.orig 65 | 66 | # Created by git when using merge tools for conflicts 67 | *.BACKUP.* 68 | *.BASE.* 69 | *.LOCAL.* 70 | *.REMOTE.* 71 | *_BACKUP_*.txt 72 | *_BASE_*.txt 73 | *_LOCAL_*.txt 74 | *_REMOTE_*.txt 75 | 76 | ### JetBrains+all ### 77 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 78 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 79 | 80 | # User-specific stuff 81 | .idea/**/workspace.xml 82 | .idea/**/tasks.xml 83 | .idea/**/usage.statistics.xml 84 | .idea/**/dictionaries 85 | .idea/**/shelf 86 | 87 | # AWS User-specific 88 | .idea/**/aws.xml 89 | 90 | # Generated files 91 | .idea/**/contentModel.xml 92 | 93 | # Sensitive or high-churn files 94 | .idea/**/dataSources/ 95 | .idea/**/dataSources.ids 96 | .idea/**/dataSources.local.xml 97 | .idea/**/sqlDataSources.xml 98 | .idea/**/dynamic.xml 99 | .idea/**/uiDesigner.xml 100 | .idea/**/dbnavigator.xml 101 | 102 | # Gradle 103 | .idea/**/gradle.xml 104 | .idea/**/libraries 105 | 106 | # Gradle and Maven with auto-import 107 | # When using Gradle or Maven with auto-import, you should exclude module files, 108 | # since they will be recreated, and may cause churn. Uncomment if using 109 | # auto-import. 110 | # .idea/artifacts 111 | # .idea/compiler.xml 112 | # .idea/jarRepositories.xml 113 | # .idea/modules.xml 114 | # .idea/*.iml 115 | # .idea/modules 116 | # *.iml 117 | # *.ipr 118 | 119 | # CMake 120 | cmake-build-*/ 121 | 122 | # Mongo Explorer plugin 123 | .idea/**/mongoSettings.xml 124 | 125 | # File-based project format 126 | *.iws 127 | 128 | # IntelliJ 129 | out/ 130 | 131 | # mpeltonen/sbt-idea plugin 132 | .idea_modules/ 133 | 134 | # JIRA plugin 135 | atlassian-ide-plugin.xml 136 | 137 | # Cursive Clojure plugin 138 | .idea/replstate.xml 139 | 140 | # SonarLint plugin 141 | .idea/sonarlint/ 142 | 143 | # Crashlytics plugin (for Android Studio and IntelliJ) 144 | com_crashlytics_export_strings.xml 145 | crashlytics.properties 146 | crashlytics-build.properties 147 | fabric.properties 148 | 149 | # Editor-based Rest Client 150 | .idea/httpRequests 151 | 152 | # Android studio 3.1+ serialized cache file 153 | .idea/caches/build_file_checksums.ser 154 | 155 | ### JetBrains+all Patch ### 156 | # Ignore everything but code style settings and run configurations 157 | # that are supposed to be shared within teams. 158 | 159 | .idea/* 160 | 161 | !.idea/codeStyles 162 | !.idea/runConfigurations 163 | 164 | ### Linux ### 165 | 166 | # temporary files which can be created if a process still has a handle open of a deleted file 167 | .fuse_hidden* 168 | 169 | # KDE directory preferences 170 | .directory 171 | 172 | # Linux trash folder which might appear on any partition or disk 173 | .Trash-* 174 | 175 | # .nfs files are created when an open file is removed but is still being accessed 176 | .nfs* 177 | 178 | ### macOS ### 179 | # General 180 | .DS_Store 181 | .AppleDouble 182 | .LSOverride 183 | 184 | # Icon must end with two \r 185 | Icon 186 | 187 | 188 | # Thumbnails 189 | ._* 190 | 191 | # Files that might appear in the root of a volume 192 | .DocumentRevisions-V100 193 | .fseventsd 194 | .Spotlight-V100 195 | .TemporaryItems 196 | .Trashes 197 | .VolumeIcon.icns 198 | .com.apple.timemachine.donotpresent 199 | 200 | # Directories potentially created on remote AFP share 201 | .AppleDB 202 | .AppleDesktop 203 | Network Trash Folder 204 | Temporary Items 205 | .apdisk 206 | 207 | ### macOS Patch ### 208 | # iCloud generated files 209 | *.icloud 210 | 211 | ### Mercurial ### 212 | .hg/ 213 | .hgignore 214 | .hgsigs 215 | .hgsub 216 | .hgsubstate 217 | .hgtags 218 | 219 | ### SVN ### 220 | .svn/ 221 | 222 | ### Vim ### 223 | # Swap 224 | [._]*.s[a-v][a-z] 225 | !*.svg # comment out if you don't need vector files 226 | [._]*.sw[a-p] 227 | [._]s[a-rt-v][a-z] 228 | [._]ss[a-gi-z] 229 | [._]sw[a-p] 230 | 231 | # Session 232 | Session.vim 233 | Sessionx.vim 234 | 235 | # Temporary 236 | .netrwhist 237 | # Auto-generated tag files 238 | tags 239 | # Persistent undo 240 | [._]*.un~ 241 | 242 | ### VisualStudioCode ### 243 | .vscode/* 244 | !.vscode/settings.json 245 | !.vscode/tasks.json 246 | !.vscode/launch.json 247 | !.vscode/extensions.json 248 | !.vscode/*.code-snippets 249 | 250 | # Local History for Visual Studio Code 251 | .history/ 252 | 253 | # Built Visual Studio Code Extensions 254 | *.vsix 255 | 256 | ### VisualStudioCode Patch ### 257 | # Ignore all local history of files 258 | .history 259 | .ionide 260 | 261 | ### Windows ### 262 | # Windows thumbnail cache files 263 | Thumbs.db 264 | Thumbs.db:encryptable 265 | ehthumbs.db 266 | ehthumbs_vista.db 267 | 268 | # Dump file 269 | *.stackdump 270 | 271 | # Folder config file 272 | [Dd]esktop.ini 273 | 274 | # Recycle Bin used on file shares 275 | $RECYCLE.BIN/ 276 | 277 | # Windows Installer files 278 | *.cab 279 | *.msi 280 | *.msix 281 | *.msm 282 | *.msp 283 | 284 | # Windows shortcuts 285 | *.lnk 286 | 287 | ### VisualStudio ### 288 | ## Ignore Visual Studio temporary files, build results, and 289 | ## files generated by popular Visual Studio add-ons. 290 | ## 291 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 292 | 293 | # User-specific files 294 | *.rsuser 295 | *.suo 296 | *.user 297 | *.userosscache 298 | *.sln.docstates 299 | 300 | # User-specific files (MonoDevelop/Xamarin Studio) 301 | *.userprefs 302 | 303 | # Mono auto generated files 304 | mono_crash.* 305 | 306 | # Build results 307 | [Dd]ebug/ 308 | [Dd]ebugPublic/ 309 | [Rr]elease/ 310 | [Rr]eleases/ 311 | x64/ 312 | x86/ 313 | [Ww][Ii][Nn]32/ 314 | [Aa][Rr][Mm]/ 315 | [Aa][Rr][Mm]64/ 316 | bld/ 317 | [Bb]in/ 318 | [Oo]bj/ 319 | [Ll]og/ 320 | [Ll]ogs/ 321 | 322 | # Visual Studio 2015/2017 cache/options directory 323 | .vs/ 324 | # Uncomment if you have tasks that create the project's static files in wwwroot 325 | #wwwroot/ 326 | 327 | # Visual Studio 2017 auto generated files 328 | Generated\ Files/ 329 | 330 | # MSTest test Results 331 | [Tt]est[Rr]esult*/ 332 | [Bb]uild[Ll]og.* 333 | 334 | # NUnit 335 | *.VisualState.xml 336 | TestResult.xml 337 | nunit-*.xml 338 | 339 | # Build Results of an ATL Project 340 | [Dd]ebugPS/ 341 | [Rr]eleasePS/ 342 | dlldata.c 343 | 344 | # Benchmark Results 345 | BenchmarkDotNet.Artifacts/ 346 | 347 | # .NET Core 348 | project.lock.json 349 | project.fragment.lock.json 350 | artifacts/ 351 | 352 | # ASP.NET Scaffolding 353 | ScaffoldingReadMe.txt 354 | 355 | # StyleCop 356 | StyleCopReport.xml 357 | 358 | # Files built by Visual Studio 359 | *_i.c 360 | *_p.c 361 | *_h.h 362 | *.ilk 363 | *.meta 364 | *.obj 365 | *.iobj 366 | *.pch 367 | *.pdb 368 | *.ipdb 369 | *.pgc 370 | *.pgd 371 | *.rsp 372 | *.sbr 373 | *.tlb 374 | *.tli 375 | *.tlh 376 | *.tmp 377 | *.tmp_proj 378 | *_wpftmp.csproj 379 | *.log 380 | *.tlog 381 | *.vspscc 382 | *.vssscc 383 | .builds 384 | *.pidb 385 | *.svclog 386 | *.scc 387 | 388 | # Chutzpah Test files 389 | _Chutzpah* 390 | 391 | # Visual C++ cache files 392 | ipch/ 393 | *.aps 394 | *.ncb 395 | *.opendb 396 | *.opensdf 397 | *.sdf 398 | *.cachefile 399 | *.VC.db 400 | *.VC.VC.opendb 401 | 402 | # Visual Studio profiler 403 | *.psess 404 | *.vsp 405 | *.vspx 406 | *.sap 407 | 408 | # Visual Studio Trace Files 409 | *.e2e 410 | 411 | # TFS 2012 Local Workspace 412 | $tf/ 413 | 414 | # Guidance Automation Toolkit 415 | *.gpState 416 | 417 | # ReSharper is a .NET coding add-in 418 | _ReSharper*/ 419 | *.[Rr]e[Ss]harper 420 | *.DotSettings.user 421 | 422 | # TeamCity is a build add-in 423 | _TeamCity* 424 | 425 | # DotCover is a Code Coverage Tool 426 | *.dotCover 427 | 428 | # AxoCover is a Code Coverage Tool 429 | .axoCover/* 430 | !.axoCover/settings.json 431 | 432 | # Coverlet is a free, cross platform Code Coverage Tool 433 | coverage*.json 434 | coverage*.xml 435 | coverage*.info 436 | 437 | # Visual Studio code coverage results 438 | *.coverage 439 | *.coveragexml 440 | 441 | # NCrunch 442 | _NCrunch_* 443 | .*crunch*.local.xml 444 | nCrunchTemp_* 445 | 446 | # MightyMoose 447 | *.mm.* 448 | AutoTest.Net/ 449 | 450 | # Web workbench (sass) 451 | .sass-cache/ 452 | 453 | # Installshield output folder 454 | [Ee]xpress/ 455 | 456 | # DocProject is a documentation generator add-in 457 | DocProject/buildhelp/ 458 | DocProject/Help/*.HxT 459 | DocProject/Help/*.HxC 460 | DocProject/Help/*.hhc 461 | DocProject/Help/*.hhk 462 | DocProject/Help/*.hhp 463 | DocProject/Help/Html2 464 | DocProject/Help/html 465 | 466 | # Click-Once directory 467 | publish/ 468 | 469 | # Publish Web Output 470 | *.[Pp]ublish.xml 471 | *.azurePubxml 472 | # Note: Comment the next line if you want to checkin your web deploy settings, 473 | # but database connection strings (with potential passwords) will be unencrypted 474 | *.pubxml 475 | *.publishproj 476 | 477 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 478 | # checkin your Azure Web App publish settings, but sensitive information contained 479 | # in these scripts will be unencrypted 480 | PublishScripts/ 481 | 482 | # NuGet Packages 483 | *.nupkg 484 | # NuGet Symbol Packages 485 | *.snupkg 486 | # The packages folder can be ignored because of Package Restore 487 | **/[Pp]ackages/* 488 | # except build/, which is used as an MSBuild target. 489 | !**/[Pp]ackages/build/ 490 | # Uncomment if necessary however generally it will be regenerated when needed 491 | #!**/[Pp]ackages/repositories.config 492 | # NuGet v3's project.json files produces more ignorable files 493 | *.nuget.props 494 | *.nuget.targets 495 | 496 | # Microsoft Azure Build Output 497 | csx/ 498 | *.build.csdef 499 | 500 | # Microsoft Azure Emulator 501 | ecf/ 502 | rcf/ 503 | 504 | # Windows Store app package directories and files 505 | AppPackages/ 506 | BundleArtifacts/ 507 | Package.StoreAssociation.xml 508 | _pkginfo.txt 509 | *.appx 510 | *.appxbundle 511 | *.appxupload 512 | 513 | # Visual Studio cache files 514 | # files ending in .cache can be ignored 515 | *.[Cc]ache 516 | # but keep track of directories ending in .cache 517 | !?*.[Cc]ache/ 518 | 519 | # Others 520 | ClientBin/ 521 | ~$* 522 | *.dbmdl 523 | *.dbproj.schemaview 524 | *.jfm 525 | *.pfx 526 | *.publishsettings 527 | orleans.codegen.cs 528 | 529 | # Including strong name files can present a security risk 530 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 531 | #*.snk 532 | 533 | # Since there are multiple workflows, uncomment next line to ignore bower_components 534 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 535 | #bower_components/ 536 | 537 | # RIA/Silverlight projects 538 | Generated_Code/ 539 | 540 | # Backup & report files from converting an old project file 541 | # to a newer Visual Studio version. Backup files are not needed, 542 | # because we have git ;-) 543 | _UpgradeReport_Files/ 544 | Backup*/ 545 | UpgradeLog*.XML 546 | UpgradeLog*.htm 547 | ServiceFabricBackup/ 548 | *.rptproj.bak 549 | 550 | # SQL Server files 551 | *.mdf 552 | *.ldf 553 | *.ndf 554 | 555 | # Business Intelligence projects 556 | *.rdl.data 557 | *.bim.layout 558 | *.bim_*.settings 559 | *.rptproj.rsuser 560 | *- [Bb]ackup.rdl 561 | *- [Bb]ackup ([0-9]).rdl 562 | *- [Bb]ackup ([0-9][0-9]).rdl 563 | 564 | # Microsoft Fakes 565 | FakesAssemblies/ 566 | 567 | # GhostDoc plugin setting file 568 | *.GhostDoc.xml 569 | 570 | # Node.js Tools for Visual Studio 571 | .ntvs_analysis.dat 572 | node_modules/ 573 | 574 | # Visual Studio 6 build log 575 | *.plg 576 | 577 | # Visual Studio 6 workspace options file 578 | *.opt 579 | 580 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 581 | *.vbw 582 | 583 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 584 | *.vbp 585 | 586 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 587 | *.dsw 588 | *.dsp 589 | 590 | # Visual Studio 6 technical files 591 | 592 | # Visual Studio LightSwitch build output 593 | **/*.HTMLClient/GeneratedArtifacts 594 | **/*.DesktopClient/GeneratedArtifacts 595 | **/*.DesktopClient/ModelManifest.xml 596 | **/*.Server/GeneratedArtifacts 597 | **/*.Server/ModelManifest.xml 598 | _Pvt_Extensions 599 | 600 | # Paket dependency manager 601 | .paket/paket.exe 602 | paket-files/ 603 | 604 | # FAKE - F# Make 605 | .fake/ 606 | 607 | # CodeRush personal settings 608 | .cr/personal 609 | 610 | # Python Tools for Visual Studio (PTVS) 611 | __pycache__/ 612 | *.pyc 613 | 614 | # Cake - Uncomment if you are using it 615 | # tools/** 616 | # !tools/packages.config 617 | 618 | # Tabs Studio 619 | *.tss 620 | 621 | # Telerik's JustMock configuration file 622 | *.jmconfig 623 | 624 | # BizTalk build output 625 | *.btp.cs 626 | *.btm.cs 627 | *.odx.cs 628 | *.xsd.cs 629 | 630 | # OpenCover UI analysis results 631 | OpenCover/ 632 | 633 | # Azure Stream Analytics local run output 634 | ASALocalRun/ 635 | 636 | # MSBuild Binary and Structured Log 637 | *.binlog 638 | 639 | # NVidia Nsight GPU debugger configuration file 640 | *.nvuser 641 | 642 | # MFractors (Xamarin productivity tool) working folder 643 | .mfractor/ 644 | 645 | # Local History for Visual Studio 646 | .localhistory/ 647 | 648 | # Visual Studio History (VSHistory) files 649 | .vshistory/ 650 | 651 | # BeatPulse healthcheck temp database 652 | healthchecksdb 653 | 654 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 655 | MigrationBackup/ 656 | 657 | # Ionide (cross platform F# VS Code tools) working folder 658 | .ionide/ 659 | 660 | # Fody - auto-generated XML schema 661 | FodyWeavers.xsd 662 | 663 | # VS Code files for those working on multiple tools 664 | *.code-workspace 665 | 666 | # Local History for Visual Studio Code 667 | 668 | # Windows Installer files from build outputs 669 | 670 | # JetBrains Rider 671 | *.sln.iml 672 | 673 | ### VisualStudio Patch ### 674 | # Additional files built by Visual Studio 675 | 676 | # End of https://www.toptal.com/developers/gitignore/api/windows,linux,macos,jetbrains+all,vim,emacs,visualstudio,visualstudiocode,git,svn,mercurial 677 | 678 | dist/ 679 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 2 | github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 3 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 4 | github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 5 | github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q= 6 | github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I= 7 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 8 | github.com/bytedance/sonic/loader v0.2.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o= 9 | github.com/bytedance/sonic/loader v0.2.2/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 10 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 11 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 12 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 13 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 14 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 15 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= 16 | github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= 17 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 18 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 21 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 22 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 23 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 24 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 25 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 26 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 27 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 28 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 29 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 30 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 31 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 32 | github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns= 33 | github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4= 34 | github.com/gin-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI= 35 | github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s= 36 | github.com/gin-contrib/requestid v1.0.4 h1:h9u+YSCMgrDcn2QlHn9c6P/Zwy4WdXqZLFTmlIAJWpA= 37 | github.com/gin-contrib/requestid v1.0.4/go.mod h1:2/3cAmLKQ9E2Pr1IrSPR7K8AWiJORo0hLvs0keKsMJw= 38 | github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= 39 | github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= 40 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 41 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 42 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 43 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 44 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 45 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 46 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 47 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 48 | github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= 49 | github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= 50 | github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= 51 | github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 52 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 53 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 54 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 55 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 56 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 57 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 58 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 59 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 60 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 61 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 62 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 63 | github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI= 64 | github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= 65 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 66 | github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= 67 | github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= 68 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 69 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 70 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 71 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 72 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 73 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 74 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 75 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 76 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 77 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 78 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 79 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 80 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 81 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 82 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 83 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 84 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 85 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 86 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 87 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 88 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 89 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 90 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 91 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 92 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 93 | github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= 94 | github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= 95 | github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= 96 | github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= 97 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 98 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 99 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 100 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 101 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 102 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 103 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 104 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 105 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 106 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 107 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 108 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 109 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 110 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 111 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 112 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 113 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 114 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= 115 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 116 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 117 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 118 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 119 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 120 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 121 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 122 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 123 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 124 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 125 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 126 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 127 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 128 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 129 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 130 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 131 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 132 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 133 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 134 | github.com/ulule/limiter/v3 v3.11.2 h1:P4yOrxoEMJbOTfRJR2OzjL90oflzYPPmWg+dvwN2tHA= 135 | github.com/ulule/limiter/v3 v3.11.2/go.mod h1:QG5GnFOCV+k7lrL5Y8kgEeeflPH3+Cviqlqa8SVSQxI= 136 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 137 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 138 | go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= 139 | go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= 140 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 141 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 142 | golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA= 143 | golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 144 | golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= 145 | golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 146 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 147 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 148 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= 149 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 150 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 151 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 152 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 153 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 154 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 155 | google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= 156 | google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 157 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 158 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 159 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 160 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 161 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 162 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 163 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 164 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 165 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 166 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 167 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 168 | -------------------------------------------------------------------------------- /cmd/server/aserver.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "errors" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "strings" 13 | "syscall" 14 | "time" 15 | 16 | "github.com/common-nighthawk/go-figure" 17 | "github.com/gin-gonic/gin" 18 | "github.com/snowdreamtech/gserver/middlewares" 19 | "github.com/snowdreamtech/gserver/pkg/configs" 20 | "github.com/snowdreamtech/gserver/pkg/env" 21 | glog "github.com/snowdreamtech/gserver/pkg/log" 22 | gnet "github.com/snowdreamtech/gserver/pkg/net" 23 | ghttp "github.com/snowdreamtech/gserver/pkg/net/http" 24 | ghttps "github.com/snowdreamtech/gserver/pkg/net/https" 25 | "github.com/snowdreamtech/gserver/pkg/tools" 26 | "github.com/spf13/cobra" 27 | "go.uber.org/automaxprocs/maxprocs" 28 | "golang.org/x/crypto/acme/autocert" 29 | ) 30 | 31 | var ( 32 | rootCmd *cobra.Command 33 | gHandler *gin.Engine 34 | ) 35 | 36 | func init() { 37 | // Disable automaxprocs log 38 | // https://github.com/uber-go/automaxprocs/issues/19#issuecomment-557382150 39 | nopLog := func(string, ...interface{}) {} 40 | maxprocs.Set(maxprocs.Logger(nopLog)) 41 | 42 | //load configs from File 43 | conf := configs.InitConfig() 44 | 45 | rootCmd = &cobra.Command{ 46 | Use: env.ProjectName, 47 | Short: env.ProjectName + " is a simple static http server", 48 | Long: "A Simple Static HTTP Server built with gin and golang.", 49 | Args: cobra.RangeArgs(0, 1), 50 | Run: func(cmd *cobra.Command, args []string) { 51 | // Load configs from Cli 52 | if conf == nil && *configs.ConfigFile() != "" { 53 | conf = configs.InitConfig() 54 | } 55 | 56 | app := configs.GetAppConfig() 57 | 58 | if args != nil && len(args) == 1 { 59 | app.WwwRoot = args[0] 60 | } 61 | 62 | glog.InitLoggerConfig() 63 | 64 | welcome() 65 | 66 | tools.DebugPrintF("[INFO] Starting Web Server %s", env.ProjectName) 67 | tools.DebugPrintF("[INFO] Args: %s", strings.Join(args, " ")) 68 | 69 | // db.Open() 70 | 71 | gHandler := gin.New() 72 | 73 | // RedirectFixedPath if enabled, the router tries to fix the current request path, if no 74 | // handle is registered for it. 75 | // First superfluous path elements like ../ or // are removed. 76 | // Afterwards the router does a case-insensitive lookup of the cleaned path. 77 | // If a handle can be found for this route, the router makes a redirection 78 | // to the corrected path with status code 301 for GET requests and 307 for 79 | // all other request methods. 80 | // For example /FOO and /..//Foo could be redirected to /foo. 81 | // RedirectTrailingSlash is independent of this option. 82 | gHandler.RedirectFixedPath = true 83 | 84 | // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes. 85 | // See the PR #1817 and issue #1644 86 | gHandler.RemoveExtraSlash = true 87 | 88 | gHandler.Use(middlewares.Configs(conf)) 89 | gHandler.Use(middlewares.LoggerWithFormatter()) 90 | gHandler.Use(middlewares.BasicAuth()) 91 | gHandler.Use(middlewares.Cors()) 92 | gHandler.Use(middlewares.Referer()) 93 | gHandler.Use(middlewares.RateLimiter()) 94 | gHandler.Use(middlewares.Gzip()) 95 | gHandler.Use(middlewares.Header()) 96 | gHandler.Use(gin.Recovery()) 97 | 98 | if app.WwwRoot == "" { 99 | app.WwwRoot = "." 100 | } 101 | 102 | // gHandler.StaticFS("/", gin.Dir(r.WwwRoot, true)) 103 | ghttp.StaticFS(&gHandler.RouterGroup, "/", http.Dir(app.WwwRoot)) 104 | 105 | // HTTP SERVER 106 | host := app.Host 107 | port := app.Port 108 | 109 | var addrHTTP string 110 | // var addrHTTPS string 111 | 112 | if port != "" { 113 | addrHTTP = host + ":" + port 114 | } else { 115 | // Get the free port from 8080 116 | addrHTTP = ":" + gnet.GetAvailablePort(8080) 117 | } 118 | 119 | httpServer := &http.Server{ 120 | Addr: addrHTTP, 121 | Handler: gHandler, 122 | ReadTimeout: time.Duration(app.ReadTimeout) * time.Second, 123 | WriteTimeout: time.Duration(app.WriteTimeout) * time.Second, 124 | MaxHeaderBytes: 1 << 20, 125 | } 126 | 127 | if !app.EnableHTTPS { 128 | tools.DebugPrintF("[INFO] HTTPS was disabled.") 129 | gracefulStart(httpServer) 130 | return 131 | } 132 | 133 | // HTTP SERVER 134 | httpsport := app.HTTPSPort 135 | 136 | var addrHTTPS string 137 | 138 | if httpsport != "" { 139 | addrHTTPS = host + ":" + httpsport 140 | } else { 141 | // Get the free port from 8443 142 | addrHTTPS = ":" + gnet.GetAvailablePort(8443) 143 | } 144 | 145 | // Load cert and key 146 | var certPEMBlock, keyPEMBlock []byte 147 | var err error 148 | var cert tls.Certificate 149 | var getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error) 150 | 151 | // load From Local Cert 152 | if app.HTTPSCertFile != "" && app.HTTPSKeyFile != "" { 153 | cert, err = tls.LoadX509KeyPair(app.HTTPSCertFile, app.HTTPSKeyFile) 154 | } else { 155 | err = errors.New("app.HTTPSCertFile is Empty or app.HTTPSKeyFile is empty") 156 | } 157 | 158 | // load From Auto Cert 159 | if err != nil { 160 | if app.Port == "80" && app.HTTPSPort == "443" && app.HTTPSDomains != nil && len(app.HTTPSDomains) > 0 && app.ContactEmail != "" { 161 | httpscertsdir := "certs" 162 | 163 | if app.HTTPSCertsDir != "" { 164 | httpscertsdir = app.HTTPSCertsDir 165 | } 166 | 167 | certManager := autocert.Manager{ 168 | Prompt: autocert.AcceptTOS, 169 | HostPolicy: autocert.HostWhitelist(app.HTTPSDomains...), //your domain here 170 | Cache: autocert.DirCache(httpscertsdir), //folder for storing certificates 171 | Email: app.ContactEmail, 172 | } 173 | 174 | getCertificate = certManager.GetCertificate 175 | } 176 | } 177 | 178 | // load From Embde Cert 179 | if err != nil && getCertificate == nil { 180 | certPEMBlock, err = ghttps.GetTLSCerts().ReadFile("certs/server.pem") 181 | 182 | if err != nil { 183 | log.Fatal(err) 184 | } 185 | 186 | keyPEMBlock, err = ghttps.GetTLSCerts().ReadFile("certs/server.key") 187 | 188 | if err != nil { 189 | log.Fatal(err) 190 | } 191 | 192 | cert, err = tls.X509KeyPair(certPEMBlock, keyPEMBlock) 193 | 194 | if err != nil { 195 | log.Fatal(err) 196 | } 197 | } 198 | 199 | // Construct a tls.config 200 | tlsConfig := &tls.Config{ 201 | Certificates: []tls.Certificate{cert}, 202 | GetCertificate: getCertificate, 203 | MinVersion: tls.VersionTLS12, 204 | MaxVersion: tls.VersionTLS13, 205 | } 206 | 207 | httpsServer := &http.Server{ 208 | Addr: addrHTTPS, 209 | TLSConfig: tlsConfig, 210 | Handler: gHandler, 211 | ReadTimeout: time.Duration(app.ReadTimeout) * time.Second, 212 | WriteTimeout: time.Duration(app.WriteTimeout) * time.Second, 213 | MaxHeaderBytes: 1 << 20, 214 | } 215 | 216 | gracefulStart(httpServer, httpsServer) 217 | }, 218 | } 219 | 220 | rootCmd.Flags().StringVarP(configs.ConfigFile(), "config", "c", "", `If it is not set, we will try with development.(json/env/ini/yaml/toml/hcl/properties`) 221 | 222 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.Port, "port", "P", configs.GetConfigs().App.Port, `Port optionally specifies the TCP Port for the server to listen on, 223 | in the form "host:port". If empty, ":port" (port 8080) is used.`) 224 | 225 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.Host, "host", "H", configs.GetConfigs().App.Host, `Host optionally specifies the Http Address for the server to listen on, 226 | in the form "host:port". If empty, "host:" (host localhost) is used.`) 227 | 228 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.WwwRoot, "wwwroot", "w", configs.GetConfigs().App.WwwRoot, `By default, the wwwroot folder is treated as a web root folder. 229 | Static files can be stored in any folder under the web root and accessed with a relative path to that root.`) 230 | 231 | rootCmd.Flags().Int64VarP(&configs.GetConfigs().App.ReadTimeout, "read-timeout", "", configs.GetConfigs().App.ReadTimeout, `ReadTimeout is the maximum duration for reading the entire 232 | request, including the body. A zero or negative value means 233 | there will be no timeout. 234 | 235 | Because ReadTimeout does not let Handlers make per-request 236 | decisions on each request body's acceptable deadline or 237 | upload rate, most users will prefer to use 238 | ReadHeaderTimeout. It is valid to use them both.`) 239 | 240 | rootCmd.Flags().Int64VarP(&configs.GetConfigs().App.WriteTimeout, "write-timeout", "", configs.GetConfigs().App.WriteTimeout, `WriteTimeout is the maximum duration before timing out 241 | writes of the response. It is reset whenever a new 242 | request's header is read. Like ReadTimeout, it does not 243 | let Handlers make decisions on a per-request basis. 244 | A zero or negative value means there will be no timeout.`) 245 | 246 | rootCmd.Flags().BoolVarP(&configs.GetConfigs().App.EnableHTTPS, "enable-https", "", configs.GetConfigs().App.EnableHTTPS, `If it is set, we will enable https.`) 247 | 248 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.HTTPSPort, "https-port", "", configs.GetConfigs().App.HTTPSPort, `HTTPS PORT`) 249 | 250 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.HTTPSCertFile, "https-cert-file", "", configs.GetConfigs().App.HTTPSCertFile, `HTTPS Cert File`) 251 | 252 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.HTTPSKeyFile, "https-key-file", "", configs.GetConfigs().App.HTTPSKeyFile, `HTTPS Key File`) 253 | 254 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.HTTPSCertsDir, "https-cert-dir", "", configs.GetConfigs().App.HTTPSCertsDir, `HTTPS Cert Directory`) 255 | 256 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.ContactEmail, "contact-email", "", configs.GetConfigs().App.ContactEmail, `HTTPS Contact Email`) 257 | 258 | rootCmd.Flags().StringArrayVarP(&configs.GetConfigs().App.HTTPSDomains, "https-domains", "", configs.GetConfigs().App.HTTPSDomains, `HTTPS Domains`) 259 | 260 | rootCmd.Flags().BoolVarP(&configs.GetConfigs().App.Gzip, "gzip", "g", configs.GetConfigs().App.Gzip, `If it is set, we will compress with gzip.`) 261 | 262 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.AutoIndexTimeFormat, "autoindex-time-format", "", configs.GetConfigs().App.AutoIndexTimeFormat, `this is the AutoIndex Time Format.`) 263 | 264 | rootCmd.Flags().BoolVarP(&configs.GetConfigs().App.AutoIndexExactSize, "autoindex-exact-size", "", configs.GetConfigs().App.AutoIndexExactSize, `For the HTML format, specifies whether exact file sizes should be output in the directory listing, 265 | or rather rounded to kilobytes, megabytes, and gigabytes.`) 266 | 267 | rootCmd.Flags().BoolVarP(&configs.GetConfigs().App.PreviewHTML, "preview-html", "", configs.GetConfigs().App.PreviewHTML, `For static web files, Whether preview them.`) 268 | 269 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.LogDir, "log-dir", "", configs.GetConfigs().App.LogDir, `The Log Directory which store access.log, error.log etc.`) 270 | 271 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.RateLimiter, "rate-limiter", "", configs.GetConfigs().App.RateLimiter, `Define a limit rate to several requests per hour. 272 | You can also use the simplified format "-"", with the given 273 | periods: 274 | 275 | * "S": second 276 | * "M": minute 277 | * "H": hour 278 | * "D": day 279 | 280 | Examples: 281 | 282 | * 5 reqs/second: "5-S" 283 | * 10 reqs/minute: "10-M" 284 | * 1000 reqs/hour: "1000-H" 285 | * 2000 reqs/day: "2000-D" 286 | `) 287 | 288 | rootCmd.Flags().Int64VarP(&configs.GetConfigs().App.SpeedLimiter, "speed-limiter", "", configs.GetConfigs().App.SpeedLimiter, ` Specify the maximum transfer rate you want curl to use - for 289 | downloads. 290 | The given speed is measured in bytes/second, `) 291 | 292 | rootCmd.Flags().BoolVarP(&configs.GetConfigs().App.RefererLimiter, "referer-limiter", "", configs.GetConfigs().App.RefererLimiter, `Limit by referer`) 293 | 294 | rootCmd.Flags().BoolVarP(&configs.GetConfigs().App.Basic, "basic", "", configs.GetConfigs().App.Basic, `If it is set, we will use HTTP Basic authentication. 295 | 296 | Used together with -u, --user . 297 | 298 | Providing --basic multiple times has no extra effect. 299 | 300 | Example:`+env.ProjectName+`-u name:password --basic https://example.com`) 301 | 302 | rootCmd.Flags().StringVarP(&configs.GetConfigs().App.User, "user", "u", configs.GetConfigs().App.User, `Specify the user name and password to use for server authentication. 303 | 304 | The user name and passwords are split up on the first colon, 305 | which makes it impossible to use a colon in the user name with 306 | this option. The password can, still.`) 307 | } 308 | 309 | // Execute start the web server 310 | func Execute() { 311 | if err := rootCmd.Execute(); err != nil { 312 | log.Fatalln(err) 313 | } 314 | } 315 | 316 | func welcome() { 317 | myFigure := figure.NewColorFigure("GSERVER", "larry3d", "green", true) 318 | myFigure.Blink(1000, 1000, 0) 319 | figureString := myFigure.ColorString() 320 | 321 | fmt.Fprint(tools.DefaultGinWriter, figureString) 322 | fmt.Fprint(tools.DefaultGinWriter, "\n\n\n") 323 | } 324 | 325 | func gracefulStart(servers ...*http.Server) { 326 | var err error 327 | 328 | for _, server := range servers { 329 | // Initializing the server in a goroutine so that 330 | // it won't block the graceful shutdown handling below 331 | go func(server *http.Server) { 332 | if server.TLSConfig != nil { 333 | //https 334 | tools.DebugPrintF("[INFO] Listening and Serving HTTPS on %s\n", server.Addr) 335 | 336 | app := configs.GetAppConfig() 337 | 338 | if !app.EnableHTTPS { 339 | tools.DebugPrintF("[INFO] Hit CTRL-C to stop the server") 340 | } 341 | 342 | if err := server.ListenAndServeTLS(app.HTTPSCertFile, app.HTTPSKeyFile); err != nil && err != http.ErrServerClosed { 343 | log.Fatalf("listen: %s\n", err) 344 | } 345 | } else { 346 | //http 347 | tools.DebugPrintF("[INFO] Listening and Serving HTTP on %s\n", server.Addr) 348 | 349 | app := configs.GetAppConfig() 350 | 351 | if app.EnableHTTPS { 352 | tools.DebugPrintF("[INFO] Hit CTRL-C to stop the server") 353 | } 354 | 355 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 356 | log.Fatalf("listen: %s\n", err) 357 | } 358 | } 359 | }(server) 360 | } 361 | 362 | // Wait for interrupt signal to gracefully shutdown the server with 363 | // a timeout of 5 seconds. 364 | quit := make(chan os.Signal, 1) 365 | // kill (no param) default send syscall.SIGTERM 366 | // kill -2 is syscall.SIGINT 367 | // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it 368 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 369 | <-quit 370 | tools.DebugPrintF("[INFO] Shutting down servers...") 371 | 372 | // The context is used to inform the server it has 5 seconds to finish 373 | // the request it is currently handling 374 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 375 | defer cancel() 376 | 377 | for _, server := range servers { 378 | if err = server.Shutdown(ctx); err != nil { 379 | log.Fatal("The Web Server forced to shutdown: ", err) 380 | } 381 | } 382 | 383 | tools.DebugPrintF("[INFO] The Web Servers have been shut down.") 384 | } 385 | --------------------------------------------------------------------------------