重构项目
This commit is contained in:
5
Makefile
5
Makefile
@@ -54,3 +54,8 @@ lint:
|
|||||||
lint-fix:
|
lint-fix:
|
||||||
@(test -f "$(GOPATH)/bin/golangci-lint" || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.6.0) && \
|
@(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
|
"$(GOPATH)/bin/golangci-lint" run -c .golangci.yml --fix
|
||||||
|
|
||||||
|
EXAMPLE_DIRS := $(shell find examples -maxdepth 1 -type d ! -path "examples" | sort)
|
||||||
|
.PHONY: $(EXAMPLE_DIRS)
|
||||||
|
$(EXAMPLE_DIRS):
|
||||||
|
@go run ./cmd/local/main.go -path $@
|
||||||
@@ -1,8 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gopkg.d7z.net/gitea-pages/pkg"
|
||||||
|
"gopkg.d7z.net/gitea-pages/pkg/providers"
|
||||||
|
"gopkg.d7z.net/middleware/cache"
|
||||||
|
"gopkg.d7z.net/middleware/kv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -11,19 +23,68 @@ var (
|
|||||||
repo = org + "." + domain
|
repo = org + "." + domain
|
||||||
path = ""
|
path = ""
|
||||||
|
|
||||||
port = 8080
|
port = ":8080"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
atom := zap.NewAtomicLevel()
|
||||||
|
atom.SetLevel(zap.DebugLevel)
|
||||||
|
cfg := zap.NewProductionConfig()
|
||||||
|
cfg.Level = atom
|
||||||
|
logger, _ := cfg.Build()
|
||||||
|
zap.ReplaceGlobals(logger)
|
||||||
dir, _ := os.Getwd()
|
dir, _ := os.Getwd()
|
||||||
path = dir
|
path = dir
|
||||||
|
zap.L().Info("exec workdir", zap.String("path", path))
|
||||||
flag.StringVar(&org, "org", org, "org")
|
flag.StringVar(&org, "org", org, "org")
|
||||||
flag.StringVar(&repo, "repo", repo, "repo")
|
flag.StringVar(&repo, "repo", repo, "repo")
|
||||||
flag.StringVar(&domain, "domain", domain, "domain")
|
flag.StringVar(&domain, "domain", domain, "domain")
|
||||||
flag.StringVar(&path, "path", path, "path")
|
flag.StringVar(&path, "path", path, "path")
|
||||||
flag.IntVar(&port, "port", port, "port")
|
flag.StringVar(&port, "port", port, "port")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
fmt.Printf("请访问 http://%s%s/", repo, port)
|
||||||
|
if stat, err := os.Stat(path); err != nil || !stat.IsDir() {
|
||||||
|
zap.L().Fatal("path is not a directory", zap.String("path", path))
|
||||||
|
}
|
||||||
|
provider := providers.NewLocalProvider(map[string][]string{
|
||||||
|
org: {repo},
|
||||||
|
}, path)
|
||||||
|
memory, err := kv.NewMemory("")
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Fatal("failed to init memory provider", zap.Error(err))
|
||||||
|
}
|
||||||
|
server := pkg.NewPageServer(http.DefaultClient,
|
||||||
|
provider, domain, "gh-pages", memory, memory, 0, &nopCache{},
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
err = http.ListenAndServe(port, server)
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
zap.L().Fatal("failed to start server", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopCache struct{}
|
||||||
|
|
||||||
|
func (n *nopCache) Child(_ string) cache.Cache {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nopCache) Put(_ context.Context, _ string, _ map[string]string, _ io.Reader, _ time.Duration) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nopCache) Get(_ context.Context, _ string) (*cache.Content, error) {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nopCache) Delete(_ context.Context, _ string) error {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -36,7 +37,7 @@ func main() {
|
|||||||
log.Fatalf("fail to load config file: %v", err)
|
log.Fatalf("fail to load config file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gitea, err := providers.NewGitea(config.Auth.Server, config.Auth.Token)
|
gitea, err := providers.NewGitea(http.DefaultClient, config.Auth.Server, config.Auth.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer cacheBlob.Close()
|
defer cacheBlob.Close()
|
||||||
backend := providers.NewProviderCache(gitea, cacheMeta, config.Cache.MetaTTL,
|
backend := providers.NewProviderCache(gitea, cacheMeta, config.Cache.MetaTTL,
|
||||||
cacheBlob, uint64(config.Cache.BlobLimit),
|
cacheBlob.Child("backend"), uint64(config.Cache.BlobLimit),
|
||||||
)
|
)
|
||||||
defer backend.Close()
|
defer backend.Close()
|
||||||
db, err := kv.NewKVFromURL(config.Database.URL)
|
db, err := kv.NewKVFromURL(config.Database.URL)
|
||||||
@@ -59,14 +60,19 @@ func main() {
|
|||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
cdb, ok := db.(kv.RawKV).Raw().(kv.CursorPagedKV)
|
||||||
|
if !ok {
|
||||||
|
log.Fatalln(errors.New("database not support cursor"))
|
||||||
|
}
|
||||||
pageServer := pkg.NewPageServer(
|
pageServer := pkg.NewPageServer(
|
||||||
http.DefaultClient,
|
http.DefaultClient,
|
||||||
backend,
|
backend,
|
||||||
config.Domain,
|
config.Domain,
|
||||||
config.Page.DefaultBranch,
|
config.Page.DefaultBranch,
|
||||||
db,
|
cdb,
|
||||||
cacheMeta,
|
cacheMeta,
|
||||||
config.Cache.MetaTTL,
|
config.Cache.MetaTTL,
|
||||||
|
cacheBlob.Child("filter"),
|
||||||
config.ErrorHandler,
|
config.ErrorHandler,
|
||||||
)
|
)
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
|||||||
13
examples/HelloWorld/index.html
Normal file
13
examples/HelloWorld/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>qjs 验证</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Hello World</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
examples/QuickJS/.pages.yaml
Normal file
5
examples/QuickJS/.pages.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
routes:
|
||||||
|
- path: "api/v1/**"
|
||||||
|
qjs:
|
||||||
|
exec: "index.js"
|
||||||
|
debug: true
|
||||||
13
examples/QuickJS/index.html
Normal file
13
examples/QuickJS/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
examples/QuickJS/index.js
Normal file
7
examples/QuickJS/index.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
response.write("hello world")
|
||||||
|
console.log("hello world")
|
||||||
|
console.log(req.methaaod)
|
||||||
|
function aaa(){
|
||||||
|
throw Error("Method not implemented")
|
||||||
|
}
|
||||||
|
aaa()
|
||||||
2
go.mod
2
go.mod
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
gopkg.d7z.net/middleware v0.0.0-20251114092249-67753b883a45
|
gopkg.d7z.net/middleware v0.0.0-20251114145539-bb74bd940f32
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -195,6 +195,12 @@ gopkg.d7z.net/middleware v0.0.0-20251113064153-9f946bf959f5 h1:RwEXivoUP8qEbKxRW
|
|||||||
gopkg.d7z.net/middleware v0.0.0-20251113064153-9f946bf959f5/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM=
|
gopkg.d7z.net/middleware v0.0.0-20251113064153-9f946bf959f5/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM=
|
||||||
gopkg.d7z.net/middleware v0.0.0-20251114092249-67753b883a45 h1:ujyhl4Di/z6fGOcIAqydzRQNwI13F9JD3xj8+s+rTVM=
|
gopkg.d7z.net/middleware v0.0.0-20251114092249-67753b883a45 h1:ujyhl4Di/z6fGOcIAqydzRQNwI13F9JD3xj8+s+rTVM=
|
||||||
gopkg.d7z.net/middleware v0.0.0-20251114092249-67753b883a45/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM=
|
gopkg.d7z.net/middleware v0.0.0-20251114092249-67753b883a45/go.mod h1:BJ8ySXqmlBpM9B2zFJfmvYQ61XPA+G0O1VDmYomxyrM=
|
||||||
|
gopkg.d7z.net/middleware v0.0.0-20251114132513-93389190aeca h1:5RUUXCIUFBMNvCXiYNDdcEu27HeNr3mfH7MRKS1ftdo=
|
||||||
|
gopkg.d7z.net/middleware v0.0.0-20251114132513-93389190aeca/go.mod h1:/1/EuissKhUbuhUe01rcWuwpA5mt7jASb4uKVNOLtR8=
|
||||||
|
gopkg.d7z.net/middleware v0.0.0-20251114144707-95f41bfca5bc h1:mPcaskQN8j32dI59txtCAFVIKUb427bh7sXS9adv2jM=
|
||||||
|
gopkg.d7z.net/middleware v0.0.0-20251114144707-95f41bfca5bc/go.mod h1:/1/EuissKhUbuhUe01rcWuwpA5mt7jASb4uKVNOLtR8=
|
||||||
|
gopkg.d7z.net/middleware v0.0.0-20251114145539-bb74bd940f32 h1:3JvqnWFLWzAoS57vLBT1LVePO3RqR32ijM3ZyjyoqyY=
|
||||||
|
gopkg.d7z.net/middleware v0.0.0-20251114145539-bb74bd940f32/go.mod h1:/1/EuissKhUbuhUe01rcWuwpA5mt7jASb4uKVNOLtR8=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|||||||
@@ -19,5 +19,5 @@ type Backend interface {
|
|||||||
// Branches return branch + commit id
|
// Branches return branch + commit id
|
||||||
Branches(ctx context.Context, owner, repo string) (map[string]*BranchInfo, error)
|
Branches(ctx context.Context, owner, repo string) (map[string]*BranchInfo, error)
|
||||||
// Open return file or error (error)
|
// Open return file or error (error)
|
||||||
Open(ctx context.Context, client *http.Client, owner, repo, commit, path string, headers http.Header) (*http.Response, error)
|
Open(ctx context.Context, owner, repo, commit, path string, headers http.Header) (*http.Response, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,36 +7,31 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.d7z.net/middleware/kv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PageDomain struct {
|
type PageDomain struct {
|
||||||
*ServerMeta
|
*ServerMeta
|
||||||
|
|
||||||
alias *DomainAlias
|
alias *DomainAlias
|
||||||
pageDB kv.KV
|
|
||||||
baseDomain string
|
baseDomain string
|
||||||
defaultBranch string
|
defaultBranch string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPageDomain(meta *ServerMeta, alias *DomainAlias, pageDB kv.KV, baseDomain, defaultBranch string) *PageDomain {
|
func NewPageDomain(meta *ServerMeta, alias *DomainAlias, baseDomain, defaultBranch string) *PageDomain {
|
||||||
return &PageDomain{
|
return &PageDomain{
|
||||||
baseDomain: baseDomain,
|
baseDomain: baseDomain,
|
||||||
defaultBranch: defaultBranch,
|
defaultBranch: defaultBranch,
|
||||||
ServerMeta: meta,
|
ServerMeta: meta,
|
||||||
alias: alias,
|
alias: alias,
|
||||||
pageDB: pageDB,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageContent struct {
|
type PageContent struct {
|
||||||
*PageMetaContent
|
*PageMetaContent
|
||||||
*PageVFS
|
|
||||||
OrgDB kv.KV
|
Owner string
|
||||||
RepoDB kv.KV
|
Repo string
|
||||||
Owner string
|
Path string
|
||||||
Repo string
|
|
||||||
Path string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PageDomain) ParseDomainMeta(ctx context.Context, domain, path, branch string) (*PageContent, error) {
|
func (p *PageDomain) ParseDomainMeta(ctx context.Context, domain, path, branch string) (*PageContent, error) {
|
||||||
@@ -87,9 +82,6 @@ func (p *PageDomain) returnMeta(ctx context.Context, owner, repo, branch string,
|
|||||||
result.PageMetaContent = meta
|
result.PageMetaContent = meta
|
||||||
result.Owner = owner
|
result.Owner = owner
|
||||||
result.Repo = repo
|
result.Repo = repo
|
||||||
result.PageVFS = NewPageVFS(p.client, p.Backend, owner, repo, result.CommitID)
|
|
||||||
result.OrgDB = p.pageDB.Child("org").Child(owner)
|
|
||||||
result.RepoDB = p.pageDB.Child("repo").Child(owner).Child(repo)
|
|
||||||
result.Path = strings.Join(path, "/")
|
result.Path = strings.Join(path, "/")
|
||||||
|
|
||||||
if err = p.alias.Bind(ctx, meta.Alias, result.Owner, result.Repo, branch); err != nil {
|
if err = p.alias.Bind(ctx, meta.Alias, result.Owner, result.Repo, branch); err != nil {
|
||||||
|
|||||||
@@ -9,8 +9,19 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"gopkg.d7z.net/middleware/kv"
|
||||||
|
"gopkg.d7z.net/middleware/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FilterContext struct {
|
||||||
|
context.Context
|
||||||
|
*PageContent
|
||||||
|
*PageVFS
|
||||||
|
Cache *tools.TTLCache
|
||||||
|
OrgDB kv.CursorPagedKV
|
||||||
|
RepoDB kv.CursorPagedKV
|
||||||
|
}
|
||||||
|
|
||||||
type FilterParams map[string]any
|
type FilterParams map[string]any
|
||||||
|
|
||||||
func (f FilterParams) String() string {
|
func (f FilterParams) String() string {
|
||||||
@@ -33,30 +44,28 @@ type Filter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NextCallWrapper(call FilterCall, parentCall NextCall, stack Filter) NextCall {
|
func NextCallWrapper(call FilterCall, parentCall NextCall, stack Filter) NextCall {
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *PageContent) error {
|
return func(ctx FilterContext, writer http.ResponseWriter, request *http.Request) error {
|
||||||
zap.L().Debug(fmt.Sprintf("call filter(%s) before", stack.Type), zap.Any("filter", stack))
|
zap.L().Debug(fmt.Sprintf("call filter(%s) before", stack.Type), zap.Any("filter", stack))
|
||||||
err := call(ctx, writer, request, metadata, parentCall)
|
err := call(ctx, writer, request, parentCall)
|
||||||
zap.L().Debug(fmt.Sprintf("call filter(%s) after", stack.Type), zap.Any("filter", stack), zap.Error(err))
|
zap.L().Debug(fmt.Sprintf("call filter(%s) after", stack.Type), zap.Any("filter", stack), zap.Error(err))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type NextCall func(
|
type NextCall func(
|
||||||
ctx context.Context,
|
ctx FilterContext,
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request,
|
||||||
metadata *PageContent,
|
|
||||||
) error
|
) error
|
||||||
|
|
||||||
var NotFountNextCall = func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *PageContent) error {
|
var NotFountNextCall = func(ctx FilterContext, writer http.ResponseWriter, request *http.Request) error {
|
||||||
return os.ErrNotExist
|
return os.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterCall func(
|
type FilterCall func(
|
||||||
ctx context.Context,
|
ctx FilterContext,
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request,
|
||||||
metadata *PageContent,
|
|
||||||
next NextCall,
|
next NextCall,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ type PageMetaContent struct {
|
|||||||
IsPage bool `json:"is_page"` // 是否为 Page
|
IsPage bool `json:"is_page"` // 是否为 Page
|
||||||
ErrorMsg string `json:"error"` // 错误消息 (作为 500 错误日志暴露至前端)
|
ErrorMsg string `json:"error"` // 错误消息 (作为 500 错误日志暴露至前端)
|
||||||
|
|
||||||
Alias []string `json:"alias"` // alias
|
Alias []string `json:"alias"` // alias
|
||||||
|
|
||||||
Filters []Filter `json:"filters"` // 路由消息
|
Filters []Filter `json:"filters"` // 路由消息
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +126,7 @@ func (s *ServerMeta) GetMeta(ctx context.Context, owner, repo, branch string) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
rel := NewEmptyPageMetaContent()
|
rel := NewEmptyPageMetaContent()
|
||||||
vfs := NewPageVFS(s.client, s.Backend, owner, repo, info.ID)
|
vfs := NewPageVFS(s.Backend, owner, repo, info.ID)
|
||||||
rel.CommitID = info.ID
|
rel.CommitID = info.ID
|
||||||
rel.LastModified = info.LastModified
|
rel.LastModified = info.LastModified
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
type PageVFS struct {
|
type PageVFS struct {
|
||||||
backend Backend
|
backend Backend
|
||||||
client *http.Client
|
|
||||||
|
|
||||||
org string
|
org string
|
||||||
repo string
|
repo string
|
||||||
@@ -18,14 +17,12 @@ type PageVFS struct {
|
|||||||
|
|
||||||
// todo: 限制最大文件加载大小
|
// todo: 限制最大文件加载大小
|
||||||
func NewPageVFS(
|
func NewPageVFS(
|
||||||
client *http.Client,
|
|
||||||
backend Backend,
|
backend Backend,
|
||||||
org string,
|
org string,
|
||||||
repo string,
|
repo string,
|
||||||
commitID string,
|
commitID string,
|
||||||
) *PageVFS {
|
) *PageVFS {
|
||||||
return &PageVFS{
|
return &PageVFS{
|
||||||
client: client,
|
|
||||||
backend: backend,
|
backend: backend,
|
||||||
org: org,
|
org: org,
|
||||||
repo: repo,
|
repo: repo,
|
||||||
@@ -34,7 +31,7 @@ func NewPageVFS(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PageVFS) NativeOpen(ctx context.Context, path string, headers http.Header) (*http.Response, error) {
|
func (p *PageVFS) NativeOpen(ctx context.Context, path string, headers http.Header) (*http.Response, error) {
|
||||||
return p.backend.Open(ctx, p.client, p.org, p.repo, p.commitID, path, headers)
|
return p.backend.Open(ctx, p.org, p.repo, p.commitID, path, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PageVFS) Exists(ctx context.Context, path string) (bool, error) {
|
func (p *PageVFS) Exists(ctx context.Context, path string) (bool, error) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||||
@@ -21,7 +20,7 @@ var FilterInstBlock core.FilterInstance = func(config core.FilterParams) (core.F
|
|||||||
if param.Message == "" {
|
if param.Message == "" {
|
||||||
param.Message = http.StatusText(param.Code)
|
param.Message = http.StatusText(param.Code)
|
||||||
}
|
}
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
writer.WriteHeader(param.Code)
|
writer.WriteHeader(param.Code)
|
||||||
if param.Message != "" {
|
if param.Message != "" {
|
||||||
_, _ = writer.Write([]byte(param.Message))
|
_, _ = writer.Write([]byte(param.Message))
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,10 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var FilterInstDefaultNotFound core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) {
|
var FilterInstDefaultNotFound core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) {
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
err := next(ctx, writer, request, metadata)
|
err := next(ctx, writer, request)
|
||||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||||
open, err := metadata.NativeOpen(ctx, "/404.html", nil)
|
open, err := ctx.NativeOpen(ctx, "/404.html", nil)
|
||||||
if open != nil {
|
if open != nil {
|
||||||
defer open.Body.Close()
|
defer open.Body.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -23,8 +22,8 @@ var FilterInstDirect core.FilterInstance = func(config core.FilterParams) (core.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
param.Prefix = strings.Trim(param.Prefix, "/") + "/"
|
param.Prefix = strings.Trim(param.Prefix, "/") + "/"
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
err := next(ctx, writer, request, metadata)
|
err := next(ctx, writer, request)
|
||||||
if (err != nil && !errors.Is(err, os.ErrNotExist)) || err == nil {
|
if (err != nil && !errors.Is(err, os.ErrNotExist)) || err == nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -34,10 +33,10 @@ var FilterInstDirect core.FilterInstance = func(config core.FilterParams) (core.
|
|||||||
}
|
}
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
var path string
|
var path string
|
||||||
defaultPath := param.Prefix + strings.TrimSuffix(metadata.Path, "/")
|
defaultPath := param.Prefix + strings.TrimSuffix(ctx.Path, "/")
|
||||||
for _, p := range []string{defaultPath, defaultPath + "/index.html"} {
|
for _, p := range []string{defaultPath, defaultPath + "/index.html"} {
|
||||||
zap.L().Debug("direct fetch", zap.String("path", p))
|
zap.L().Debug("direct fetch", zap.String("path", p))
|
||||||
resp, err = metadata.NativeOpen(request.Context(), p, nil)
|
resp, err = ctx.NativeOpen(request.Context(), p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -23,12 +22,12 @@ var FilterInstFailback core.FilterInstance = func(config core.FilterParams) (cor
|
|||||||
if param.Path == "" {
|
if param.Path == "" {
|
||||||
return nil, errors.Errorf("filter failback: path is empty")
|
return nil, errors.Errorf("filter failback: path is empty")
|
||||||
}
|
}
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
err := next(ctx, writer, request, metadata)
|
err := next(ctx, writer, request)
|
||||||
if (err != nil && !errors.Is(err, os.ErrNotExist)) || err == nil {
|
if (err != nil && !errors.Is(err, os.ErrNotExist)) || err == nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err := metadata.NativeOpen(ctx, param.Path, nil)
|
resp, err := ctx.NativeOpen(ctx, param.Path, nil)
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -22,8 +21,8 @@ var FilterInstProxy core.FilterInstance = func(config core.FilterParams) (core.F
|
|||||||
if err := config.Unmarshal(¶m); err != nil {
|
if err := config.Unmarshal(¶m); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
proxyPath := "/" + metadata.Path
|
proxyPath := "/" + ctx.Path
|
||||||
targetPath := strings.TrimPrefix(proxyPath, param.Prefix)
|
targetPath := strings.TrimPrefix(proxyPath, param.Prefix)
|
||||||
if !strings.HasPrefix(targetPath, "/") {
|
if !strings.HasPrefix(targetPath, "/") {
|
||||||
targetPath = "/" + targetPath
|
targetPath = "/" + targetPath
|
||||||
@@ -38,7 +37,7 @@ var FilterInstProxy core.FilterInstance = func(config core.FilterParams) (core.F
|
|||||||
request.Header.Set("X-Real-IP", host)
|
request.Header.Set("X-Real-IP", host)
|
||||||
}
|
}
|
||||||
request.Header.Set("X-Page-IP", utils.GetRemoteIP(request))
|
request.Header.Set("X-Page-IP", utils.GetRemoteIP(request))
|
||||||
request.Header.Set("X-Page-Refer", fmt.Sprintf("%s/%s/%s", metadata.Owner, metadata.Repo, metadata.Path))
|
request.Header.Set("X-Page-Refer", fmt.Sprintf("%s/%s/%s", ctx.Owner, ctx.Repo, ctx.Path))
|
||||||
request.Header.Set("X-Page-Host", request.Host)
|
request.Header.Set("X-Page-Host", request.Host)
|
||||||
zap.L().Debug("命中反向代理", zap.Any("prefix", param.Prefix), zap.Any("target", param.Target),
|
zap.L().Debug("命中反向代理", zap.Any("prefix", param.Prefix), zap.Any("target", param.Target),
|
||||||
zap.Any("path", proxyPath), zap.Any("target", fmt.Sprintf("%s%s", u, targetPath)))
|
zap.Any("path", proxyPath), zap.Any("target", fmt.Sprintf("%s%s", u, targetPath)))
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package quickjs
|
package quickjs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/buke/quickjs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DebugData struct {
|
type DebugData struct {
|
||||||
@@ -37,3 +40,96 @@ func (w *debugResponseWriter) Write(data []byte) (int, error) {
|
|||||||
func (w *debugResponseWriter) WriteHeader(statusCode int) {
|
func (w *debugResponseWriter) WriteHeader(statusCode int) {
|
||||||
w.status = statusCode
|
w.status = statusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renderDebugPage 渲染调试页面
|
||||||
|
func renderDebugPage(writer http.ResponseWriter, outputBuffer, logBuffer *strings.Builder, jsError error) error {
|
||||||
|
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
|
html := `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>QuickJS Debug</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
||||||
|
.section { margin-bottom: 30px; border: 1px solid #ddd; border-radius: 5px; }
|
||||||
|
.section-header { background: #f5f5f5; padding: 10px 15px; border-bottom: 1px solid #ddd; font-weight: bold; }
|
||||||
|
.section-content { padding: 15px; background: white; }
|
||||||
|
.output { white-space: pre-wrap; font-family: monospace; }
|
||||||
|
.log { white-space: pre-wrap; font-family: monospace; background: #f8f8f8; }
|
||||||
|
.error { color: #d00; background: #fee; padding: 10px; border-radius: 3px; }
|
||||||
|
.success { color: #080; background: #efe; padding: 10px; border-radius: 3px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>QuickJS Debug Output</h1>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-header">执行结果</div>
|
||||||
|
<div class="section-content">
|
||||||
|
<div class="output">`
|
||||||
|
|
||||||
|
// 转义输出内容
|
||||||
|
output := outputBuffer.String()
|
||||||
|
if output == "" {
|
||||||
|
output = "(无输出)"
|
||||||
|
}
|
||||||
|
html += htmlEscape(output)
|
||||||
|
|
||||||
|
html += `</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-header">控制台日志</div>
|
||||||
|
<div class="section-content">
|
||||||
|
<div class="log">`
|
||||||
|
|
||||||
|
// 转义日志内容
|
||||||
|
logs := logBuffer.String()
|
||||||
|
if logs == "" {
|
||||||
|
logs = "(无日志)"
|
||||||
|
}
|
||||||
|
html += htmlEscape(logs)
|
||||||
|
|
||||||
|
html += `</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-header">执行状态</div>
|
||||||
|
<div class="section-content">`
|
||||||
|
|
||||||
|
if jsError != nil {
|
||||||
|
html += `<div class="error"><pre><code><strong>Message:</strong></br>`
|
||||||
|
var q *quickjs.Error
|
||||||
|
if errors.As(jsError, &q) {
|
||||||
|
html += q.Message + "</br></br>"
|
||||||
|
html += `<strong>Stack:</strong></br>` + q.Stack
|
||||||
|
} else {
|
||||||
|
html += jsError.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</pre></code></div>`
|
||||||
|
} else {
|
||||||
|
html += `<div class="success">执行成功</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
||||||
|
_, err := writer.Write([]byte(html))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// htmlEscape 转义 HTML 特殊字符
|
||||||
|
func htmlEscape(s string) string {
|
||||||
|
return strings.NewReplacer(
|
||||||
|
"&", "&",
|
||||||
|
"<", "<",
|
||||||
|
">", ">",
|
||||||
|
`"`, """,
|
||||||
|
"'", "'",
|
||||||
|
).Replace(s)
|
||||||
|
}
|
||||||
|
|||||||
2
pkg/filters/quickjs/inject.js
Normal file
2
pkg/filters/quickjs/inject.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const req = request;
|
||||||
|
const resp = response;
|
||||||
@@ -1,19 +1,21 @@
|
|||||||
package quickjs
|
package quickjs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"bytes"
|
||||||
"fmt"
|
_ "embed"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/buke/quickjs-go"
|
"github.com/buke/quickjs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed inject.js
|
||||||
|
var inject string
|
||||||
|
|
||||||
var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) {
|
var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) {
|
||||||
var param struct {
|
var param struct {
|
||||||
Exec string `json:"exec"`
|
Exec string `json:"exec"`
|
||||||
@@ -25,8 +27,8 @@ var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core
|
|||||||
if param.Exec == "" {
|
if param.Exec == "" {
|
||||||
return nil, errors.Errorf("no exec specified")
|
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 {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
js, err := metadata.ReadString(ctx, param.Exec)
|
js, err := ctx.ReadString(ctx, param.Exec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -34,10 +36,29 @@ var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core
|
|||||||
rt := quickjs.NewRuntime()
|
rt := quickjs.NewRuntime()
|
||||||
rt.SetExecuteTimeout(5)
|
rt.SetExecuteTimeout(5)
|
||||||
defer rt.Close()
|
defer rt.Close()
|
||||||
|
|
||||||
jsCtx := rt.NewContext()
|
jsCtx := rt.NewContext()
|
||||||
defer jsCtx.Close()
|
defer jsCtx.Close()
|
||||||
|
cacheKey := "qjs/" + param.Exec
|
||||||
|
var bytecode []byte
|
||||||
|
cacheData, err := ctx.Cache.Get(ctx, cacheKey)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bytecode, err = jsCtx.Compile(js,
|
||||||
|
quickjs.EvalFlagCompileOnly(true),
|
||||||
|
quickjs.EvalFileName(param.Exec)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ctx.Cache.Put(ctx, cacheKey, map[string]string{}, bytes.NewBuffer(bytecode)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer cacheData.Close()
|
||||||
|
if bytecode, err = io.ReadAll(cacheData); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
// 在 debug 模式下,我们需要拦截输出
|
// 在 debug 模式下,我们需要拦截输出
|
||||||
var (
|
var (
|
||||||
outputBuffer strings.Builder
|
outputBuffer strings.Builder
|
||||||
@@ -46,8 +67,7 @@ var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core
|
|||||||
)
|
)
|
||||||
|
|
||||||
global := jsCtx.Globals()
|
global := jsCtx.Globals()
|
||||||
global.Set("request", createRequestObject(jsCtx, request, metadata))
|
global.Set("request", createRequestObject(jsCtx, request, ctx))
|
||||||
|
|
||||||
// 根据是否 debug 模式创建不同的 response 对象
|
// 根据是否 debug 模式创建不同的 response 对象
|
||||||
if param.Debug {
|
if param.Debug {
|
||||||
// debug 模式下使用虚假的 writer 来捕获输出
|
// debug 模式下使用虚假的 writer 来捕获输出
|
||||||
@@ -60,8 +80,8 @@ var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core
|
|||||||
global.Set("response", createResponseObject(jsCtx, writer, request))
|
global.Set("response", createResponseObject(jsCtx, writer, request))
|
||||||
global.Set("console", createConsoleObject(jsCtx, nil))
|
global.Set("console", createConsoleObject(jsCtx, nil))
|
||||||
}
|
}
|
||||||
|
jsCtx.Eval(inject)
|
||||||
ret := jsCtx.Eval(js)
|
ret := jsCtx.EvalBytecode(bytecode)
|
||||||
defer ret.Free()
|
defer ret.Free()
|
||||||
jsCtx.Loop()
|
jsCtx.Loop()
|
||||||
|
|
||||||
@@ -78,346 +98,3 @@ var FilterInstQuickJS core.FilterInstance = func(config core.FilterParams) (core
|
|||||||
return jsError
|
return jsError
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderDebugPage 渲染调试页面
|
|
||||||
func renderDebugPage(writer http.ResponseWriter, outputBuffer, logBuffer *strings.Builder, jsError error) error {
|
|
||||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
|
|
||||||
html := `<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>QuickJS Debug</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
|
|
||||||
.section { margin-bottom: 30px; border: 1px solid #ddd; border-radius: 5px; }
|
|
||||||
.section-header { background: #f5f5f5; padding: 10px 15px; border-bottom: 1px solid #ddd; font-weight: bold; }
|
|
||||||
.section-content { padding: 15px; background: white; }
|
|
||||||
.output { white-space: pre-wrap; font-family: monospace; }
|
|
||||||
.log { white-space: pre-wrap; font-family: monospace; background: #f8f8f8; }
|
|
||||||
.error { color: #d00; background: #fee; padding: 10px; border-radius: 3px; }
|
|
||||||
.success { color: #080; background: #efe; padding: 10px; border-radius: 3px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>QuickJS Debug Output</h1>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<div class="section-header">执行结果</div>
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="output">`
|
|
||||||
|
|
||||||
// 转义输出内容
|
|
||||||
output := outputBuffer.String()
|
|
||||||
if output == "" {
|
|
||||||
output = "(无输出)"
|
|
||||||
}
|
|
||||||
html += htmlEscape(output)
|
|
||||||
|
|
||||||
html += `</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<div class="section-header">控制台日志</div>
|
|
||||||
<div class="section-content">
|
|
||||||
<div class="log">`
|
|
||||||
|
|
||||||
// 转义日志内容
|
|
||||||
logs := logBuffer.String()
|
|
||||||
if logs == "" {
|
|
||||||
logs = "(无日志)"
|
|
||||||
}
|
|
||||||
html += htmlEscape(logs)
|
|
||||||
|
|
||||||
html += `</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<div class="section-header">执行状态</div>
|
|
||||||
<div class="section-content">`
|
|
||||||
|
|
||||||
if jsError != nil {
|
|
||||||
html += `<div class="error"><strong>错误:</strong> ` + htmlEscape(jsError.Error()) + `</div>`
|
|
||||||
} else {
|
|
||||||
html += `<div class="success">执行成功</div>`
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>`
|
|
||||||
|
|
||||||
_, err := writer.Write([]byte(html))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// htmlEscape 转义 HTML 特殊字符
|
|
||||||
func htmlEscape(s string) string {
|
|
||||||
return strings.NewReplacer(
|
|
||||||
"&", "&",
|
|
||||||
"<", "<",
|
|
||||||
">", ">",
|
|
||||||
`"`, """,
|
|
||||||
"'", "'",
|
|
||||||
).Replace(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createRequestObject 创建表示 HTTP 请求的 JavaScript 对象
|
|
||||||
func createRequestObject(ctx *quickjs.Context, req *http.Request, metadata *core.PageContent) *quickjs.Value {
|
|
||||||
obj := ctx.NewObject()
|
|
||||||
// 基本属性
|
|
||||||
obj.Set("method", ctx.NewString(req.Method))
|
|
||||||
url := *req.URL
|
|
||||||
url.Path = metadata.Path
|
|
||||||
obj.Set("url", ctx.NewString(url.String()))
|
|
||||||
obj.Set("path", ctx.NewString(url.Path))
|
|
||||||
obj.Set("rawPath", ctx.NewString(req.URL.Path))
|
|
||||||
obj.Set("query", ctx.NewString(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 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]
|
|
||||||
names, err := headersObj.PropertyNames()
|
|
||||||
if err != nil {
|
|
||||||
return ctx.NewError(err)
|
|
||||||
}
|
|
||||||
for _, key := range names {
|
|
||||||
writer.Header().Set(key, headersObj.Get(key).String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.ToBool()
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpOnly := options.Get("httpOnly"); !httpOnly.IsNull() {
|
|
||||||
cookie.HttpOnly = httpOnly.ToBool()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http.SetCookie(writer, cookie)
|
|
||||||
}
|
|
||||||
return ctx.NewNull()
|
|
||||||
}))
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
// createConsoleObject 创建 console 对象用于日志输出
|
|
||||||
func createConsoleObject(ctx *quickjs.Context, buf *strings.Builder) *quickjs.Value {
|
|
||||||
console := ctx.NewObject()
|
|
||||||
|
|
||||||
logFunc := func(level string, buffer *strings.Builder) 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())
|
|
||||||
}
|
|
||||||
message := fmt.Sprintf("[%s] %s", level, strings.Join(messages, " "))
|
|
||||||
|
|
||||||
// 总是输出到系统日志
|
|
||||||
log.Print(message)
|
|
||||||
|
|
||||||
// 如果有缓冲区,也写入缓冲区
|
|
||||||
if buffer != nil {
|
|
||||||
buffer.WriteString(message + "\n")
|
|
||||||
}
|
|
||||||
return ctx.NewNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.Set("log", ctx.NewFunction(logFunc("INFO", buf)))
|
|
||||||
console.Set("info", ctx.NewFunction(logFunc("INFO", buf)))
|
|
||||||
console.Set("warn", ctx.NewFunction(logFunc("WARN", buf)))
|
|
||||||
console.Set("error", ctx.NewFunction(logFunc("ERROR", buf)))
|
|
||||||
console.Set("debug", ctx.NewFunction(logFunc("DEBUG", buf)))
|
|
||||||
return console
|
|
||||||
}
|
|
||||||
|
|||||||
40
pkg/filters/quickjs/var_console.go
Normal file
40
pkg/filters/quickjs/var_console.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package quickjs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/buke/quickjs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createConsoleObject 创建 console 对象用于日志输出
|
||||||
|
func createConsoleObject(ctx *quickjs.Context, buf *strings.Builder) *quickjs.Value {
|
||||||
|
console := ctx.NewObject()
|
||||||
|
|
||||||
|
logFunc := func(level string, buffer *strings.Builder) 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())
|
||||||
|
}
|
||||||
|
message := fmt.Sprintf("[%s] %s", level, strings.Join(messages, " "))
|
||||||
|
|
||||||
|
// 总是输出到系统日志
|
||||||
|
log.Print(message)
|
||||||
|
|
||||||
|
// 如果有缓冲区,也写入缓冲区
|
||||||
|
if buffer != nil {
|
||||||
|
buffer.WriteString(message + "\n")
|
||||||
|
}
|
||||||
|
return ctx.NewNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.Set("log", ctx.NewFunction(logFunc("INFO", buf)))
|
||||||
|
console.Set("info", ctx.NewFunction(logFunc("INFO", buf)))
|
||||||
|
console.Set("warn", ctx.NewFunction(logFunc("WARN", buf)))
|
||||||
|
console.Set("error", ctx.NewFunction(logFunc("ERROR", buf)))
|
||||||
|
console.Set("debug", ctx.NewFunction(logFunc("DEBUG", buf)))
|
||||||
|
return console
|
||||||
|
}
|
||||||
1
pkg/filters/quickjs/var_kv.go
Normal file
1
pkg/filters/quickjs/var_kv.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package quickjs
|
||||||
64
pkg/filters/quickjs/var_request.go
Normal file
64
pkg/filters/quickjs/var_request.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package quickjs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/buke/quickjs-go"
|
||||||
|
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// createRequestObject 创建表示 HTTP 请求的 JavaScript 对象
|
||||||
|
func createRequestObject(ctx *quickjs.Context, req *http.Request, filterCtx core.FilterContext) *quickjs.Value {
|
||||||
|
obj := ctx.NewObject()
|
||||||
|
// 基本属性
|
||||||
|
obj.Set("method", ctx.NewString(req.Method))
|
||||||
|
url := *req.URL
|
||||||
|
url.Path = filterCtx.Path
|
||||||
|
obj.Set("url", ctx.NewString(url.String()))
|
||||||
|
obj.Set("path", ctx.NewString(url.Path))
|
||||||
|
obj.Set("rawPath", ctx.NewString(req.URL.Path))
|
||||||
|
obj.Set("query", ctx.NewString(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 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
|
||||||
|
}
|
||||||
181
pkg/filters/quickjs/var_response.go
Normal file
181
pkg/filters/quickjs/var_response.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package quickjs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/buke/quickjs-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
names, err := headersObj.PropertyNames()
|
||||||
|
if err != nil {
|
||||||
|
return ctx.NewError(err)
|
||||||
|
}
|
||||||
|
for _, key := range names {
|
||||||
|
writer.Header().Set(key, headersObj.Get(key).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.ToBool()
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpOnly := options.Get("httpOnly"); !httpOnly.IsNull() {
|
||||||
|
cookie.HttpOnly = httpOnly.ToBool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(writer, cookie)
|
||||||
|
}
|
||||||
|
return ctx.NewNull()
|
||||||
|
}))
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package filters
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -33,12 +32,12 @@ var FilterInstRedirect core.FilterInstance = func(config core.FilterParams) (cor
|
|||||||
if param.Code < 300 || param.Code > 399 {
|
if param.Code < 300 || param.Code > 399 {
|
||||||
return nil, fmt.Errorf("invalid code: %d", param.Code)
|
return nil, fmt.Errorf("invalid code: %d", param.Code)
|
||||||
}
|
}
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
domain := portExp.ReplaceAllString(strings.ToLower(request.Host), "")
|
domain := portExp.ReplaceAllString(strings.ToLower(request.Host), "")
|
||||||
if len(param.Targets) > 0 && !slices.Contains(metadata.Alias, domain) {
|
if len(param.Targets) > 0 && !slices.Contains(ctx.Alias, domain) {
|
||||||
// 重定向到配置的地址
|
// 重定向到配置的地址
|
||||||
zap.L().Debug("redirect", zap.Any("src", request.Host), zap.Any("dst", param.Targets[0]))
|
zap.L().Debug("redirect", zap.Any("src", request.Host), zap.Any("dst", param.Targets[0]))
|
||||||
path := metadata.Path
|
path := ctx.Path
|
||||||
if strings.HasSuffix(path, "/index.html") || path == "index.html" {
|
if strings.HasSuffix(path, "/index.html") || path == "index.html" {
|
||||||
path = strings.TrimSuffix(path, "index.html")
|
path = strings.TrimSuffix(path, "index.html")
|
||||||
}
|
}
|
||||||
@@ -51,6 +50,6 @@ var FilterInstRedirect core.FilterInstance = func(config core.FilterParams) (cor
|
|||||||
http.Redirect(writer, request, target.String(), param.Code)
|
http.Redirect(writer, request, target.String(), param.Code)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return next(ctx, writer, request, metadata)
|
return next(ctx, writer, request)
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package filters
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -18,8 +17,8 @@ var FilterInstTemplate core.FilterInstance = func(config core.FilterParams) (cor
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
param.Prefix = strings.Trim(param.Prefix, "/") + "/"
|
param.Prefix = strings.Trim(param.Prefix, "/") + "/"
|
||||||
return func(ctx context.Context, writer http.ResponseWriter, request *http.Request, metadata *core.PageContent, next core.NextCall) error {
|
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||||
data, err := metadata.ReadString(ctx, param.Prefix+metadata.Path)
|
data, err := ctx.ReadString(ctx, param.Prefix+ctx.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -29,7 +28,7 @@ var FilterInstTemplate core.FilterInstance = func(config core.FilterParams) (cor
|
|||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
parse, err := utils.NewTemplate().Funcs(map[string]any{
|
parse, err := utils.NewTemplate().Funcs(map[string]any{
|
||||||
"load": func(path string) (any, error) {
|
"load": func(path string) (any, error) {
|
||||||
return metadata.ReadString(ctx, param.Prefix+path)
|
return ctx.ReadString(ctx, param.Prefix+path)
|
||||||
},
|
},
|
||||||
}).Parse(data)
|
}).Parse(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import (
|
|||||||
|
|
||||||
type ProviderCache struct {
|
type ProviderCache struct {
|
||||||
parent core.Backend
|
parent core.Backend
|
||||||
cacheRepo *tools.Cache[map[string]string]
|
cacheRepo *tools.KVCache[map[string]string]
|
||||||
cacheBranch *tools.Cache[map[string]*core.BranchInfo]
|
cacheBranch *tools.KVCache[map[string]*core.BranchInfo]
|
||||||
|
|
||||||
cacheBlob cache.Cache
|
cacheBlob cache.Cache
|
||||||
cacheBlobLimit uint64
|
cacheBlobLimit uint64
|
||||||
@@ -88,10 +88,10 @@ func (c *ProviderCache) Branches(ctx context.Context, owner, repo string) (map[s
|
|||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProviderCache) Open(ctx context.Context, client *http.Client, owner, repo, commit, path string, headers http.Header) (*http.Response, error) {
|
func (c *ProviderCache) Open(ctx context.Context, owner, repo, commit, path string, headers http.Header) (*http.Response, error) {
|
||||||
if headers != nil && headers.Get("Range") != "" {
|
if headers != nil && headers.Get("Range") != "" {
|
||||||
// ignore custom header
|
// ignore custom header
|
||||||
return c.parent.Open(ctx, client, owner, repo, commit, path, headers)
|
return c.parent.Open(ctx, owner, repo, commit, path, headers)
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf("%s/%s/%s/%s", owner, repo, commit, path)
|
key := fmt.Sprintf("%s/%s/%s/%s", owner, repo, commit, path)
|
||||||
lastCache, err := c.cacheBlob.Get(ctx, key)
|
lastCache, err := c.cacheBlob.Get(ctx, key)
|
||||||
@@ -125,7 +125,7 @@ func (c *ProviderCache) Open(ctx context.Context, client *http.Client, owner, re
|
|||||||
Header: respHeader,
|
Header: respHeader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
open, err := c.parent.Open(ctx, client, owner, repo, commit, path, http.Header{})
|
open, err := c.parent.Open(ctx, owner, repo, commit, path, http.Header{})
|
||||||
if err != nil || open == nil {
|
if err != nil || open == nil {
|
||||||
if open != nil {
|
if open != nil {
|
||||||
_ = open.Body.Close()
|
_ = open.Body.Close()
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ type ProviderGitea struct {
|
|||||||
BaseURL string
|
BaseURL string
|
||||||
Token string
|
Token string
|
||||||
|
|
||||||
gitea *gitea.Client
|
gitea *gitea.Client
|
||||||
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGitea(url, token string) (*ProviderGitea, error) {
|
func NewGitea(httpClient *http.Client, url, token string) (*ProviderGitea, error) {
|
||||||
client, err := gitea.NewClient(url, gitea.SetGiteaVersion(""), gitea.SetToken(token))
|
client, err := gitea.NewClient(url, gitea.SetGiteaVersion(""), gitea.SetToken(token))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -29,6 +30,7 @@ func NewGitea(url, token string) (*ProviderGitea, error) {
|
|||||||
BaseURL: url,
|
BaseURL: url,
|
||||||
Token: token,
|
Token: token,
|
||||||
gitea: client,
|
gitea: client,
|
||||||
|
client: httpClient,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +105,7 @@ func (g *ProviderGitea) Branches(_ context.Context, owner, repo string) (map[str
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *ProviderGitea) Open(ctx context.Context, client *http.Client, owner, repo, commit, path string, headers http.Header) (*http.Response, error) {
|
func (g *ProviderGitea) Open(ctx context.Context, owner, repo, commit, path string, headers http.Header) (*http.Response, error) {
|
||||||
if headers == nil {
|
if headers == nil {
|
||||||
headers = make(http.Header)
|
headers = make(http.Header)
|
||||||
}
|
}
|
||||||
@@ -122,7 +124,7 @@ func (g *ProviderGitea) Open(ctx context.Context, client *http.Client, owner, re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
req.Header.Add("Authorization", "token "+g.Token)
|
req.Header.Add("Authorization", "token "+g.Token)
|
||||||
return client.Do(req)
|
return g.client.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *ProviderGitea) Close() error {
|
func (g *ProviderGitea) Close() error {
|
||||||
|
|||||||
84
pkg/providers/local.go
Normal file
84
pkg/providers/local.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package providers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LocalProvider struct {
|
||||||
|
graph map[string][]string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLocalProvider(
|
||||||
|
graph map[string][]string,
|
||||||
|
path string,
|
||||||
|
) *LocalProvider {
|
||||||
|
return &LocalProvider{
|
||||||
|
graph: graph,
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalProvider) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalProvider) Repos(_ context.Context, owner string) (map[string]string, error) {
|
||||||
|
item, ok := l.graph[owner]
|
||||||
|
if !ok {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
result := make(map[string]string)
|
||||||
|
for _, s := range item {
|
||||||
|
result[s] = "gh-pages"
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalProvider) Branches(_ context.Context, owner, repo string) (map[string]*core.BranchInfo, error) {
|
||||||
|
item, ok := l.graph[owner]
|
||||||
|
if !ok {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
if !slices.Contains(item, repo) {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
return map[string]*core.BranchInfo{
|
||||||
|
"gh-pages": {
|
||||||
|
ID: "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc",
|
||||||
|
LastModified: time.Now(),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalProvider) Open(_ context.Context, _, _, _, path string, _ http.Header) (*http.Response, error) {
|
||||||
|
open, err := os.Open(filepath.Join(l.path, path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(err, os.ErrNotExist)
|
||||||
|
}
|
||||||
|
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)))
|
||||||
|
stat, _ := open.Stat()
|
||||||
|
recorder.Header().Add("Content-Length", strconv.FormatInt(stat.Size(), 10))
|
||||||
|
recorder.Header().Add("Last-Modified", stat.ModTime().Format(http.TimeFormat))
|
||||||
|
return recorder.Result(), nil
|
||||||
|
}
|
||||||
@@ -15,19 +15,22 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||||
"gopkg.d7z.net/gitea-pages/pkg/filters"
|
"gopkg.d7z.net/gitea-pages/pkg/filters"
|
||||||
|
"gopkg.d7z.net/middleware/cache"
|
||||||
"gopkg.d7z.net/middleware/kv"
|
"gopkg.d7z.net/middleware/kv"
|
||||||
|
"gopkg.d7z.net/middleware/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
var portExp = regexp.MustCompile(`:\d+$`)
|
var portExp = regexp.MustCompile(`:\d+$`)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
backend core.Backend
|
backend core.Backend
|
||||||
meta *core.PageDomain
|
meta *core.PageDomain
|
||||||
|
db kv.CursorPagedKV
|
||||||
errorHandler func(w http.ResponseWriter, r *http.Request, err error)
|
|
||||||
|
|
||||||
filterMgr map[string]core.FilterInstance
|
filterMgr map[string]core.FilterInstance
|
||||||
globCache *lru.Cache[string, glob.Glob]
|
globCache *lru.Cache[string, glob.Glob]
|
||||||
|
cacheBlob cache.Cache
|
||||||
|
|
||||||
|
errorHandler func(w http.ResponseWriter, r *http.Request, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPageServer(
|
func NewPageServer(
|
||||||
@@ -35,27 +38,26 @@ func NewPageServer(
|
|||||||
backend core.Backend,
|
backend core.Backend,
|
||||||
domain string,
|
domain string,
|
||||||
defaultBranch string,
|
defaultBranch string,
|
||||||
db kv.KV,
|
db kv.CursorPagedKV,
|
||||||
cache kv.KV,
|
cacheMeta kv.KV,
|
||||||
cacheTTL time.Duration,
|
cacheTTL time.Duration,
|
||||||
|
cacheBlob cache.Cache,
|
||||||
errorHandler func(w http.ResponseWriter, r *http.Request, err error),
|
errorHandler func(w http.ResponseWriter, r *http.Request, err error),
|
||||||
) *Server {
|
) *Server {
|
||||||
svcMeta := core.NewServerMeta(client, backend, domain, cache, cacheTTL)
|
svcMeta := core.NewServerMeta(client, backend, domain, cacheMeta, cacheTTL)
|
||||||
cfgDB := db.Child("config")
|
pageMeta := core.NewPageDomain(svcMeta, core.NewDomainAlias(db.Child("config").Child("alias")), domain, defaultBranch)
|
||||||
pageMeta := core.NewPageDomain(svcMeta,
|
globCache, err := lru.New[string, glob.Glob](256)
|
||||||
core.NewDomainAlias(cfgDB.Child("alias")),
|
|
||||||
cfgDB.Child("pages"),
|
|
||||||
domain, defaultBranch)
|
|
||||||
c, err := lru.New[string, glob.Glob](256)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return &Server{
|
return &Server{
|
||||||
backend: backend,
|
backend: backend,
|
||||||
meta: pageMeta,
|
meta: pageMeta,
|
||||||
globCache: c,
|
db: db,
|
||||||
|
globCache: globCache,
|
||||||
filterMgr: filters.DefaultFilters(),
|
filterMgr: filters.DefaultFilters(),
|
||||||
errorHandler: errorHandler,
|
errorHandler: errorHandler,
|
||||||
|
cacheBlob: cacheBlob,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +90,16 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterCtx := core.FilterContext{
|
||||||
|
PageContent: meta,
|
||||||
|
Context: request.Context(),
|
||||||
|
PageVFS: core.NewPageVFS(s.backend, meta.Owner, meta.Repo, meta.CommitID),
|
||||||
|
Cache: tools.NewTTLCache(s.cacheBlob.Child("filter").Child(meta.Owner).Child(meta.Repo).Child(meta.CommitID), time.Minute),
|
||||||
|
OrgDB: s.db.Child("org").Child(meta.Owner).(kv.CursorPagedKV),
|
||||||
|
RepoDB: s.db.Child("repo").Child(meta.Owner).Child(meta.Repo).(kv.CursorPagedKV),
|
||||||
|
}
|
||||||
|
|
||||||
zap.L().Debug("new request", zap.Any("request path", meta.Path))
|
zap.L().Debug("new request", zap.Any("request path", meta.Path))
|
||||||
|
|
||||||
if strings.HasSuffix(meta.Path, "/") || meta.Path == "" {
|
if strings.HasSuffix(meta.Path, "/") || meta.Path == "" {
|
||||||
@@ -133,6 +145,6 @@ func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error
|
|||||||
for i, filter := range activeFiltersCall {
|
for i, filter := range activeFiltersCall {
|
||||||
stack = core.NextCallWrapper(filter, stack, activeFilters[i])
|
stack = core.NextCallWrapper(filter, stack, activeFilters[i])
|
||||||
}
|
}
|
||||||
err = stack(ctx, writer, request, meta)
|
err = stack(filterCtx, writer, request)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (p *ProviderDummy) Branches(_ context.Context, owner, repo string) (map[str
|
|||||||
return branches, nil
|
return branches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProviderDummy) Open(_ context.Context, _ *http.Client, owner, repo, commit, path string, _ http.Header) (*http.Response, error) {
|
func (p *ProviderDummy) Open(_ context.Context, owner, repo, commit, path string, _ http.Header) (*http.Response, error) {
|
||||||
open, err := os.Open(filepath.Join(p.BaseDir, owner, repo, commit, path))
|
open, err := os.Open(filepath.Join(p.BaseDir, owner, repo, commit, path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(err, os.ErrNotExist)
|
return nil, errors.Join(err, os.ErrNotExist)
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.d7z.net/gitea-pages/pkg"
|
"gopkg.d7z.net/gitea-pages/pkg"
|
||||||
|
"gopkg.d7z.net/middleware/cache"
|
||||||
"gopkg.d7z.net/middleware/kv"
|
"gopkg.d7z.net/middleware/kv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,7 +36,10 @@ func NewTestServer(domain string) *TestServer {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
zap.S().Fatal(err)
|
zap.S().Fatal(err)
|
||||||
}
|
}
|
||||||
|
memoryCache, _ := cache.NewMemoryCache(cache.MemoryCacheConfig{
|
||||||
|
MaxCapacity: 256,
|
||||||
|
CleanupInt: time.Minute,
|
||||||
|
})
|
||||||
memoryKV, _ := kv.NewMemory("")
|
memoryKV, _ := kv.NewMemory("")
|
||||||
server := pkg.NewPageServer(
|
server := pkg.NewPageServer(
|
||||||
http.DefaultClient,
|
http.DefaultClient,
|
||||||
@@ -44,6 +49,7 @@ func NewTestServer(domain string) *TestServer {
|
|||||||
memoryKV,
|
memoryKV,
|
||||||
memoryKV.Child("cache"),
|
memoryKV.Child("cache"),
|
||||||
0,
|
0,
|
||||||
|
memoryCache,
|
||||||
func(w http.ResponseWriter, r *http.Request, err error) {
|
func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
http.Error(w, "page not found.", http.StatusNotFound)
|
http.Error(w, "page not found.", http.StatusNotFound)
|
||||||
|
|||||||
Reference in New Issue
Block a user