From adb9bf25b8ddad737d26f7b665d2c44713eacac6 Mon Sep 17 00:00:00 2001 From: dragon Date: Wed, 12 Nov 2025 13:50:38 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 6 +- pkg/core/backend_cache.go | 8 +- pkg/core/domain.go | 4 +- pkg/core/meta.go | 260 ++++++++++++++++++++------------------ pkg/core/meta_content.go | 2 +- pkg/core/page.go | 8 ++ pkg/core/parser.go | 61 --------- pkg/core/render.go | 3 +- pkg/renders/gotemplate.go | 12 +- pkg/server.go | 102 +++++++-------- pkg/utils/template.go | 8 +- 11 files changed, 217 insertions(+), 257 deletions(-) delete mode 100644 pkg/core/parser.go diff --git a/main.go b/main.go index acd1203..1b97016 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "syscall" "go.uber.org/zap" + "gopkg.d7z.net/gitea-pages/pkg/core" "gopkg.in/yaml.v3" "gopkg.d7z.net/gitea-pages/pkg" @@ -64,7 +65,10 @@ func main() { if err != nil { log.Fatalln(err) } - giteaServer := pkg.NewPageServer(gitea, *options) + backend := core.NewCacheBackend(gitea, options.CacheMeta, options.CacheMetaTTL, + options.CacheBlob, options.CacheBlobLimit, + ) + giteaServer := pkg.NewPageServer(backend, *options) defer giteaServer.Close() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) defer stop() diff --git a/pkg/core/backend_cache.go b/pkg/core/backend_cache.go index 3a7146d..0a710bb 100644 --- a/pkg/core/backend_cache.go +++ b/pkg/core/backend_cache.go @@ -34,13 +34,13 @@ func (c *CacheBackend) Close() error { func NewCacheBackend( backend Backend, cacheMeta kv.KV, - cacheMetaTtl time.Duration, + cacheMetaTTL time.Duration, cacheBlob cache.Cache, cacheBlobLimit uint64, ) *CacheBackend { - repoCache := tools.NewCache[map[string]string](cacheMeta, "repos", cacheMetaTtl) - branchCache := tools.NewCache[map[string]*BranchInfo](cacheMeta, "branches", cacheMetaTtl) + repoCache := tools.NewCache[map[string]string](cacheMeta, "repos", cacheMetaTTL) + branchCache := tools.NewCache[map[string]*BranchInfo](cacheMeta, "branches", cacheMetaTTL) return &CacheBackend{ backend: backend, cacheRepo: repoCache, @@ -115,7 +115,7 @@ func (c *CacheBackend) Open(ctx context.Context, client *http.Client, owner, rep } return &http.Response{ Status: "200 OK", - StatusCode: 200, + StatusCode: http.StatusOK, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, diff --git a/pkg/core/domain.go b/pkg/core/domain.go index 5f0e3a9..5ddd8ed 100644 --- a/pkg/core/domain.go +++ b/pkg/core/domain.go @@ -70,7 +70,7 @@ func (p *PageDomain) ParseDomainMeta(ctx context.Context, domain, path, branch s func (p *PageDomain) returnMeta(ctx context.Context, owner, repo, branch string, path []string) (*PageDomainContent, error) { result := &PageDomainContent{} - meta, err := p.GetMeta(ctx, owner, repo, branch) + meta, vfs, err := p.GetMeta(ctx, owner, repo, branch) if err != nil { zap.L().Debug("查询错误", zap.Error(err)) if meta != nil { @@ -82,7 +82,7 @@ func (p *PageDomain) returnMeta(ctx context.Context, owner, repo, branch string, result.PageMetaContent = meta result.Owner = owner result.Repo = repo - result.PageVFS = NewPageVFS(p.client, p, result.Owner, result.Repo, result.CommitID) + result.PageVFS = vfs 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/meta.go b/pkg/core/meta.go index e8ac582..26c65ff 100644 --- a/pkg/core/meta.go +++ b/pkg/core/meta.go @@ -3,7 +3,6 @@ package core import ( "context" "fmt" - "io" "net/http" "net/url" "os" @@ -32,183 +31,200 @@ type ServerMeta struct { client *http.Client cache *tools.Cache[PageMetaContent] - locker *utils.Locker } func NewServerMeta(client *http.Client, backend Backend, kv kv.KV, domain string, ttl time.Duration) *ServerMeta { return &ServerMeta{ - backend, domain, client, - tools.NewCache[PageMetaContent](kv, "pages/meta", ttl), - utils.NewLocker(), + Backend: backend, + Domain: domain, + client: client, + cache: tools.NewCache[PageMetaContent](kv, "pages/meta", ttl), + locker: utils.NewLocker(), } } -func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (*PageMetaContent, error) { - rel := NewPageMetaContent() +func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (*PageMetaContent, *PageVFS, error) { repos, err := s.Repos(ctx, owner) if err != nil { - return nil, err + return nil, nil, err } + defBranch := repos[repo] if defBranch == "" { - return nil, os.ErrNotExist + return nil, nil, os.ErrNotExist } + if branch == "" { branch = defBranch } + branches, err := s.Branches(ctx, owner, repo) if err != nil { - return nil, err + return nil, nil, err } + info := branches[branch] if info == nil { - return nil, os.ErrNotExist + return nil, nil, os.ErrNotExist } - rel.CommitID = info.ID - rel.LastModified = info.LastModified key := fmt.Sprintf("%s/%s/%s", owner, repo, branch) - if cache, find := s.cache.Load(ctx, key); find { + + if cache, found := s.cache.Load(ctx, key); found { if cache.IsPage { - return rel, nil - } else { - return nil, os.ErrNotExist + return &cache, NewPageVFS(s.client, s.Backend, owner, repo, cache.CommitID), nil } + return nil, nil, os.ErrNotExist } + mux := s.locker.Open(key) mux.Lock() defer mux.Unlock() - if cache, find := s.cache.Load(ctx, key); find { + + if cache, found := s.cache.Load(ctx, key); found { if cache.IsPage { - return rel, nil - } else { - return nil, os.ErrNotExist + return &cache, NewPageVFS(s.client, s.Backend, owner, repo, cache.CommitID), nil } + return nil, nil, os.ErrNotExist } - // 确定存在 index.html , 否则跳过 - if find, _ := s.FileExists(ctx, owner, repo, rel.CommitID, "index.html"); !find { + rel := NewEmptyPageMetaContent() + vfs := NewPageVFS(s.client, s.Backend, owner, repo, info.ID) + rel.CommitID = info.ID + rel.LastModified = info.LastModified + + // 检查是否存在 index.html + if exists, _ := vfs.Exists(ctx, "index.html"); !exists { rel.IsPage = false _ = s.cache.Store(ctx, key, *rel) - return nil, os.ErrNotExist + return nil, nil, os.ErrNotExist } + rel.IsPage = true - errCall := func(err error) error { - rel.IsPage = false - rel.ErrorMsg = err.Error() - _ = s.cache.Store(ctx, key, *rel) - return err - } + // 添加默认跳过的内容 for _, defIgnore := range rel.Ignore { rel.ignoreL = append(rel.ignoreL, glob.MustCompile(defIgnore)) } + // 解析配置 - 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 nil, errCall(err) - } - rel.VRoute = cfg.VirtualRoute - // 处理 CNAME - for _, cname := range cfg.Alias { - cname = strings.TrimSpace(cname) - if regexpHostname.MatchString(cname) && !strings.HasSuffix(strings.ToLower(cname), strings.ToLower(s.Domain)) { - rel.Alias = append(rel.Alias, cname) - } else { - 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 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{ - regex: g, - Render: r, - }) - } else { - return nil, errCall(err) - } - rel.Renders[sType] = append(rel.Renders[sType], pattern) - } - // 处理跳过内容 - for _, pattern := range cfg.Ignores() { - if g, err := glob.Compile(pattern); err == nil { - rel.ignoreL = append(rel.ignoreL, g) - } else { - return nil, errCall(err) - } - rel.Ignore = append(rel.Ignore, pattern) - } - // 处理反向代理 (清理内容,符合 /) - for path, backend := range cfg.ReverseProxy { - path = filepath.ToSlash(filepath.Clean(path)) - if !strings.HasPrefix(path, "/") { - path = "/" + path - } - path = strings.TrimSuffix(path, "/") - var rURL *url.URL - if rURL, err = url.Parse(backend); err != nil { - return nil, errCall(err) - } - if rURL.Scheme != "http" && rURL.Scheme != "https" { - return nil, errCall(errors.New("invalid backend url " + backend)) - } - rel.Proxy[path] = rURL.String() - } - } else { - // 不存在配置,但也可以重定向 - zap.L().Debug("failed to read meta data", zap.String("error", err.Error())) + if err := s.parsePageConfig(ctx, rel, vfs); err != nil { + rel.IsPage = false + rel.ErrorMsg = err.Error() + _ = s.cache.Store(ctx, key, *rel) + return nil, nil, err } - // 兼容 github 的 CNAME 模式 - if cname, err := s.ReadString(ctx, owner, repo, rel.CommitID, "CNAME"); err == nil { - cname = strings.TrimSpace(cname) - if regexpHostname.MatchString(cname) && !strings.HasSuffix(strings.ToLower(cname), strings.ToLower(s.Domain)) { - rel.Alias = append(rel.Alias, cname) - } else { - zap.L().Debug("指定的 CNAME 不合法", zap.String("cname", cname)) - } + // 处理 CNAME 文件 + if err := s.parseCNAME(ctx, rel, vfs); err != nil { + rel.IsPage = false + rel.ErrorMsg = err.Error() + _ = s.cache.Store(ctx, key, *rel) + return nil, nil, err } + rel.Alias = utils.ClearDuplicates(rel.Alias) rel.Ignore = utils.ClearDuplicates(rel.Ignore) _ = s.cache.Store(ctx, key, *rel) - return rel, nil + return rel, vfs, nil } -func (s *ServerMeta) ReadString(ctx context.Context, owner, repo, branch, path string) (string, error) { - resp, err := s.Open(ctx, s.client, owner, repo, branch, path, nil) - if resp != nil { - defer resp.Body.Close() - } - if err != nil || resp == nil { - return "", err - } - if resp.StatusCode != http.StatusOK { - return "", os.ErrNotExist - } - all, err := io.ReadAll(resp.Body) +func (s *ServerMeta) parsePageConfig(ctx context.Context, rel *PageMetaContent, vfs *PageVFS) error { + data, err := vfs.ReadString(ctx, ".pages.yaml") if err != nil { - return "", err + zap.L().Debug("failed to read meta data", zap.String("error", err.Error())) + return nil // 配置文件不存在不是错误 } - return string(all), nil + + cfg := new(PageConfig) + if err = yaml.Unmarshal([]byte(data), cfg); err != nil { + return errors.Wrap(err, "parse .pages.yaml failed") + } + + rel.VRoute = cfg.VirtualRoute + + // 处理别名 + for _, cname := range cfg.Alias { + if err := s.addAlias(rel, cname); err != nil { + return err + } + } + + // 处理渲染器 + for sType, pattern := range cfg.Renders() { + r := GetRender(sType) + if r == nil { + return errors.Errorf("render not found %s", sType) + } + + g, err := glob.Compile(strings.TrimSpace(pattern)) + if err != nil { + return errors.Wrapf(err, "compile render pattern failed: %s", pattern) + } + + rel.rendersL = append(rel.rendersL, &renderCompiler{ + regex: g, + Render: r, + }) + rel.Renders[sType] = append(rel.Renders[sType], pattern) + } + + // 处理跳过内容 + for _, pattern := range cfg.Ignores() { + g, err := glob.Compile(pattern) + if err != nil { + return errors.Wrapf(err, "compile ignore pattern failed: %s", pattern) + } + rel.ignoreL = append(rel.ignoreL, g) + rel.Ignore = append(rel.Ignore, pattern) + } + + // 处理反向代理 + for path, backend := range cfg.ReverseProxy { + path = filepath.ToSlash(filepath.Clean(path)) + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + path = strings.TrimSuffix(path, "/") + + rURL, err := url.Parse(backend) + if err != nil { + return errors.Wrapf(err, "parse backend url failed: %s", backend) + } + + if rURL.Scheme != "http" && rURL.Scheme != "https" { + return errors.Errorf("invalid backend url scheme: %s", backend) + } + + rel.Proxy[path] = rURL.String() + } + + return nil } -func (s *ServerMeta) FileExists(ctx context.Context, owner, repo, branch, path string) (bool, error) { - resp, err := s.Open(ctx, s.client, owner, repo, branch, path, nil) - if resp != nil { - defer resp.Body.Close() +func (s *ServerMeta) parseCNAME(ctx context.Context, rel *PageMetaContent, vfs *PageVFS) error { + cname, err := vfs.ReadString(ctx, "CNAME") + if err != nil { + return nil // CNAME 文件不存在是正常情况 } - if err != nil || resp == nil { - return false, err + if err := s.addAlias(rel, cname); err != nil { + zap.L().Debug("指定的 CNAME 不合法", zap.String("cname", cname), zap.Error(err)) + return err } - if resp.StatusCode == http.StatusOK { - return true, nil - } - return false, nil + return nil +} + +func (s *ServerMeta) addAlias(rel *PageMetaContent, cname string) error { + cname = strings.TrimSpace(cname) + if !regexpHostname.MatchString(cname) { + return errors.New("invalid domain name format") + } + + if strings.HasSuffix(strings.ToLower(cname), strings.ToLower(s.Domain)) { + return errors.New("alias cannot be subdomain of main domain") + } + + rel.Alias = append(rel.Alias, cname) + return nil } diff --git a/pkg/core/meta_content.go b/pkg/core/meta_content.go index 22301ca..60b728e 100644 --- a/pkg/core/meta_content.go +++ b/pkg/core/meta_content.go @@ -32,7 +32,7 @@ type PageMetaContent struct { ignoreL []glob.Glob } -func NewPageMetaContent() *PageMetaContent { +func NewEmptyPageMetaContent() *PageMetaContent { return &PageMetaContent{ IsPage: false, Proxy: make(map[string]string), diff --git a/pkg/core/page.go b/pkg/core/page.go index 7a2e941..9b25f6b 100644 --- a/pkg/core/page.go +++ b/pkg/core/page.go @@ -73,3 +73,11 @@ func (p *PageVFS) Read(ctx context.Context, path string) ([]byte, error) { defer open.Close() return io.ReadAll(open) } + +func (p *PageVFS) ReadString(ctx context.Context, path string) (string, error) { + read, err := p.Read(ctx, path) + if err != nil { + return "", err + } + return string(read), nil +} diff --git a/pkg/core/parser.go b/pkg/core/parser.go deleted file mode 100644 index 570cee8..0000000 --- a/pkg/core/parser.go +++ /dev/null @@ -1,61 +0,0 @@ -package core - -import ( - "net/http" - "regexp" - "strings" -) - -type Domain struct { - Org string `json:"org"` - Repo string `json:"repo"` - Branch string `json:"branch"` // commit id or branch - Path string `json:"path"` -} - -var portExp = regexp.MustCompile(`:\d+$`) - -type DomainParser struct { - baseDomain string - defaultBranch string - alias *DomainAlias -} - -func (d *DomainParser) ParseDomains(request *http.Request) ([]Domain, error) { - host := portExp.ReplaceAllString(strings.ToLower(request.Host), "") - path := strings.Split(strings.Trim(request.URL.Path, "/"), "/") - branch := request.URL.Query().Get("branch") - if branch == "" { - branch = d.defaultBranch - } - result := make([]Domain, 0) - if strings.HasSuffix(host, d.baseDomain) { - org := strings.TrimSuffix(host, d.baseDomain) - if len(path) > 1 { - // repo.base.com/path - result = append(result, Domain{ - Org: org, - Repo: path[0], - Branch: branch, - Path: strings.Join(path[1:], "/"), - }) - } - // repo.base.com/ - result = append(result, Domain{ - Org: org, - Repo: host, - Branch: branch, - Path: strings.Join(path, "/"), - }) - } else { - if find, _ := d.alias.Query(request.Context(), host); find != nil { - result = append(result, Domain{ - Org: find.Owner, - Repo: find.Repo, - Branch: find.Branch, - Path: request.URL.Path, - }) - } - } - return result, nil -} diff --git a/pkg/core/render.go b/pkg/core/render.go index 73eab32..5bb8f23 100644 --- a/pkg/core/render.go +++ b/pkg/core/render.go @@ -1,6 +1,7 @@ package core import ( + "context" "io" "net/http" "sync" @@ -12,7 +13,7 @@ var ( ) type Render interface { - Render(w http.ResponseWriter, r *http.Request, input io.Reader) error + Render(ctx context.Context, w http.ResponseWriter, r *http.Request, input io.Reader, meta *PageDomainContent) error } func RegisterRender(fType string, r Render) { diff --git a/pkg/renders/gotemplate.go b/pkg/renders/gotemplate.go index d4803c2..a0b3f84 100644 --- a/pkg/renders/gotemplate.go +++ b/pkg/renders/gotemplate.go @@ -2,6 +2,7 @@ package renders import ( "bytes" + "context" "io" "net/http" @@ -16,14 +17,17 @@ func init() { core.RegisterRender("gotemplate", &GoTemplate{}) } -func (g GoTemplate) Render(w http.ResponseWriter, r *http.Request, input io.Reader) error { - dataB, err := io.ReadAll(input) +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(string(dataB)) + 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 } diff --git a/pkg/server.go b/pkg/server.go index 5762a35..ce54ea6 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -1,6 +1,8 @@ package pkg import ( + "context" + "errors" "fmt" "io" "net" @@ -15,16 +17,11 @@ import ( "time" "github.com/google/uuid" - "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/middleware/cache" + "gopkg.d7z.net/middleware/kv" ) var portExp = regexp.MustCompile(`:\d+$`) @@ -94,9 +91,6 @@ type Server struct { var staticPrefix = "/.well-known/page-server/" func NewPageServer(backend core.Backend, options ServerOptions) *Server { - backend = core.NewCacheBackend(backend, options.CacheMeta, options.CacheMetaTTL, - options.CacheBlob, options.CacheBlobLimit, - ) 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 @@ -135,24 +129,22 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error { ctx := request.Context() - domainHost := portExp.ReplaceAllString(strings.ToLower(request.Host), "") - - meta, err := s.meta.ParseDomainMeta(ctx, domainHost, request.URL.Path, request.URL.Query().Get("branch")) + domain := portExp.ReplaceAllString(strings.ToLower(request.Host), "") + meta, err := s.meta.ParseDomainMeta(ctx, domain, request.URL.Path, request.URL.Query().Get("branch")) if err != nil { return err } zap.L().Debug("new request", zap.Any("request path", meta.Path)) - if len(meta.Alias) > 0 && !slices.Contains(meta.Alias, domainHost) { + if len(meta.Alias) > 0 && !slices.Contains(meta.Alias, domain) { // 重定向到配置的地址 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 } - // 处理反向代理 if s.options.EnableProxy && s.Proxy(writer, request, meta) { return nil } - // 在非反向代理时处理目录访问 + if strings.HasSuffix(meta.Path, "/") || meta.Path == "" { meta.Path += "index.html" } @@ -162,66 +154,62 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error } if meta.IgnorePath(meta.Path) { zap.L().Debug("ignore path", zap.Any("request", request.RequestURI), zap.Any("meta.path", meta.Path)) - err = os.ErrNotExist - } - type resp struct { - IsError bool - Path string + return os.ErrNotExist } - callPath := []resp{{false, meta.Path}} + var resp *http.Response + var path string + failback := []string{meta.Path, meta.Path + "/index.html"} if meta.VRoute { - callPath = append(callPath, resp{false, "index.html"}) - } else { - callPath = append(callPath, resp{false, meta.Path + "/index.html"}) + failback = append(failback, "index.html") } - callPath = append(callPath, resp{true, "404.html"}) - - var callResp *http.Response - callErr := os.ErrNotExist - var callRespMeta resp - for _, r := range callPath { - callResp, callErr = meta.NativeOpen(request.Context(), r.Path, nil) - if callErr != nil { - if callResp != nil { - _ = callResp.Body.Close() + failback = append(failback, "404.html") + for _, p := range failback { + resp, err = meta.NativeOpen(request.Context(), p, nil) + if err != nil { + if resp != nil { + resp.Body.Close() } - if !errors.Is(callErr, os.ErrNotExist) { - zap.L().Debug("error", zap.Any("error", callErr)) + if !errors.Is(err, os.ErrNotExist) { + zap.L().Debug("error", zap.Any("error", err)) } - callRespMeta = r continue } + path = p break } - if callResp == nil { + if resp == nil { return os.ErrNotExist } - if callErr != nil { - // 回退失败 - return callErr + defer resp.Body.Close() + + if err != nil { + return err } - render := meta.TryRender(meta.Path) - writer.Header().Set("Content-Type", callResp.Header.Get("Content-Type")) - if callRespMeta.IsError { - render = meta.TryRender(meta.Path) + if path == "404.html" && request.URL.Path != "/404.html" { writer.WriteHeader(http.StatusNotFound) - } else if render == nil { - lastMod, err := time.Parse(http.TimeFormat, callResp.Header.Get("Last-Modified")) - if seekResp, ok := callResp.Body.(io.ReadSeeker); ok && err == nil { - http.ServeContent(writer, request, filepath.Base(callRespMeta.Path), lastMod, seekResp) - } - } else { - defer callResp.Body.Close() - return render.Render(writer, request, callResp.Body) } - return nil + ctx, cancel := context.WithTimeout(request.Context(), 3*time.Second) + defer cancel() + if render := meta.TryRender(path); render != nil { + return render.Render(ctx, writer, request, resp.Body, meta) + } + writer.Header().Set("Content-Type", resp.Header.Get("Content-Type")) + lastMod, err := time.Parse(http.TimeFormat, resp.Header.Get("Last-Modified")) + if err == nil { + if seeker, ok := resp.Body.(io.ReadSeeker); ok && !(path == "404.html" && request.URL.Path != "/404.html") { + http.ServeContent(writer, request, filepath.Base(path), lastMod, seeker) + return nil + } + } + _, err = io.Copy(writer, resp.Body) + return err } func (s *Server) Proxy(writer http.ResponseWriter, request *http.Request, meta *core.PageDomainContent) bool { + proxyPath := "/" + meta.Path for prefix, backend := range meta.Proxy { - proxyPath := "/" + meta.Path if strings.HasPrefix(proxyPath, prefix) { targetPath := strings.TrimPrefix(proxyPath, prefix) if !strings.HasPrefix(targetPath, "/") { @@ -250,7 +238,7 @@ func (s *Server) Proxy(writer http.ResponseWriter, request *http.Request, meta * } func (s *Server) Close() error { - return stdErr.Join( + return errors.Join( s.options.CacheBlob.Close(), s.options.CacheMeta.Close(), s.options.Alias.Close(), diff --git a/pkg/utils/template.go b/pkg/utils/template.go index 4a05a90..aa121db 100644 --- a/pkg/utils/template.go +++ b/pkg/utils/template.go @@ -27,17 +27,17 @@ func NewTemplateInject(r *http.Request, def map[string]any) map[string]any { } func MustTemplate(data string) *template.Template { - newTemplate, err := NewTemplate(data) + parse, err := NewTemplate().Parse(data) if err != nil { panic(err) } - return newTemplate + return parse } -func NewTemplate(data string) (*template.Template, error) { +func NewTemplate() *template.Template { funcMap := sprig.FuncMap() delete(funcMap, "env") delete(funcMap, "expandenv") t := template.New("tmpl").Funcs(funcMap) - return t.Parse(data) + return t }