diff --git a/config.go b/config.go index 31af548..6ee614e 100644 --- a/config.go +++ b/config.go @@ -10,10 +10,7 @@ import ( "github.com/alecthomas/units" "github.com/pkg/errors" "go.uber.org/zap" - "gopkg.d7z.net/gitea-pages/pkg" "gopkg.d7z.net/gitea-pages/pkg/utils" - "gopkg.d7z.net/middleware/cache" - "gopkg.d7z.net/middleware/kv" "gopkg.in/yaml.v3" ) @@ -40,84 +37,6 @@ type Config struct { pageErrNotFound, pageErrUnknown *template.Template } -func (c *Config) NewPageServerOptions() (*pkg.ServerOptions, error) { - if c.Domain == "" { - return nil, errors.New("domain is required") - } - var err error - - if c.Database.URL == "" { - return nil, errors.New("config is required") - } - if c.StaticDir != "" { - stat, err := os.Stat(c.StaticDir) - if err != nil { - return nil, errors.Wrap(err, "static dir not exists") - } - if !stat.IsDir() { - return nil, errors.New("static dir is not a directory") - } - } - if c.Page.DefaultBranch == "" { - c.Page.DefaultBranch = "gh-pages" - } - defaultErr := utils.MustTemplate(defaultErrPage) - if c.Page.ErrUnknownPage != "" { - data, err := os.ReadFile(c.Page.ErrUnknownPage) - if err != nil { - return nil, errors.Wrapf(err, "failed to read file %s", string(data)) - } - c.pageErrUnknown = utils.MustTemplate(string(data)) - } else { - c.pageErrUnknown = defaultErr - } - if c.Page.ErrNotFoundPage != "" { - data, err := os.ReadFile(c.Page.ErrNotFoundPage) - if err != nil { - return nil, errors.Wrapf(err, "failed to read file %s", c.Page.ErrNotFoundPage) - } - c.pageErrNotFound = utils.MustTemplate(string(data)) - } else { - c.pageErrNotFound = defaultErr - } - - memoryCache, err := cache.NewMemoryCache(cache.MemoryCacheConfig{ - MaxCapacity: 8102, - CleanupInt: time.Hour, - }) - if err != nil { - return nil, errors.Wrap(err, "create cache") - } - alias, err := kv.NewKVFromURL(c.Database.URL) - if err != nil { - return nil, errors.Wrapf(err, "failed to init alias config") - } - cacheMeta, err := kv.NewKVFromURL(c.Cache.Meta) - if err != nil { - return nil, errors.Wrapf(err, "failed to init cache meta") - } - if c.Cache.CacheControl == "" { - c.Cache.CacheControl = "public, max-age=86400" - } - rel := pkg.ServerOptions{ - Domain: c.Domain, - DefaultBranch: c.Page.DefaultBranch, - Alias: alias, - CacheMeta: cacheMeta, - CacheMetaTTL: c.Cache.MetaTTL, - CacheControl: c.Cache.CacheControl, - CacheBlob: memoryCache, - CacheBlobTTL: c.Cache.BlobTTL, - CacheBlobLimit: uint64(c.Cache.BlobLimit), - HTTPClient: http.DefaultClient, - EnableRender: c.Render.Enable, - EnableProxy: c.Proxy.Enable, - StaticDir: c.StaticDir, - DefaultErrorHandler: c.ErrorHandler, - } - return &rel, nil -} - func (c *Config) ErrorHandler(w http.ResponseWriter, r *http.Request, err error) { if errors.Is(err, os.ErrNotExist) { w.WriteHeader(http.StatusNotFound) @@ -171,10 +90,9 @@ type ConfigCache struct { Meta string `yaml:"meta"` // 元数据缓存 MetaTTL time.Duration `yaml:"meta_ttl"` // 缓存时间 - Blob string `yaml:"blob"` // 缓存归档位置 - BlobTTL time.Duration `yaml:"blob_ttl"` // 缓存归档位置 - BlobLimit units.Base2Bytes `yaml:"blob_limit"` // 单个文件最大大小 - CacheControl string `yaml:"cache_control"` // 缓存配置 + Blob string `yaml:"blob"` // 缓存归档位置 + BlobTTL time.Duration `yaml:"blob_ttl"` // 缓存归档位置 + BlobLimit units.Base2Bytes `yaml:"blob_limit"` // 单个文件最大大小 } func LoadConfig(path string) (*Config, error) { @@ -183,11 +101,49 @@ func LoadConfig(path string) (*Config, error) { return nil, err } defer f.Close() - var config Config + var c Config decoder := yaml.NewDecoder(f) - err = decoder.Decode(&config) + err = decoder.Decode(&c) if err != nil { return nil, err } - return &config, nil + + if c.Domain == "" { + return nil, errors.New("domain is required") + } + if c.Database.URL == "" { + return nil, errors.New("c is required") + } + if c.StaticDir != "" { + stat, err := os.Stat(c.StaticDir) + if err != nil { + return nil, errors.Wrap(err, "static dir not exists") + } + if !stat.IsDir() { + return nil, errors.New("static dir is not a directory") + } + } + if c.Page.DefaultBranch == "" { + c.Page.DefaultBranch = "gh-pages" + } + defaultErr := utils.MustTemplate(defaultErrPage) + if c.Page.ErrUnknownPage != "" { + data, err := os.ReadFile(c.Page.ErrUnknownPage) + if err != nil { + return nil, errors.Wrapf(err, "failed to read file %s", string(data)) + } + c.pageErrUnknown = utils.MustTemplate(string(data)) + } else { + c.pageErrUnknown = defaultErr + } + if c.Page.ErrNotFoundPage != "" { + data, err := os.ReadFile(c.Page.ErrNotFoundPage) + if err != nil { + return nil, errors.Wrapf(err, "failed to read file %s", c.Page.ErrNotFoundPage) + } + c.pageErrNotFound = utils.MustTemplate(string(data)) + } else { + c.pageErrNotFound = defaultErr + } + return &c, nil } diff --git a/go.mod b/go.mod index c9a1632..3d82b4f 100644 --- a/go.mod +++ b/go.mod @@ -11,12 +11,13 @@ require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.0 - gopkg.d7z.net/middleware v0.0.0-20251111072327-ca8cc16305f4 + gopkg.d7z.net/middleware v0.0.0-20251113064153-9f946bf959f5 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/42wim/httpsig v1.2.3 // indirect + github.com/buke/quickjs-go v0.6.6 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.6.0 // indirect diff --git a/go.sum b/go.sum index f1aeb6e..49c4b05 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/buke/quickjs-go v0.6.6 h1:JNnssa84PRX5eX3HnG1L4skIAhjV+49WHk51JugwWgg= +github.com/buke/quickjs-go v0.6.6/go.mod h1:C32R9ThDIFSIN8jRdvSkTHBZp/uOfxi5s+/xc0lAq+I= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= @@ -187,6 +189,10 @@ gopkg.d7z.net/middleware v0.0.0-20251111034620-9ddf39894699 h1:5IRYlPahwQZ54nLnx gopkg.d7z.net/middleware v0.0.0-20251111034620-9ddf39894699/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM= gopkg.d7z.net/middleware v0.0.0-20251111072327-ca8cc16305f4 h1:29Thhz0nYiK+BYG0yjU6Cxu/3p/k2umeokfXGTp0NTg= gopkg.d7z.net/middleware v0.0.0-20251111072327-ca8cc16305f4/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM= +gopkg.d7z.net/middleware v0.0.0-20251113014952-1d1e87d199a4 h1:0O6cCLuM+IHaDqpBUgPQA5H8ZL2gYC8z6tR4jNW4H5Q= +gopkg.d7z.net/middleware v0.0.0-20251113014952-1d1e87d199a4/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM= +gopkg.d7z.net/middleware v0.0.0-20251113064153-9f946bf959f5 h1:RwEXivoUP8qEbKxRWhJfaSTWHg+AEpV0p0K2DSG+LGw= +gopkg.d7z.net/middleware v0.0.0-20251113064153-9f946bf959f5/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/main.go b/main.go index caff76e..25ab0da 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "context" "flag" + "fmt" "log" "net/http" "os" @@ -10,69 +11,68 @@ 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" + "gopkg.d7z.net/middleware/cache" + "gopkg.d7z.net/middleware/kv" ) 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() { - 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 func() { - _ = call() - }() + defer call() config, err := LoadConfig(configPath) if err != nil { log.Fatalf("fail to load config file: %v", err) } - options, err := config.NewPageServerOptions() - if err != nil { - zap.L().Fatal("fail to load options", zap.Error(err)) - } + gitea, err := providers.NewGitea(config.Auth.Server, config.Auth.Token) if err != nil { log.Fatalln(err) } - backend := providers.NewProviderCache(gitea, options.CacheMeta, options.CacheMetaTTL, - options.CacheBlob, options.CacheBlobLimit, + cacheMeta, err := kv.NewKVFromURL(config.Cache.Meta) + if err != nil { + log.Fatalln(err) + } + defer cacheMeta.Close() + cacheBlob, err := cache.NewCacheFromURL(config.Cache.Blob) + if err != nil { + log.Fatalln(err) + } + defer cacheBlob.Close() + backend := providers.NewProviderCache(gitea, cacheMeta, config.Cache.MetaTTL, + cacheBlob, uint64(config.Cache.BlobLimit), + ) + defer backend.Close() + db, err := kv.NewKVFromURL(config.Database.URL) + if err != nil { + log.Fatalln(err) + } + defer db.Close() + pageServer := pkg.NewPageServer( + http.DefaultClient, + backend, + config.Domain, + config.Page.DefaultBranch, + db, + cacheMeta, + config.Cache.MetaTTL, + config.ErrorHandler, ) - giteaServer := pkg.NewPageServer(backend, *options) - defer giteaServer.Close() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) defer stop() - svc := http.Server{Addr: config.Bind, Handler: giteaServer} + svc := http.Server{Addr: config.Bind, Handler: pageServer} go func() { <-ctx.Done() zap.L().Debug("shutdown gracefully") @@ -81,7 +81,7 @@ func main() { _ = svc.ListenAndServe() } -func logInject() func() error { +func logInject() func() { atom := zap.NewAtomicLevel() if debug { atom.SetLevel(zap.DebugLevel) @@ -94,5 +94,9 @@ func logInject() func() error { logger, _ := cfg.Build() zap.ReplaceGlobals(logger) zap.L().Debug("debug enabled") - return logger.Sync + return func() { + if err := logger.Sync(); err != nil { + fmt.Println(err) + } + } } diff --git a/pkg/core/alias.go b/pkg/core/alias.go index 10b861d..54fa47a 100644 --- a/pkg/core/alias.go +++ b/pkg/core/alias.go @@ -23,7 +23,7 @@ func NewDomainAlias(config kv.KV) *DomainAlias { } func (a *DomainAlias) Query(ctx context.Context, domain string) (*Alias, error) { - get, err := a.config.Get(ctx, "domain/alias/"+domain) + get, err := a.config.Get(ctx, domain) if err != nil { return nil, err } @@ -36,7 +36,7 @@ func (a *DomainAlias) Query(ctx context.Context, domain string) (*Alias, error) func (a *DomainAlias) Bind(ctx context.Context, domains []string, owner, repo, branch string) error { oldDomains := make([]string, 0) - rKey := fmt.Sprintf("domain/r-alias/%s/%s/%s", owner, repo, branch) + rKey := fmt.Sprintf("%s/%s/%s", owner, repo, branch) if oldStr, err := a.config.Get(ctx, rKey); err == nil { _ = json.Unmarshal([]byte(oldStr), &oldDomains) } @@ -57,7 +57,7 @@ func (a *DomainAlias) Bind(ctx context.Context, domains []string, owner, repo, b domainsRaw, _ := json.Marshal(domains) _ = a.config.Put(ctx, rKey, string(domainsRaw), kv.TTLKeep) for _, domain := range domains { - if err := a.config.Put(ctx, "domain/alias/"+domain, string(aliasMetaRaw), kv.TTLKeep); err != nil { + if err := a.config.Put(ctx, domain, string(aliasMetaRaw), kv.TTLKeep); err != nil { return err } } @@ -65,6 +65,6 @@ func (a *DomainAlias) Bind(ctx context.Context, domains []string, owner, repo, b } func (a *DomainAlias) Unbind(ctx context.Context, domain string) error { - _, err := a.config.Delete(ctx, "domain/alias/"+domain) + _, err := a.config.Delete(ctx, domain) return err } diff --git a/pkg/core/config.go b/pkg/core/config.go index bff7355..98ae458 100644 --- a/pkg/core/config.go +++ b/pkg/core/config.go @@ -37,6 +37,7 @@ func (p *PageConfigRoute) UnmarshalYAML(value *yaml.Node) error { p.Type = keys[0] params := data[p.Type] // 跳过空参数 + p.Params = make(map[string]any) if _, ok := params.(string); ok || params == nil { return nil } diff --git a/pkg/core/domain.go b/pkg/core/domain.go index 66aa1bb..0b36615 100644 --- a/pkg/core/domain.go +++ b/pkg/core/domain.go @@ -7,34 +7,40 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" + "gopkg.d7z.net/middleware/kv" + "gopkg.d7z.net/middleware/tools" ) type PageDomain struct { *ServerMeta alias *DomainAlias + pageDB kv.KV baseDomain string defaultBranch string } -func NewPageDomain(meta *ServerMeta, alias *DomainAlias, baseDomain, defaultBranch string) *PageDomain { +func NewPageDomain(meta *ServerMeta, alias *DomainAlias, pageDB kv.KV, baseDomain, defaultBranch string) *PageDomain { return &PageDomain{ baseDomain: baseDomain, defaultBranch: defaultBranch, ServerMeta: meta, alias: alias, + pageDB: pageDB, } } -type PageDomainContent struct { +type PageContent struct { *PageMetaContent *PageVFS - Owner string - Repo string - Path string + OrgDB kv.KV + RepoDB kv.KV + Owner string + Repo string + Path string } -func (p *PageDomain) ParseDomainMeta(ctx context.Context, domain, path, branch string) (*PageDomainContent, error) { +func (p *PageDomain) ParseDomainMeta(ctx context.Context, domain, path, branch string) (*PageContent, error) { if branch == "" { branch = p.defaultBranch } @@ -50,7 +56,7 @@ func (p *PageDomain) ParseDomainMeta(ctx context.Context, domain, path, branch s } owner := strings.TrimSuffix(domain, "."+p.baseDomain) repo := pathArr[0] - var returnMeta *PageDomainContent + var returnMeta *PageContent var err error if repo == "" { // 回退到默认仓库 (路径未包含仓库) @@ -68,8 +74,8 @@ 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, repo, branch string, path []string) (*PageDomainContent, error) { - result := &PageDomainContent{} +func (p *PageDomain) returnMeta(ctx context.Context, owner, repo, branch string, path []string) (*PageContent, error) { + result := &PageContent{} meta, err := p.GetMeta(ctx, owner, repo, branch) if err != nil { zap.L().Debug("查询错误", zap.Error(err)) @@ -83,6 +89,8 @@ func (p *PageDomain) returnMeta(ctx context.Context, owner, repo, branch string, result.Owner = owner result.Repo = repo result.PageVFS = NewPageVFS(p.client, p.Backend, owner, repo, result.CommitID) + result.OrgDB = tools.NewPrefixKV(p.pageDB, p.pageDB.WithKey("org", owner)) + result.RepoDB = tools.NewPrefixKV(p.pageDB, p.pageDB.WithKey("repo", owner, repo)) result.Path = strings.Join(path, "/") if err = p.alias.Bind(ctx, meta.Alias, result.Owner, result.Repo, branch); err != nil { diff --git a/pkg/core/filter.go b/pkg/core/filter.go index afd834c..ca4a791 100644 --- a/pkg/core/filter.go +++ b/pkg/core/filter.go @@ -33,7 +33,7 @@ type Filter struct { } func NextCallWrapper(call FilterCall, parentCall NextCall, stack Filter) NextCall { - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *PageDomainContent) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *PageContent) error { zap.L().Debug(fmt.Sprintf("call filter(%s) before", stack.Type), zap.Any("filter", stack)) err := call(ctx, writer, request, metadata, parentCall) zap.L().Debug(fmt.Sprintf("call filter(%s) after", stack.Type), zap.Any("filter", stack), zap.Error(err)) @@ -45,10 +45,10 @@ type NextCall func( ctx context.Context, writer http.ResponseWriter, request *http.Request, - metadata *PageDomainContent, + metadata *PageContent, ) error -var NotFountNextCall = func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *PageDomainContent) error { +var NotFountNextCall = func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *PageContent) error { return os.ErrNotExist } @@ -56,7 +56,7 @@ type FilterCall func( ctx context.Context, writer http.ResponseWriter, request *http.Request, - metadata *PageDomainContent, + metadata *PageContent, next NextCall, ) error diff --git a/pkg/core/meta.go b/pkg/core/meta.go index 3d649d4..7fda014 100644 --- a/pkg/core/meta.go +++ b/pkg/core/meta.go @@ -71,12 +71,12 @@ func (m *PageMetaContent) String() string { return string(marshal) } -func NewServerMeta(client *http.Client, backend Backend, kv kv.KV, domain string, ttl time.Duration) *ServerMeta { +func NewServerMeta(client *http.Client, backend Backend, domain string, cache kv.KV, ttl time.Duration) *ServerMeta { return &ServerMeta{ Backend: backend, Domain: domain, client: client, - cache: tools.NewCache[PageMetaContent](kv, "pages/meta", ttl), + cache: tools.NewCache[PageMetaContent](cache, "meta", ttl), locker: utils.NewLocker(), } } diff --git a/pkg/filters/block.go b/pkg/filters/block.go index 38a7786..6b90ab8 100644 --- a/pkg/filters/block.go +++ b/pkg/filters/block.go @@ -21,7 +21,7 @@ var FilterInstBlock core.FilterInstance = func(config core.FilterParams) (core.F if param.Message == "" { param.Message = http.StatusText(param.Code) } - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageDomainContent, next core.NextCall) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { writer.WriteHeader(param.Code) if param.Message != "" { _, _ = writer.Write([]byte(param.Message)) diff --git a/pkg/filters/common.go b/pkg/filters/common.go index c3e3c1a..051b3ba 100644 --- a/pkg/filters/common.go +++ b/pkg/filters/common.go @@ -4,12 +4,13 @@ import "gopkg.d7z.net/gitea-pages/pkg/core" func DefaultFilters() map[string]core.FilterInstance { return map[string]core.FilterInstance{ - "block": FilterInstBlock, - "redirect": FilterInstRedirect, - "direct": FilterInstDirect, - "reverse_proxy": FilterInstProxy, - "_404_": FilterInstDefaultNotFound, - "failback": FilterInstFailback, - "template": FilterInstTemplate, + "block": FilterInstBlock, + "redirect": FilterInstRedirect, + "direct": FilterInstDirect, + //"reverse_proxy": FilterInstProxy, + "_404_": FilterInstDefaultNotFound, + "failback": FilterInstFailback, + "template": FilterInstTemplate, + "qjs": FilterInstQuickJS, } } diff --git a/pkg/filters/default.go b/pkg/filters/default.go index 494fb2b..140102a 100644 --- a/pkg/filters/default.go +++ b/pkg/filters/default.go @@ -11,7 +11,7 @@ import ( ) var FilterInstDefaultNotFound core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) { - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageDomainContent, next core.NextCall) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { err := next(ctx, writer, request, metadata) if err != nil && errors.Is(err, os.ErrNotExist) { open, err := metadata.NativeOpen(ctx, "/404.html", nil) diff --git a/pkg/filters/direct.go b/pkg/filters/direct.go index f38fd53..52a668e 100644 --- a/pkg/filters/direct.go +++ b/pkg/filters/direct.go @@ -23,11 +23,15 @@ var FilterInstDirect core.FilterInstance = func(config core.FilterParams) (core. return nil, err } param.Prefix = strings.Trim(param.Prefix, "/") + "/" - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageDomainContent, next core.NextCall) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { err := next(ctx, writer, request, metadata) if (err != nil && !errors.Is(err, os.ErrNotExist)) || err == nil { return err } + if request.Method != http.MethodHead && request.Method != http.MethodGet { + http.Error(writer, "Method not allowed", http.StatusMethodNotAllowed) + return nil + } var resp *http.Response var path string defaultPath := param.Prefix + strings.TrimSuffix(metadata.Path, "/") diff --git a/pkg/filters/failback.go b/pkg/filters/failback.go index b0b6b2a..3a4fb8a 100644 --- a/pkg/filters/failback.go +++ b/pkg/filters/failback.go @@ -23,7 +23,7 @@ var FilterInstFailback core.FilterInstance = func(config core.FilterParams) (cor if param.Path == "" { return nil, errors.Errorf("filter failback: path is empty") } - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageDomainContent, next core.NextCall) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { err := next(ctx, writer, request, metadata) if (err != nil && !errors.Is(err, os.ErrNotExist)) || err == nil { return err diff --git a/pkg/filters/proxy.go b/pkg/filters/proxy.go index 5b4e2b6..03a3823 100644 --- a/pkg/filters/proxy.go +++ b/pkg/filters/proxy.go @@ -22,7 +22,7 @@ var FilterInstProxy core.FilterInstance = func(config core.FilterParams) (core.F if err := config.Unmarshal(¶m); err != nil { return nil, err } - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageDomainContent, next core.NextCall) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { proxyPath := "/" + metadata.Path targetPath := strings.TrimPrefix(proxyPath, param.Prefix) if !strings.HasPrefix(targetPath, "/") { diff --git a/pkg/filters/quickjs.go b/pkg/filters/quickjs.go new file mode 100644 index 0000000..6bcf0a0 --- /dev/null +++ b/pkg/filters/quickjs.go @@ -0,0 +1,325 @@ +package filters + +import ( + "context" + "io" + "log" + "net/http" + "strings" + "time" + + "github.com/buke/quickjs-go" + "github.com/pkg/errors" + "gopkg.d7z.net/gitea-pages/pkg/core" +) + +var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) { + var param struct { + Exec string `json:"exec"` + } + if err := config.Unmarshal(¶m); err != nil { + return nil, err + } + if param.Exec == "" { + return nil, errors.Errorf("no exec specified") + } + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { + js, err := metadata.ReadString(ctx, param.Exec) + if err != nil { + return err + } + + var rt = quickjs.NewRuntime() + defer rt.Close() + + jsCtx := rt.NewContext() + defer jsCtx.Close() + + global := jsCtx.Globals() + global.Set("request", createRequestObject(jsCtx, request)) + global.Set("response", createResponseObject(jsCtx, writer, request)) + global.Set("console", createConsoleObject(jsCtx)) + + ret := jsCtx.Eval(js) + defer ret.Free() + + if ret.IsException() { + err := jsCtx.Exception() + return err + } + return nil + }, nil +} + +// createRequestObject 创建表示 HTTP 请求的 JavaScript 对象 +func createRequestObject(ctx *quickjs.Context, req *http.Request) *quickjs.Value { + obj := ctx.NewObject() + + // 基本属性 + obj.Set("method", ctx.NewString(req.Method)) + obj.Set("url", ctx.NewString(req.URL.String())) + obj.Set("path", ctx.NewString(req.URL.Path)) + obj.Set("query", ctx.NewString(req.URL.RawQuery)) + obj.Set("host", ctx.NewString(req.Host)) + obj.Set("remoteAddr", ctx.NewString(req.RemoteAddr)) + obj.Set("proto", ctx.NewString(req.Proto)) + obj.Set("httpVersion", ctx.NewString(req.Proto)) + + // 解析查询参数 + queryObj := ctx.NewObject() + for key, values := range req.URL.Query() { + if len(values) > 0 { + queryObj.Set(key, ctx.NewString(values[0])) + } + } + obj.Set("query", queryObj) + + // 添加 headers + headersObj := ctx.NewObject() + for key, values := range req.Header { + if len(values) > 0 { + headersObj.Set(key, ctx.NewString(values[0])) + } + } + obj.Set("headers", headersObj) + + // 请求方法 + obj.Set("get", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + key := args[0].String() + return ctx.NewString(req.Header.Get(key)) + } + return ctx.NewNull() + })) + + // 读取请求体 + obj.Set("readBody", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + body, err := io.ReadAll(req.Body) + if err != nil { + return ctx.NewError(err) + } + return ctx.NewString(string(body)) + })) + + return obj +} + +// createResponseObject 创建表示 HTTP 响应的 JavaScript 对象 +func createResponseObject(ctx *quickjs.Context, writer http.ResponseWriter, req *http.Request) *quickjs.Value { + obj := ctx.NewObject() + + // 响应头操作 + obj.Set("setHeader", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) >= 2 { + key := args[0].String() + value := args[1].String() + writer.Header().Set(key, value) + } + return ctx.NewNull() + })) + + obj.Set("getHeader", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + key := args[0].String() + return ctx.NewString(writer.Header().Get(key)) + } + return ctx.NewNull() + })) + + obj.Set("removeHeader", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + key := args[0].String() + writer.Header().Del(key) + } + return ctx.NewNull() + })) + + obj.Set("hasHeader", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + key := args[0].String() + _, exists := writer.Header()[key] + return ctx.NewBool(exists) + } + return ctx.NewBool(false) + })) + + // 状态码操作 + obj.Set("setStatus", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + writer.WriteHeader(int(args[0].ToInt32())) + } + return ctx.NewNull() + })) + + obj.Set("statusCode", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + writer.WriteHeader(int(args[0].ToInt32())) + } + return ctx.NewNull() + })) + + // 写入响应 + obj.Set("write", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + data := args[0].String() + _, err := writer.Write([]byte(data)) + if err != nil { + return ctx.NewError(err) + } + } + return ctx.NewNull() + })) + + obj.Set("writeHead", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) >= 1 { + statusCode := int(args[0].ToInt32()) + + // 处理可选的 headers 参数 + if len(args) >= 2 && args[1].IsObject() { + headersObj := args[1] + headersObj.Properties().ForEach(func(key string, value *quickjs.Value) bool { + writer.Header().Set(key, value.String()) + return true + }) + } + + writer.WriteHeader(statusCode) + } + return ctx.NewNull() + })) + + obj.Set("end", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + data := args[0].String() + _, err := writer.Write([]byte(data)) + if err != nil { + return ctx.NewError(err) + } + } + // 在实际的 HTTP 处理中,我们通常不手动结束响应 + return ctx.NewNull() + })) + + // 重定向 + obj.Set("redirect", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + location := args[0].String() + statusCode := 302 + if len(args) > 1 { + statusCode = int(args[1].ToInt32()) + } + http.Redirect(writer, req, location, statusCode) + } + return ctx.NewNull() + })) + + // JSON 响应 + obj.Set("json", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + writer.Header().Set("Content-Type", "application/json") + jsonData := args[0].String() + _, err := writer.Write([]byte(jsonData)) + if err != nil { + return ctx.NewError(err) + } + } + return ctx.NewNull() + })) + + // 设置 cookie + obj.Set("setCookie", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) >= 2 { + name := args[0].String() + value := args[1].String() + + cookie := &http.Cookie{ + Name: name, + Value: value, + Path: "/", + } + + // 处理可选参数 + if len(args) >= 3 && args[2].IsObject() { + options := args[2] + + if maxAge := options.Get("maxAge"); !maxAge.IsNull() { + cookie.MaxAge = int(maxAge.ToInt32()) + } + + if expires := options.Get("expires"); !expires.IsNull() { + if expires.IsNumber() { + cookie.Expires = time.Unix(expires.ToInt64(), 0) + } + } + + if path := options.Get("path"); !path.IsNull() { + cookie.Path = path.String() + } + + if domain := options.Get("domain"); !domain.IsNull() { + cookie.Domain = domain.String() + } + + if secure := options.Get("secure"); !secure.IsNull() { + cookie.Secure = secure.Bool() + } + + if httpOnly := options.Get("httpOnly"); !httpOnly.IsNull() { + cookie.HttpOnly = httpOnly.Bool() + } + } + + http.SetCookie(writer, cookie) + } + return ctx.NewNull() + })) + + return obj +} + +// createConsoleObject 创建 console 对象用于日志输出 +func createConsoleObject(ctx *quickjs.Context) *quickjs.Value { + console := ctx.NewObject() + + logFunc := func(level string) func(*quickjs.Context, *quickjs.Value, []*quickjs.Value) *quickjs.Value { + return func(q *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + var messages []string + for _, arg := range args { + messages = append(messages, arg.String()) + } + log.Printf("[" + level + "] " + strings.Join(messages, " ")) + return ctx.NewNull() + } + } + + console.Set("log", ctx.NewFunction(logFunc("INFO"))) + console.Set("info", ctx.NewFunction(logFunc("INFO"))) + console.Set("warn", ctx.NewFunction(logFunc("WARN"))) + console.Set("error", ctx.NewFunction(logFunc("ERROR"))) + console.Set("debug", ctx.NewFunction(logFunc("DEBUG"))) + + // 添加 time 和 timeEnd 方法用于性能测量 + timers := make(map[string]time.Time) + + console.Set("time", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + label := args[0].String() + timers[label] = time.Now() + } + return ctx.NewNull() + })) + + console.Set("timeEnd", ctx.NewFunction(func(c *quickjs.Context, value *quickjs.Value, args []*quickjs.Value) *quickjs.Value { + if len(args) > 0 { + label := args[0].String() + if start, exists := timers[label]; exists { + elapsed := time.Since(start) + log.Printf("[TIMER] %s: %v", label, elapsed) + delete(timers, label) + } + } + return ctx.NewNull() + })) + + return console +} diff --git a/pkg/filters/redirect.go b/pkg/filters/redirect.go index a5d9639..2d77110 100644 --- a/pkg/filters/redirect.go +++ b/pkg/filters/redirect.go @@ -32,7 +32,7 @@ var FilterInstRedirect core.FilterInstance = func(config core.FilterParams) (cor if param.Code < 300 || param.Code > 399 { return nil, fmt.Errorf("invalid code: %d", param.Code) } - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageDomainContent, next core.NextCall) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { domain := portExp.ReplaceAllString(strings.ToLower(request.Host), "") if len(param.Targets) > 0 && !slices.Contains(metadata.Alias, domain) { // 重定向到配置的地址 diff --git a/pkg/filters/template.go b/pkg/filters/template.go index f925fad..6537606 100644 --- a/pkg/filters/template.go +++ b/pkg/filters/template.go @@ -18,7 +18,7 @@ var FilterInstTemplate core.FilterInstance = func(config core.FilterParams) (cor return nil, err } param.Prefix = strings.Trim(param.Prefix, "/") + "/" - return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageDomainContent, next core.NextCall) error { + return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error { data, err := metadata.ReadString(ctx, param.Prefix+metadata.Path) if err != nil { return err @@ -28,7 +28,7 @@ var FilterInstTemplate core.FilterInstance = func(config core.FilterParams) (cor } out := &bytes.Buffer{} parse, err := utils.NewTemplate().Funcs(map[string]any{ - "template": func(path string) (any, error) { + "load": func(path string) (any, error) { return metadata.ReadString(ctx, param.Prefix+path) }, }).Parse(data) diff --git a/pkg/providers/cache.go b/pkg/providers/cache.go index ee62c97..a10a2c9 100644 --- a/pkg/providers/cache.go +++ b/pkg/providers/cache.go @@ -36,7 +36,6 @@ func NewProviderCache( backend core.Backend, cacheMeta kv.KV, cacheMetaTTL time.Duration, - cacheBlob cache.Cache, cacheBlobLimit uint64, ) *ProviderCache { diff --git a/pkg/renders/gotemplate.go b/pkg/renders/gotemplate.go deleted file mode 100644 index 0c4170e..0000000 --- a/pkg/renders/gotemplate.go +++ /dev/null @@ -1,39 +0,0 @@ -package renders - -import ( - "bytes" - "context" - "io" - "net/http" - - "gopkg.d7z.net/gitea-pages/pkg/core" - - "gopkg.d7z.net/gitea-pages/pkg/utils" -) - -type GoTemplate struct{} - -func init() { -} - -func (g GoTemplate) Render(ctx context.Context, w http.ResponseWriter, r *http.Request, input io.Reader, meta *core.PageDomainContent) error { - data, err := io.ReadAll(input) - if err != nil { - return err - } - out := &bytes.Buffer{} - parse, err := utils.NewTemplate().Funcs(map[string]any{ - "template": func(path string) (any, error) { - return meta.ReadString(ctx, path) - }, - }).Parse(string(data)) - if err != nil { - return err - } - err = parse.Execute(out, utils.NewTemplateInject(r, nil)) - if err != nil { - return err - } - _, err = out.WriteTo(w) - return err -} diff --git a/pkg/server.go b/pkg/server.go index 83ee0d0..a5f8c2d 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "net/http" - "os" "regexp" "slices" "strings" @@ -16,119 +15,69 @@ import ( "go.uber.org/zap" "gopkg.d7z.net/gitea-pages/pkg/core" "gopkg.d7z.net/gitea-pages/pkg/filters" - "gopkg.d7z.net/middleware/cache" "gopkg.d7z.net/middleware/kv" + "gopkg.d7z.net/middleware/tools" ) var portExp = regexp.MustCompile(`:\d+$`) -type ServerOptions struct { - Domain string // 默认域名 - DefaultBranch string // 默认分支 - - Alias kv.KV // 配置映射关系 - - CacheMeta kv.KV // 配置缓存 - CacheMetaTTL time.Duration // 配置缓存时长 - - CacheBlob cache.Cache // blob缓存 - CacheBlobTTL time.Duration // 配置缓存时长 - CacheControl string // 缓存配置 - - CacheBlobLimit uint64 // blob最大缓存大小 - - HTTPClient *http.Client // 自定义客户端 - EnableRender bool // 允许渲染 - - EnableProxy bool // 允许代理 - - StaticDir string // 静态文件位置 - DefaultErrorHandler func(w http.ResponseWriter, r *http.Request, err error) -} - -func DefaultOptions(domain string) ServerOptions { - configMemory, _ := kv.NewMemory("") - cacheMemory, _ := cache.NewMemoryCache(cache.MemoryCacheConfig{MaxCapacity: 4096, CleanupInt: time.Hour}) - return ServerOptions{ - Domain: domain, - DefaultBranch: "gh-pages", - - Alias: configMemory, - CacheMeta: configMemory, - CacheMetaTTL: time.Minute, - - CacheBlob: cacheMemory, - CacheBlobTTL: time.Minute, - CacheBlobLimit: 1024 * 1024 * 10, - CacheControl: "public, max-age=86400", - - HTTPClient: http.DefaultClient, - - EnableRender: true, - EnableProxy: true, - StaticDir: "", - DefaultErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { - if errors.Is(err, os.ErrNotExist) { - http.Error(w, "page not found.", http.StatusNotFound) - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - }, - } -} - type Server struct { - backend core.Backend - options *ServerOptions - meta *core.PageDomain - staticFS http.Handler + backend core.Backend + meta *core.PageDomain + + errorHandler func(w http.ResponseWriter, r *http.Request, err error) filterMgr map[string]core.FilterInstance globCache *lru.Cache[string, glob.Glob] } -var staticPrefix = "/.well-known/page-server/" - -func NewPageServer(backend core.Backend, options ServerOptions) *Server { - svcMeta := core.NewServerMeta(options.HTTPClient, backend, options.CacheMeta, options.Domain, options.CacheMetaTTL) - pageMeta := core.NewPageDomain(svcMeta, core.NewDomainAlias(options.Alias), options.Domain, options.DefaultBranch) - var fs http.Handler - if options.StaticDir != "" { - fs = http.StripPrefix(staticPrefix, http.FileServer(http.Dir(options.StaticDir))) - } +func NewPageServer( + client *http.Client, + backend core.Backend, + domain string, + defaultBranch string, + db kv.KV, + cache kv.KV, + cacheTTL time.Duration, + errorHandler func(w http.ResponseWriter, r *http.Request, err error), +) *Server { + svcMeta := core.NewServerMeta(client, backend, domain, cache, cacheTTL) + pageMeta := core.NewPageDomain(svcMeta, + core.NewDomainAlias(tools.NewPrefixKV(db, "config/alias")), + tools.NewPrefixKV(db, "config/pages"), + domain, defaultBranch) c, err := lru.New[string, glob.Glob](256) if err != nil { panic(err) } return &Server{ - backend: backend, - options: &options, - meta: pageMeta, - staticFS: fs, - globCache: c, - filterMgr: filters.DefaultFilters(), + backend: backend, + meta: pageMeta, + globCache: c, + filterMgr: filters.DefaultFilters(), + errorHandler: errorHandler, } } func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { sessionID, _ := uuid.NewRandom() request.Header.Set("Session-ID", sessionID.String()) - if s.staticFS != nil && strings.HasPrefix(request.URL.Path, staticPrefix) { - s.staticFS.ServeHTTP(writer, request) - return - } + //if s.staticFS != nil && strings.HasPrefix(request.URL.Path, staticPrefix) { + // s.staticFS.ServeHTTP(writer, request) + // return + //} defer func() { if e := recover(); e != nil { zap.L().Error("panic!", zap.Any("error", e), zap.Any("id", sessionID)) if err, ok := e.(error); ok { - s.options.DefaultErrorHandler(writer, request, err) + s.errorHandler(writer, request, err) } } }() err := s.Serve(writer, request) if err != nil { zap.L().Debug("错误请求", zap.Error(err), zap.Any("request", request.RequestURI), zap.Any("id", sessionID)) - s.options.DefaultErrorHandler(writer, request, err) + s.errorHandler(writer, request, err) } } @@ -187,12 +136,3 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error err = stack(ctx, writer, request, meta) return err } - -func (s *Server) Close() error { - return errors.Join( - s.options.CacheBlob.Close(), - s.options.CacheMeta.Close(), - s.options.Alias.Close(), - s.backend.Close(), - ) -} diff --git a/tests/core/test.go b/tests/core/test.go index f58ee7a..0977c43 100644 --- a/tests/core/test.go +++ b/tests/core/test.go @@ -11,6 +11,8 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" "gopkg.d7z.net/gitea-pages/pkg" + "gopkg.d7z.net/middleware/kv" + "gopkg.d7z.net/middleware/tools" ) type TestServer struct { @@ -18,31 +20,39 @@ type TestServer struct { dummy *ProviderDummy } -type SvcOpts func(options *pkg.ServerOptions) - func NewDefaultTestServer() *TestServer { - return NewTestServer("example.com", func(options *pkg.ServerOptions) { - options.CacheMetaTTL = 0 - }) + return NewTestServer("example.com") } -func NewTestServer(domain string, opts ...SvcOpts) *TestServer { +func NewTestServer(domain string) *TestServer { atom := zap.NewAtomicLevel() atom.SetLevel(zap.DebugLevel) cfg := zap.NewProductionConfig() cfg.Level = atom logger, _ := cfg.Build() zap.ReplaceGlobals(logger) - options := pkg.DefaultOptions(domain) - for _, opt := range opts { - opt(&options) - } dummy, err := NewDummy() if err != nil { zap.S().Fatal(err) } - server := pkg.NewPageServer(dummy, options) + memoryKV, _ := kv.NewMemory("") + server := pkg.NewPageServer( + http.DefaultClient, + dummy, + domain, + "gh-pages", + memoryKV, + tools.NewPrefixKV(memoryKV, "cache"), + 0, + func(w http.ResponseWriter, r *http.Request, err error) { + if errors.Is(err, os.ErrNotExist) { + http.Error(w, "page not found.", http.StatusNotFound) + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }, + ) return &TestServer{ dummy: dummy, @@ -77,5 +87,5 @@ func (t *TestServer) OpenFile(url string) ([]byte, *http.Response, error) { } func (t *TestServer) Close() error { - return t.server.Close() + return nil } diff --git a/tests/filter_qjs_test.go b/tests/filter_qjs_test.go new file mode 100644 index 0000000..b9cf0f3 --- /dev/null +++ b/tests/filter_qjs_test.go @@ -0,0 +1,34 @@ +package tests + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gopkg.d7z.net/gitea-pages/tests/core" +) + +func Test_JS(t *testing.T) { + server := core.NewDefaultTestServer() + defer server.Close() + server.AddFile("org1/repo1/gh-pages/index.html", "hello world") + server.AddFile("org1/repo1/gh-pages/index.js", ` +function get(a,b) { + return a + b; +} +request.Write() +`) + server.AddFile("org1/repo1/gh-pages/.pages.yaml", ` +routes: +- path: "api/v1/**" + qjs: + exec: "index.js" +`) + data, _, err := server.OpenFile("https://org1.example.com/repo1/") + assert.NoError(t, err) + assert.Equal(t, "hello world", string(data)) + + data, _, err = server.OpenFile("https://org1.example.com/repo1/api/v1/get") + assert.NoError(t, err) + assert.Equal(t, "512 + 512 = 1024", string(data)) + +} diff --git a/tests/filter_template_test.go b/tests/filter_template_test.go index 6d7adde..0d1b6ed 100644 --- a/tests/filter_template_test.go +++ b/tests/filter_template_test.go @@ -5,8 +5,6 @@ import ( "github.com/stretchr/testify/assert" "gopkg.d7z.net/gitea-pages/tests/core" - - _ "gopkg.d7z.net/gitea-pages/pkg/renders" ) func Test_Filter_Template(t *testing.T) { @@ -15,9 +13,12 @@ func Test_Filter_Template(t *testing.T) { server.AddFile("org1/repo1/gh-pages/index.html", "hello world") server.AddFile("org1/repo1/gh-pages/tmpl/index.html", "hello world,{{ .Request.Host }}") server.AddFile("org1/repo1/gh-pages/tmpl/ignore.html", "hello world, No Template") + server.AddFile("org1/repo1/gh-pages/tmpl/include.txt", "master") + server.AddFile("org1/repo1/gh-pages/tmpl/include.html", `hello world, {{ load "tmpl/include.txt" }}`) + server.AddFile("org1/repo1/gh-pages/.pages.yaml", ` routes: -- path: tmpl/index.html +- path: tmpl/index.html,tmpl/include.html template: `) data, _, err := server.OpenFile("https://org1.example.com/repo1/") @@ -31,4 +32,7 @@ routes: data, _, err = server.OpenFile("https://org1.example.com/repo1/tmpl/ignore.html") assert.NoError(t, err) assert.Equal(t, "hello world, No Template", string(data)) + data, _, err = server.OpenFile("https://org1.example.com/repo1/tmpl/include.html") + assert.NoError(t, err) + assert.Equal(t, "hello world, master", string(data)) }