From aa5d6b0f103bb606edfe612d472979fdd6efb8d4 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 11 Nov 2025 10:13:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=AD=98=E5=82=A8=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/go-test.yml | 2 +- .gitignore | 3 +- .golangci.yml | 183 ++++++++++++++++++++++++++++++++++ Makefile | 12 ++- README.md | 2 +- README_zh.md | 2 +- config.go | 12 ++- config.yaml | 24 +++-- main.go | 31 +++++- pkg/core/alias.go | 2 +- pkg/core/backend.go | 3 +- pkg/core/backend_cache.go | 1 - pkg/core/domain.go | 26 ++--- pkg/core/meta.go | 72 +++++++------ pkg/providers/gitea.go | 45 +++++---- pkg/server.go | 61 ++++++------ tests/core/dummy.go | 10 +- tests/core/test.go | 2 +- tests/pages_core_test.go | 38 +++---- tests/pages_proxy_test.go | 20 ++-- tests/pages_render_test.go | 2 + 21 files changed, 388 insertions(+), 165 deletions(-) create mode 100644 .golangci.yml diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 53e90dd..e123cf2 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -28,7 +28,7 @@ jobs: run: go mod download - name: Run tests with coverage - run: go test -v -coverprofile=coverage.txt ./... + run: make test lint - uses: codecov/codecov-action@v5 with: diff --git a/.gitignore b/.gitignore index 1052c35..ca10e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,5 @@ fabric.properties config-local.yaml gitea-pages *.zip -dist/ \ No newline at end of file +dist/ +coverage.txt \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e033ccf --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,183 @@ +# fork from gitea https://github.com/go-gitea/gitea/blob/main/.golangci.yml +version: "2" +output: + sort-order: + - file +linters: + default: none + enable: + - bidichk + - depguard + - dupl + - errcheck + - forbidigo + - gocritic + - govet + - ineffassign + - mirror + - nakedret + - nolintlint + - perfsprint + - revive + - staticcheck + - testifylint + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - wastedassign + settings: + depguard: + rules: + main: + deny: + - pkg: github.com/unknwon/com + desc: use gitea's util and replacements + - pkg: io/ioutil + desc: use os or io instead + - pkg: golang.org/x/exp + desc: it's experimental and unreliable + - pkg: code.gitea.io/gitea/modules/git/internal + desc: do not use the internal package, use AddXxx function instead + - pkg: gopkg.in/ini.v1 + desc: do not use the ini package, use gitea's config system instead + - pkg: gitea.com/go-chi/cache + desc: do not use the go-chi cache package, use gitea's cache system + nolintlint: + allow-unused: false + require-explanation: true + require-specific: true + gocritic: + enabled-checks: + - equalFold + disabled-checks: + - ifElseChain + - singleCaseSwitch # Every time this occurred in the code, there was no other way. + revive: + severity: error + rules: + - name: atomic + - name: bare-return + - name: blank-imports + - name: constant-logical-expr + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: duplicated-imports + - name: empty-lines + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: identical-branches + - name: if-return + - name: increment-decrement + - name: indent-error-flow + - name: modifies-value-receiver + - name: package-comments + - name: range + - name: receiver-naming + - name: redefines-builtin-id + - name: string-of-int + - name: superfluous-else + - name: time-naming + - name: unconditional-recursion + - name: unexported-return + - name: unreachable-code + - name: var-declaration + - name: var-naming + arguments: + - [] # AllowList - do not remove as args for the rule are positional and won't work without lists first + - [] # DenyList + - - skip-package-name-checks: true # supress errors from underscore in migration packages + staticcheck: + checks: + - all + - -ST1003 + - -ST1005 + - -QF1001 + - -QF1006 + - -QF1008 + testifylint: + disable: + - go-require + - require-error + usetesting: + os-temp-dir: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - dupl + - errcheck + - gocyclo + - gosec + - staticcheck + - unparam + path: _test\.go + - linters: + - dupl + - errcheck + - gocyclo + - gosec + path: models/migrations/v + - linters: + - forbidigo + path: cmd + - linters: + - dupl + text: (?i)webhook + - linters: + - gocritic + text: (?i)`ID' should not be capitalized + - linters: + - deadcode + - unused + text: (?i)swagger + - linters: + - staticcheck + text: (?i)argument x is overwritten before first use + - linters: + - gocritic + text: '(?i)commentFormatting: put a space between `//` and comment text' + - linters: + - gocritic + text: '(?i)exitAfterDefer:' + paths: + - node_modules + - .venv + - public + - web_src + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofmt + - gofumpt + settings: + gofumpt: + extra-rules: true + exclusions: + generated: lax + paths: + - node_modules + - .venv + - public + - web_src + - third_party$ + - builtin$ + - examples$ + +run: + timeout: 10m \ No newline at end of file diff --git a/Makefile b/Makefile index 4165b73..f008b9d 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ debug: gitea-pages .PHONY: test test: - @go test -v ./... + @go test -v -coverprofile=coverage.txt ./... .PHONY: releases @@ -44,3 +44,13 @@ releases: make release GOOS=linux GOARCH=arm64 && \ make release GOOS=linux GOARCH=loong64 && \ make release GOOS=windows GOARCH=amd64 + +.PHONY: lint +lint: + @(test -f "$(GOPATH)/bin/golangci-lint" || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0) && \ + "$(GOPATH)/bin/golangci-lint" run -c .golangci.yml + +.PHONY: lint-fix +lint-fix: + @(test -f "$(GOPATH)/bin/golangci-lint" || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0) && \ + "$(GOPATH)/bin/golangci-lint" run -c .golangci.yml --fix diff --git a/README.md b/README.md index d62b7d5..63e627e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This project focuses on functional implementation and does not consider any perf ## Get Started -Install `go1.24` or later, along with the `Make` tool, and then execute the following command: +Install `go1.25` or later, along with the `Make` tool, and then execute the following command: ```bash make gitea-pages diff --git a/README_zh.md b/README_zh.md index a6a2a24..0f8f90f 100644 --- a/README_zh.md +++ b/README_zh.md @@ -10,7 +10,7 @@ ## Get Started -安装 `go1.24` 或更高版本,同时安装 `Make` 工具 ,然后执行如下命令: +安装 `go1.25` 或更高版本,同时安装 `Make` 工具 ,然后执行如下命令: ```bash make gitea-pages diff --git a/config.go b/config.go index 1137367..40dc5d2 100644 --- a/config.go +++ b/config.go @@ -24,7 +24,7 @@ type Config struct { Bind string `yaml:"bind"` // HTTP 绑定 Domain string `yaml:"domain"` // 基础域名 - Config string `yaml:"config"` // 配置 + Database ConfigDatabase `yaml:"database"` // 配置 Auth ConfigAuth `yaml:"auth"` // 后端认证配置 @@ -46,7 +46,7 @@ func (c *Config) NewPageServerOptions() (*pkg.ServerOptions, error) { } var err error - if c.Config == "" { + if c.Database.URL == "" { return nil, errors.New("config is required") } if c.StaticDir != "" { @@ -88,7 +88,7 @@ func (c *Config) NewPageServerOptions() (*pkg.ServerOptions, error) { if err != nil { return nil, errors.Wrap(err, "create cache") } - alias, err := kv.NewKVFromURL(c.Config) + alias, err := kv.NewKVFromURL(c.Database.URL) if err != nil { return nil, errors.Wrapf(err, "failed to init alias config") } @@ -105,7 +105,7 @@ func (c *Config) NewPageServerOptions() (*pkg.ServerOptions, error) { CacheBlob: memoryCache, CacheBlobTTL: c.Cache.BlobTTL, CacheBlobLimit: uint64(c.Cache.BlobLimit), - HttpClient: http.DefaultClient, + HTTPClient: http.DefaultClient, EnableRender: c.Render.Enable, EnableProxy: c.Proxy.Enable, StaticDir: c.StaticDir, @@ -151,6 +151,10 @@ type ConfigPage struct { ErrUnknownPage string `yaml:"500"` } +type ConfigDatabase struct { + URL string `yaml:"url"` +} + type ConfigProxy struct { Enable bool `yaml:"enable"` // 是否允许反向代理 } diff --git a/config.yaml b/config.yaml index 1615b6f..0f5183a 100644 --- a/config.yaml +++ b/config.yaml @@ -2,20 +2,26 @@ bind: 127.0.0.1:18080 # 基础域名 domain: example.com +# 持久化存储 +database: + # 持久化存储配置 + url: "memory://" auth: + type: gitea server: https://gitea.com # 需要 user , org , repo 的 read 权限 token: token cache: - # 配置存储 - storage: /path/to/config.json - # 配置缓存时长 - ttl: 10m - - # 单个文件最大缓存大小 - size: 10MB - # 总缓存大小 - max: 1GB + # 元数据缓存 + meta: "memory://" + # 元数据缓存时长 + meta_ttl: 1m + # 响应数据缓存 + blob: "memory://" + # 响应数据缓存时长 + blob_ttl: 1m + # 最大单个响应数据缓存大小 + blob_limit: 10MB page: # 默认页面分支 default_branch: gh-pages diff --git a/main.go b/main.go index 93b2280..acd1203 100644 --- a/main.go +++ b/main.go @@ -10,25 +10,48 @@ import ( "syscall" "go.uber.org/zap" + "gopkg.in/yaml.v3" "gopkg.d7z.net/gitea-pages/pkg" "gopkg.d7z.net/gitea-pages/pkg/providers" + _ "gopkg.d7z.net/gitea-pages/pkg/renders" ) var ( configPath = "config-local.yaml" debug = false + generate = false ) func init() { flag.StringVar(&configPath, "conf", configPath, "config file path") + flag.BoolVar(&generate, "generate", debug, "generate config file") flag.BoolVar(&debug, "debug", debug, "debug mode") + flag.Parse() } func main() { - flag.Parse() + if generate { + var cfg Config + file, err := os.ReadFile(configPath) + if err == nil { + _ = yaml.Unmarshal(file, &cfg) + } + out, err := yaml.Marshal(&cfg) + if err != nil { + log.Fatal("marshal config file failed", zap.Error(err)) + } + err = os.WriteFile(configPath, out, 0o644) + if err != nil { + log.Fatal("write config file failed", zap.Error(err)) + } + return + } + call := logInject() - defer call() + defer func() { + _ = call() + }() config, err := LoadConfig(configPath) if err != nil { log.Fatalf("fail to load config file: %v", err) @@ -48,9 +71,7 @@ func main() { svc := http.Server{Addr: config.Bind, Handler: giteaServer} go func() { - select { - case <-ctx.Done(): - } + <-ctx.Done() zap.L().Debug("shutdown gracefully") _ = svc.Close() }() diff --git a/pkg/core/alias.go b/pkg/core/alias.go index b26cfe2..10b861d 100644 --- a/pkg/core/alias.go +++ b/pkg/core/alias.go @@ -45,7 +45,7 @@ func (a *DomainAlias) Bind(ctx context.Context, domains []string, owner, repo, b return err } } - if domains == nil || len(domains) == 0 { + if len(domains) == 0 { return nil } aliasMeta := &Alias{ diff --git a/pkg/core/backend.go b/pkg/core/backend.go index ea432ff..a322e82 100644 --- a/pkg/core/backend.go +++ b/pkg/core/backend.go @@ -2,6 +2,7 @@ package core import ( "context" + "io" "net/http" "time" ) @@ -12,7 +13,7 @@ type BranchInfo struct { } type Backend interface { - Close() error + io.Closer // Repos return repo name + default branch Repos(ctx context.Context, owner string) (map[string]string, error) // Branches return branch + commit id diff --git a/pkg/core/backend_cache.go b/pkg/core/backend_cache.go index 8aa29db..d579e18 100644 --- a/pkg/core/backend_cache.go +++ b/pkg/core/backend_cache.go @@ -57,7 +57,6 @@ func (c *CacheBackend) Repos(ctx context.Context, owner string) (map[string]stri } func (c *CacheBackend) Branches(ctx context.Context, owner, repo string) (map[string]*BranchInfo, error) { - ret := make(map[string]*BranchInfo) key := fmt.Sprintf("%s/%s", owner, repo) if load, b := c.cacheBranch.Load(ctx, key); b { return load, nil diff --git a/pkg/core/domain.go b/pkg/core/domain.go index ab4d8da..f8f5ff2 100644 --- a/pkg/core/domain.go +++ b/pkg/core/domain.go @@ -70,24 +70,24 @@ func (p *PageDomain) ParseDomainMeta(ctx context.Context, domain, path, branch s return p.ReturnMeta(ctx, owner, domain, branch, pathArr) } -func (p *PageDomain) ReturnMeta(ctx context.Context, owner string, repo string, branch string, path []string) (*PageDomainContent, error) { +func (p *PageDomain) ReturnMeta(ctx context.Context, owner, repo, branch string, path []string) (*PageDomainContent, error) { rel := &PageDomainContent{} - if meta, err := p.GetMeta(ctx, owner, repo, branch); err == nil { - rel.PageMetaContent = meta - rel.Owner = owner - rel.Repo = repo - rel.Path = strings.Join(path, "/") - if err = p.alias.Bind(ctx, meta.Alias, rel.Owner, rel.Repo, branch); err != nil { - zap.L().Warn("别名绑定失败", zap.Error(err)) - return nil, err - } - return rel, nil - } else { + meta, err := p.GetMeta(ctx, owner, repo, branch) + if err != nil { zap.L().Debug("查询错误", zap.Error(err)) if meta != nil { // 解析错误汇报 return nil, errors.New(meta.ErrorMsg) } + return nil, errors.Wrap(os.ErrNotExist, strings.Join(path, "/")) } - return nil, errors.Wrap(os.ErrNotExist, strings.Join(path, "/")) + rel.PageMetaContent = meta + rel.Owner = owner + rel.Repo = repo + rel.Path = strings.Join(path, "/") + if err = p.alias.Bind(ctx, meta.Alias, rel.Owner, rel.Repo, branch); err != nil { + zap.L().Warn("别名绑定失败", zap.Error(err)) + return nil, err + } + return rel, nil } diff --git a/pkg/core/meta.go b/pkg/core/meta.go index 01f7b73..04b0af6 100644 --- a/pkg/core/meta.go +++ b/pkg/core/meta.go @@ -2,7 +2,6 @@ package core import ( "context" - "fmt" "io" "net/http" "net/url" @@ -31,7 +30,7 @@ type ServerMeta struct { Domain string client *http.Client - + cache kv.KV ttl time.Duration @@ -44,29 +43,29 @@ func NewServerMeta(client *http.Client, backend Backend, kv kv.KV, domain string func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (*PageMetaContent, error) { rel := NewPageMetaContent() - if repos, err := s.Repos(ctx, owner); err != nil { + repos, err := s.Repos(ctx, owner) + if err != nil { return nil, err - } else { - defBranch := repos[repo] - if defBranch == "" { - return nil, os.ErrNotExist - } - if branch == "" { - branch = defBranch - } } - if branches, err := s.Branches(ctx, owner, repo); err != nil { + defBranch := repos[repo] + if defBranch == "" { + return nil, os.ErrNotExist + } + if branch == "" { + branch = defBranch + } + branches, err := s.Branches(ctx, owner, repo) + if err != nil { return nil, err - } else { - info := branches[branch] - if info == nil { - return nil, os.ErrNotExist - } - rel.CommitID = info.ID - rel.LastModified = info.LastModified } + info := branches[branch] + if info == nil { + return nil, os.ErrNotExist + } + rel.CommitID = info.ID + rel.LastModified = info.LastModified - key := fmt.Sprintf("meta/%s/%s/%s", owner, repo, branch) + key := s.cache.WithKey("meta", owner, repo, branch) cache, err := s.cache.Get(ctx, key) if err != nil && !errors.Is(err, os.ErrNotExist) { return nil, err @@ -97,14 +96,13 @@ func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (* rel.IsPage = false _ = s.cache.Put(ctx, key, rel.String(), s.ttl) return nil, os.ErrNotExist - } else { - rel.IsPage = true } - errFunc := func(err error) (*PageMetaContent, error) { + rel.IsPage = true + errCall := func(err error) error { rel.IsPage = false rel.ErrorMsg = err.Error() _ = s.cache.Put(ctx, key, rel.String(), s.ttl) - return nil, err + return err } // 添加默认跳过的内容 for _, defIgnore := range rel.Ignore { @@ -114,7 +112,7 @@ func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (* if data, err := s.ReadString(ctx, owner, repo, rel.CommitID, ".pages.yaml"); err == nil { cfg := new(PageConfig) if err = yaml.Unmarshal([]byte(data), cfg); err != nil { - return errFunc(err) + return nil, errCall(err) } rel.VRoute = cfg.VirtualRoute // 处理 CNAME @@ -123,14 +121,14 @@ func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (* if regexpHostname.MatchString(cname) && !strings.HasSuffix(strings.ToLower(cname), strings.ToLower(s.Domain)) { rel.Alias = append(rel.Alias, cname) } else { - return errFunc(errors.New("invalid alias name " + cname)) + return nil, errCall(errors.New("invalid alias name " + cname)) } } // 处理渲染器 for sType, pattern := range cfg.Renders() { var r Render if r = GetRender(sType); r == nil { - return errFunc(errors.Errorf("render not found %s", sType)) + return nil, errCall(errors.Errorf("render not found %s", sType)) } if g, err := glob.Compile(strings.TrimSpace(pattern)); err == nil { rel.rendersL = append(rel.rendersL, &renderCompiler{ @@ -138,7 +136,7 @@ func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (* Render: r, }) } else { - return errFunc(err) + return nil, errCall(err) } rel.Renders[sType] = append(rel.Renders[sType], pattern) } @@ -147,7 +145,7 @@ func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (* if g, err := glob.Compile(pattern); err == nil { rel.ignoreL = append(rel.ignoreL, g) } else { - return errFunc(err) + return nil, errCall(err) } rel.Ignore = append(rel.Ignore, pattern) } @@ -157,17 +155,15 @@ func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (* if !strings.HasPrefix(path, "/") { path = "/" + path } - if strings.HasSuffix(path, "/") { - path = path[:len(path)-1] + path = strings.TrimSuffix(path, "/") + var rURL *url.URL + if rURL, err = url.Parse(backend); err != nil { + return nil, errCall(err) } - var rUrl *url.URL - if rUrl, err = url.Parse(backend); err != nil { - return errFunc(err) + if rURL.Scheme != "http" && rURL.Scheme != "https" { + return nil, errCall(errors.New("invalid backend url " + backend)) } - if rUrl.Scheme != "http" && rUrl.Scheme != "https" { - return errFunc(errors.New("invalid backend url " + backend)) - } - rel.Proxy[path] = rUrl.String() + rel.Proxy[path] = rURL.String() } } else { // 不存在配置,但也可以重定向 diff --git a/pkg/providers/gitea.go b/pkg/providers/gitea.go index 6382598..f37ad54 100644 --- a/pkg/providers/gitea.go +++ b/pkg/providers/gitea.go @@ -14,25 +14,25 @@ import ( const GiteaMaxCount = 9999 type ProviderGitea struct { - BaseUrl string + BaseURL string Token string gitea *gitea.Client } -func NewGitea(url string, token string) (*ProviderGitea, error) { +func NewGitea(url, token string) (*ProviderGitea, error) { client, err := gitea.NewClient(url, gitea.SetGiteaVersion(""), gitea.SetToken(token)) if err != nil { return nil, err } return &ProviderGitea{ - BaseUrl: url, + BaseURL: url, Token: token, gitea: client, }, nil } -func (g *ProviderGitea) Repos(ctx context.Context, owner string) (map[string]string, error) { +func (g *ProviderGitea) Repos(_ context.Context, owner string) (map[string]string, error) { result := make(map[string]string) if repos, resp, err := g.gitea.ListOrgRepos(owner, gitea.ListOrgReposOptions{ ListOptions: gitea.ListOptions{ @@ -75,26 +75,26 @@ func (g *ProviderGitea) Repos(ctx context.Context, owner string) (map[string]str return result, nil } -func (g *ProviderGitea) Branches(ctx context.Context, owner, repo string) (map[string]*core.BranchInfo, error) { +func (g *ProviderGitea) Branches(_ context.Context, owner, repo string) (map[string]*core.BranchInfo, error) { result := make(map[string]*core.BranchInfo) - if branches, resp, err := g.gitea.ListRepoBranches(owner, repo, gitea.ListRepoBranchesOptions{ + branches, resp, err := g.gitea.ListRepoBranches(owner, repo, gitea.ListRepoBranchesOptions{ ListOptions: gitea.ListOptions{ PageSize: GiteaMaxCount, }, - }); err != nil { + }) + if err != nil { if resp != nil { _ = resp.Body.Close() } return nil, err - } else { - if resp != nil { - _ = resp.Body.Close() - } - for _, branch := range branches { - result[branch.Name] = &core.BranchInfo{ - ID: branch.Commit.ID, - LastModified: branch.Commit.Timestamp, - } + } + if resp != nil { + _ = resp.Body.Close() + } + for _, branch := range branches { + result[branch.Name] = &core.BranchInfo{ + ID: branch.Commit.ID, + LastModified: branch.Commit.Timestamp, } } if len(result) == 0 { @@ -104,7 +104,10 @@ func (g *ProviderGitea) Branches(ctx context.Context, owner, repo string) (map[s } func (g *ProviderGitea) Open(ctx context.Context, client *http.Client, owner, repo, commit, path string, headers http.Header) (*http.Response, error) { - giteaURL, err := url.JoinPath(g.BaseUrl, "api/v1/repos", owner, repo, "media", path) + if headers == nil { + headers = make(http.Header) + } + giteaURL, err := url.JoinPath(g.BaseURL, "api/v1/repos", owner, repo, "media", path) if err != nil { return nil, err } @@ -113,11 +116,9 @@ func (g *ProviderGitea) Open(ctx context.Context, client *http.Client, owner, re if err != nil { return nil, err } - if headers != nil { - for key, values := range headers { - for _, value := range values { - req.Header.Add(key, value) - } + for key, values := range headers { + for _, value := range values { + req.Header.Add(key, value) } } req.Header.Add("Authorization", "token "+g.Token) diff --git a/pkg/server.go b/pkg/server.go index 9a8330d..91ee4ab 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -12,6 +12,7 @@ import ( "path/filepath" "regexp" "slices" + "strconv" "strings" "time" @@ -19,19 +20,19 @@ import ( "gopkg.d7z.net/middleware/cache" "gopkg.d7z.net/middleware/kv" + stdErr "errors" + "github.com/pkg/errors" "go.uber.org/zap" "gopkg.d7z.net/gitea-pages/pkg/core" "gopkg.d7z.net/gitea-pages/pkg/utils" - - _ "gopkg.d7z.net/gitea-pages/pkg/renders" ) var portExp = regexp.MustCompile(`:\d+$`) type ServerOptions struct { - Domain string //默认域名 + Domain string // 默认域名 DefaultBranch string // 默认分支 Alias kv.KV // 配置映射关系 @@ -43,7 +44,7 @@ type ServerOptions struct { CacheBlobTTL time.Duration // 配置缓存时长 CacheBlobLimit uint64 // blob最大缓存大小 - HttpClient *http.Client //自定义客户端 + HTTPClient *http.Client // 自定义客户端 EnableRender bool // 允许渲染 EnableProxy bool // 允许代理 @@ -68,7 +69,7 @@ func DefaultOptions(domain string) ServerOptions { CacheBlobTTL: time.Minute, CacheBlobLimit: 1024 * 1024 * 10, - HttpClient: http.DefaultClient, + HTTPClient: http.DefaultClient, EnableRender: true, EnableProxy: true, @@ -95,9 +96,9 @@ var staticPrefix = "/.well-known/page-server/" func NewPageServer(backend core.Backend, options ServerOptions) *Server { backend = core.NewCacheBackend(backend, options.CacheMeta, options.CacheMetaTTL) - svcMeta := core.NewServerMeta(options.HttpClient, backend, options.CacheMeta, options.Domain, options.CacheMetaTTL) + svcMeta := core.NewServerMeta(options.HTTPClient, backend, options.CacheMeta, options.Domain, options.CacheMetaTTL) pageMeta := core.NewPageDomain(svcMeta, options.Alias, options.Domain, options.DefaultBranch) - reader := core.NewCacheBackendBlobReader(options.HttpClient, backend, options.CacheBlob, options.CacheBlobLimit) + reader := core.NewCacheBackendBlobReader(options.HTTPClient, backend, options.CacheBlob, options.CacheBlobLimit) var fs http.Handler if options.StaticDir != "" { fs = http.StripPrefix(staticPrefix, http.FileServer(http.Dir(options.StaticDir))) @@ -112,15 +113,15 @@ func NewPageServer(backend core.Backend, options ServerOptions) *Server { } func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - sessionId, _ := uuid.NewRandom() - request.Header.Set("Session-ID", sessionId.String()) + sessionID, _ := uuid.NewRandom() + request.Header.Set("Session-ID", sessionID.String()) if s.fs != nil && strings.HasPrefix(request.URL.Path, staticPrefix) { s.fs.ServeHTTP(writer, request) return } defer func() { if e := recover(); e != nil { - zap.L().Error("panic!", zap.Any("error", e), zap.Any("id", sessionId)) + zap.L().Error("panic!", zap.Any("error", e), zap.Any("id", sessionID)) if err, ok := e.(error); ok { s.options.DefaultErrorHandler(writer, request, err) } @@ -128,7 +129,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { }() err := s.Serve(writer, request) if err != nil { - zap.L().Debug("错误请求", zap.Error(err), zap.Any("request", request.RequestURI), zap.Any("id", sessionId)) + zap.L().Debug("错误请求", zap.Error(err), zap.Any("request", request.RequestURI), zap.Any("id", sessionID)) s.options.DefaultErrorHandler(writer, request, err) } } @@ -144,9 +145,9 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error if err != nil { return err } - zap.L().Debug("获取请求", zap.Any("request", meta.Path)) + zap.L().Debug("new request", zap.Any("request path", meta.Path)) if len(meta.Alias) > 0 && !slices.Contains(meta.Alias, domainHost) { - zap.L().Debug("重定向地址", zap.Any("src", request.Host), zap.Any("dst", meta.Alias[0])) + zap.L().Debug("redirect", zap.Any("src", request.Host), zap.Any("dst", meta.Alias[0])) http.Redirect(writer, request, fmt.Sprintf("https://%s/%s", meta.Alias[0], meta.Path), http.StatusFound) return nil } @@ -163,7 +164,7 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error request.URL.Path = targetPath request.RequestURI = request.URL.RequestURI() proxy := httputil.NewSingleHostReverseProxy(u) - proxy.Transport = s.options.HttpClient.Transport + proxy.Transport = s.options.HTTPClient.Transport if host, _, err := net.SplitHostPort(request.RemoteAddr); err == nil { request.Header.Set("X-Real-IP", host) @@ -181,11 +182,11 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error } // 在非反向代理时处理目录访问 if strings.HasSuffix(meta.Path, "/") || meta.Path == "" { - meta.Path = meta.Path + "index.html" + meta.Path += "index.html" } // 如果不是反向代理路由则跳过任何配置 - if request.Method != "GET" { + if request.Method != http.MethodGet { return os.ErrNotExist } var result io.ReadCloser @@ -225,18 +226,13 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error writer.WriteHeader(http.StatusNotFound) if render := meta.TryRender(meta.Path, "/404.html"); render != nil && s.options.EnableRender { defer result.Close() - if err = render.Render(writer, request, result); err != nil { - return err - } - return nil - } else { - _, _ = io.Copy(writer, result) - _ = result.Close() + return render.Render(writer, request, result) } + _, _ = io.Copy(writer, result) + _ = result.Close() return nil - } else { - return err } + return err } fileName := filepath.Base(meta.Path) render := meta.TryRender(meta.Path) @@ -257,7 +253,7 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error } } else { if reader, ok := result.(*utils.SizeReadCloser); ok && render == nil { - writer.Header().Add("Content-Length", fmt.Sprintf("%d", reader.Size)) + writer.Header().Add("Content-Length", strconv.FormatUint(reader.Size, 10)) } // todo(bug) : 直连模式下告知数据长度 writer.Header().Add("X-CacheBlob", "MISS") @@ -276,11 +272,10 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error } func (s *Server) Close() error { - if err := s.options.Alias.Close(); err != nil { - return err - } - if err := s.options.CacheBlob.Close(); err != nil { - return err - } - return s.backend.Close() + return stdErr.Join( + s.options.CacheBlob.Close(), + s.options.CacheMeta.Close(), + s.options.Alias.Close(), + s.backend.Close(), + ) } diff --git a/tests/core/dummy.go b/tests/core/dummy.go index 4ab3f0b..d1b3182 100644 --- a/tests/core/dummy.go +++ b/tests/core/dummy.go @@ -3,6 +3,7 @@ package core import ( "bytes" "context" + "errors" "io" "mime" "net/http" @@ -60,13 +61,16 @@ func (p *ProviderDummy) Branches(ctx context.Context, owner, repo string) (map[s return branches, nil } -func (p *ProviderDummy) Open(ctx context.Context, _ *http.Client, owner, repo, commit, path string, _ http.Header) (*http.Response, error) { +func (p *ProviderDummy) Open(_ context.Context, _ *http.Client, owner, repo, commit, path string, _ http.Header) (*http.Response, error) { open, err := os.Open(filepath.Join(p.BaseDir, owner, repo, commit, path)) if err != nil { - return nil, err + return nil, errors.Join(err, os.ErrNotExist) } - all, err := io.ReadAll(open) defer open.Close() + all, err := io.ReadAll(open) + if err != nil { + return nil, errors.Join(err, os.ErrNotExist) + } recorder := httptest.NewRecorder() recorder.Body = bytes.NewBuffer(all) recorder.Header().Add("Content-Type", mime.TypeByExtension(filepath.Ext(path))) diff --git a/tests/core/test.go b/tests/core/test.go index edcce13..f58ee7a 100644 --- a/tests/core/test.go +++ b/tests/core/test.go @@ -64,7 +64,7 @@ func (t *TestServer) AddFile(path, data string, args ...interface{}) { func (t *TestServer) OpenFile(url string) ([]byte, *http.Response, error) { recorder := httptest.NewRecorder() - t.server.ServeHTTP(recorder, httptest.NewRequest("GET", url, nil)) + t.server.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, url, nil)) response := recorder.Result() if response.Body != nil { defer response.Body.Close() diff --git a/tests/pages_core_test.go b/tests/pages_core_test.go index 07aab3c..76f04ea 100644 --- a/tests/pages_core_test.go +++ b/tests/pages_core_test.go @@ -15,9 +15,9 @@ func Test_get_simple_html(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "hello world", string(data)) - _, resp, err := server.OpenFile("https://org1.example.com/repo1/404") + _, resp, _ := server.OpenFile("https://org1.example.com/repo1/404") assert.NotNil(t, resp) - assert.Equal(t, resp.StatusCode, 404) + assert.Equal(t, 404, resp.StatusCode) } func Test_get_alias(t *testing.T) { @@ -28,13 +28,13 @@ func Test_get_alias(t *testing.T) { alias: - www.example.org `) - data, resp, err := server.OpenFile("https://www.example.org") - assert.Equal(t, resp.StatusCode, 404) + _, resp, _ := server.OpenFile("https://www.example.org") + assert.Equal(t, 404, resp.StatusCode) - data, resp, err = server.OpenFile("https://org1.example.com/repo1/") - assert.Equal(t, resp.StatusCode, 302) - assert.Equal(t, resp.Header.Get("Location"), "https://www.example.org/") - data, resp, err = server.OpenFile("https://www.example.org") + _, resp, _ = server.OpenFile("https://org1.example.com/repo1/") + assert.Equal(t, 302, resp.StatusCode) + assert.Equal(t, "https://www.example.org/", resp.Header.Get("Location")) + data, _, err := server.OpenFile("https://www.example.org") assert.NoError(t, err) assert.Equal(t, "hello world", string(data)) @@ -42,20 +42,20 @@ alias: alias: - zzz.example.top `) - data, resp, err = server.OpenFile("https://www.example.org") - assert.Equal(t, resp.StatusCode, 302) - assert.Equal(t, resp.Header.Get("Location"), "https://zzz.example.top/") + _, resp, _ = server.OpenFile("https://www.example.org") + assert.Equal(t, 302, resp.StatusCode) + assert.Equal(t, "https://zzz.example.top/", resp.Header.Get("Location")) - data, resp, err = server.OpenFile("https://www.example.org") - assert.Equal(t, resp.StatusCode, 404) + _, resp, _ = server.OpenFile("https://www.example.org") + assert.Equal(t, 404, resp.StatusCode) - data, resp, err = server.OpenFile("https://org1.example.com/repo1/") - assert.Equal(t, resp.StatusCode, 302) - assert.Equal(t, resp.Header.Get("Location"), "https://zzz.example.top/") + _, resp, _ = server.OpenFile("https://org1.example.com/repo1/") + assert.Equal(t, 302, resp.StatusCode) + assert.Equal(t, "https://zzz.example.top/", resp.Header.Get("Location")) - data, resp, err = server.OpenFile("https://org1.example.com/repo1/get/some") - assert.Equal(t, resp.StatusCode, 302) - assert.Equal(t, resp.Header.Get("Location"), "https://zzz.example.top/get/some") + _, resp, _ = server.OpenFile("https://org1.example.com/repo1/get/some") + assert.Equal(t, 302, resp.StatusCode) + assert.Equal(t, "https://zzz.example.top/get/some", resp.Header.Get("Location")) } func Test_fail_back(t *testing.T) { diff --git a/tests/pages_proxy_test.go b/tests/pages_proxy_test.go index dd05f79..bdfecdc 100644 --- a/tests/pages_proxy_test.go +++ b/tests/pages_proxy_test.go @@ -32,8 +32,8 @@ proxy: assert.NoError(t, err) assert.Equal(t, "hello data", string(data)) - _, resp, err := server.OpenFile("https://org1.example.com/repo1/abi/data") - assert.Equal(t, resp.StatusCode, 404) + _, resp, _ := server.OpenFile("https://org1.example.com/repo1/abi/data") + assert.Equal(t, 404, resp.StatusCode) } func Test_cname_proxy(t *testing.T) { @@ -50,18 +50,18 @@ alias: proxy: /api: %s/test `, hs.URL) - _, resp, err := server.OpenFile("https://org1.example.com/repo1/") - assert.Equal(t, resp.StatusCode, 302) - assert.Equal(t, resp.Header.Get("Location"), "https://www.example.org/") - data, resp, err := server.OpenFile("https://www.example.org") + _, resp, _ := server.OpenFile("https://org1.example.com/repo1/") + assert.Equal(t, 302, resp.StatusCode) + assert.Equal(t, "https://www.example.org/", resp.Header.Get("Location")) + data, _, err := server.OpenFile("https://www.example.org") assert.NoError(t, err) assert.Equal(t, "hello world", string(data)) - _, resp, err = server.OpenFile("https://org1.example.com/repo1/api") - assert.Equal(t, resp.StatusCode, 302) - assert.Equal(t, resp.Header.Get("Location"), "https://www.example.org/api") + _, resp, _ = server.OpenFile("https://org1.example.com/repo1/api") + assert.Equal(t, 302, resp.StatusCode) + assert.Equal(t, "https://www.example.org/api", resp.Header.Get("Location")) - data, resp, err = server.OpenFile("https://www.example.org/api") + data, _, err = server.OpenFile("https://www.example.org/api") assert.NoError(t, err) assert.Equal(t, "hello proxy", string(data)) } diff --git a/tests/pages_render_test.go b/tests/pages_render_test.go index 470db87..9dd762c 100644 --- a/tests/pages_render_test.go +++ b/tests/pages_render_test.go @@ -5,6 +5,8 @@ import ( "github.com/stretchr/testify/assert" "gopkg.d7z.net/gitea-pages/tests/core" + + _ "gopkg.d7z.net/gitea-pages/pkg/renders" ) func Test_get_render(t *testing.T) {