切换存储方式

This commit is contained in:
dragon
2025-11-11 10:13:53 +08:00
parent a0e6bb668e
commit aa5d6b0f10
21 changed files with 388 additions and 165 deletions

View File

@@ -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:

3
.gitignore vendored
View File

@@ -63,4 +63,5 @@ fabric.properties
config-local.yaml
gitea-pages
*.zip
dist/
dist/
coverage.txt

183
.golangci.yml Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -10,7 +10,7 @@
## Get Started
安装 `go1.24` 或更高版本,同时安装 `Make` 工具 ,然后执行如下命令:
安装 `go1.25` 或更高版本,同时安装 `Make` 工具 ,然后执行如下命令:
```bash
make gitea-pages

View File

@@ -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"` // 是否允许反向代理
}

View File

@@ -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

31
main.go
View File

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

View File

@@ -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{

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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 {
// 不存在配置,但也可以重定向

View File

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

View File

@@ -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(),
)
}

View File

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

View File

@@ -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()

View File

@@ -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) {

View File

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

View File

@@ -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) {