支持 await/async
This commit is contained in:
5
examples/event/.pages.yaml
Normal file
5
examples/event/.pages.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
routes:
|
||||
- path: "**"
|
||||
js:
|
||||
exec: "index.js"
|
||||
debug: true
|
||||
12
examples/event/index.html
Normal file
12
examples/event/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!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>JS 概念验证</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
18
examples/event/index.js
Normal file
18
examples/event/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
console.log('boot');
|
||||
(async () => {
|
||||
console.log(0);
|
||||
await sleep(1000);
|
||||
console.log(1000);
|
||||
await sleep(1000);
|
||||
console.log(2000);
|
||||
await sleep(1000);
|
||||
console.log(3000);
|
||||
await sleep(1000);
|
||||
console.log(4000);
|
||||
await sleep(1000);
|
||||
console.log(5000);
|
||||
})();
|
||||
console.log('boot end');
|
||||
@@ -72,7 +72,7 @@ func (p *PageDomain) returnMeta(ctx context.Context, owner, repo, branch string,
|
||||
result := &PageContent{}
|
||||
meta, err := p.GetMeta(ctx, owner, repo, branch)
|
||||
if err != nil {
|
||||
zap.L().Debug("查询错误", zap.Error(err))
|
||||
zap.L().Debug("repo does not exists", zap.Error(err), zap.Strings("meta", []string{owner, repo, branch}))
|
||||
if meta != nil {
|
||||
// 解析错误汇报
|
||||
return nil, errors.New(meta.ErrorMsg)
|
||||
@@ -85,7 +85,7 @@ func (p *PageDomain) returnMeta(ctx context.Context, owner, repo, branch string,
|
||||
result.Path = strings.Join(path, "/")
|
||||
|
||||
if err = p.alias.Bind(ctx, meta.Alias, result.Owner, result.Repo, branch); err != nil {
|
||||
zap.L().Warn("别名绑定失败", zap.Error(err))
|
||||
zap.L().Warn("alias binding error.", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
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">
|
||||
<style>
|
||||
.clear{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
@@ -70,7 +74,7 @@
|
||||
<div class="section">
|
||||
<div class="section-header">控制台日志</div>
|
||||
<div class="section-content">
|
||||
<div class="log">{{- range .Logs }}<p>{{ .Time.Format "2006/01/02 15:04:05" }} - {{ .Level }} : {{ .Message }}</p>{{- end }}</div>
|
||||
<div class="log">{{- range .Logs }}<p class="clear">{{ .Time.Format "2006/01/02 15:04:05" }} - {{ .Level }} : {{ .Message }}</p>{{- end }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,15 +2,16 @@ package goja
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
"github.com/dop251/goja_nodejs/eventloop"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"github.com/dop251/goja_nodejs/url"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||
)
|
||||
|
||||
@@ -23,40 +24,56 @@ var FilterInstGoJa core.FilterInstance = func(config core.FilterParams) (core.Fi
|
||||
return nil, err
|
||||
}
|
||||
if param.Exec == "" {
|
||||
return nil, errors.Errorf("no exec specified")
|
||||
return nil, errors.New("no exec specified")
|
||||
}
|
||||
return func(ctx core.FilterContext, w http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||
js, err := ctx.ReadString(ctx, param.Exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prg, err := goja.Compile("main.js", js, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
debug := NewDebug(param.Debug && request.URL.Query().Get("debug") == "true", request, w)
|
||||
registry := newRegistry(ctx)
|
||||
registry.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(debug))
|
||||
vm := goja.New()
|
||||
_ = registry.Enable(vm)
|
||||
console.Enable(vm)
|
||||
url.Enable(vm)
|
||||
if err = RequestInject(ctx, vm, request); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ResponseInject(vm, debug, request); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = KVInject(ctx, vm); err != nil {
|
||||
return err
|
||||
}
|
||||
coreCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancel()
|
||||
loop := eventloop.NewEventLoop(eventloop.WithRegistry(registry), eventloop.EnableConsole(true))
|
||||
stop := make(chan struct{}, 1)
|
||||
shutdown := make(chan struct{}, 1)
|
||||
timeout, cancelFunc := context.WithTimeout(ctx, 3*time.Second)
|
||||
defer cancelFunc()
|
||||
count := 0
|
||||
go func() {
|
||||
defer func() {
|
||||
shutdown <- struct{}{}
|
||||
close(shutdown)
|
||||
}()
|
||||
select {
|
||||
case <-coreCtx.Done():
|
||||
vm.Interrupt(coreCtx.Err())
|
||||
return
|
||||
case <-timeout.Done():
|
||||
case <-stop:
|
||||
}
|
||||
count = loop.Stop()
|
||||
}()
|
||||
_, err = vm.RunScript(param.Exec, js)
|
||||
cancel()
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
url.Enable(vm)
|
||||
if err = RequestInject(ctx, vm, request); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = ResponseInject(vm, debug, request); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = KVInject(ctx, vm); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = vm.RunProgram(prg)
|
||||
})
|
||||
stop <- struct{}{}
|
||||
close(stop)
|
||||
<-shutdown
|
||||
if count != 0 {
|
||||
err = errors.Join(context.DeadlineExceeded, err)
|
||||
}
|
||||
return debug.Flush(err)
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func kvResult(db kv.CursorPagedKV) func(ctx core.FilterContext, jsCtx *goja.Runt
|
||||
}
|
||||
return jsCtx.ToValue(get)
|
||||
},
|
||||
"set": func(key string, value string) {
|
||||
"set": func(key, value string) {
|
||||
err := db.Put(ctx, key, value, kv.TTLKeep)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -52,6 +52,20 @@ func kvResult(db kv.CursorPagedKV) func(ctx core.FilterContext, jsCtx *goja.Runt
|
||||
}
|
||||
return b
|
||||
},
|
||||
"putIfNotExists": func(key, value string) bool {
|
||||
exists, err := db.PutIfNotExists(ctx, key, value, kv.TTLKeep)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exists
|
||||
},
|
||||
"compareAndSwap": func(key, oldValue, newValue string) bool {
|
||||
swap, err := db.CompareAndSwap(ctx, key, oldValue, newValue)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return swap
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
func ResponseInject(jsCtx *goja.Runtime, writer http.ResponseWriter, req *http.Request) error {
|
||||
return jsCtx.GlobalObject().Set("response", map[string]any{
|
||||
// 响应头操作
|
||||
"setHeader": func(key string, value string) {
|
||||
"setHeader": func(key, value string) {
|
||||
writer.Header().Set(key, value)
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ func ResponseInject(jsCtx *goja.Runtime, writer http.ResponseWriter, req *http.R
|
||||
},
|
||||
|
||||
// 设置 cookie
|
||||
"setCookie": func(name string, value string, options ...map[string]interface{}) {
|
||||
"setCookie": func(name, value string, options ...map[string]interface{}) {
|
||||
cookie := &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/filters"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/utils"
|
||||
"gopkg.d7z.net/middleware/cache"
|
||||
"gopkg.d7z.net/middleware/kv"
|
||||
"gopkg.d7z.net/middleware/tools"
|
||||
@@ -61,29 +62,32 @@ func NewPageServer(
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, request *http.Request) {
|
||||
sessionID, _ := uuid.NewRandom()
|
||||
request.Header.Set("Session-ID", sessionID.String())
|
||||
//if s.staticFS != nil && strings.HasPrefix(request.URL.Path, staticPrefix) {
|
||||
// s.staticFS.ServeHTTP(writer, request)
|
||||
// return
|
||||
//}
|
||||
writer := utils.NewWrittenResponseWriter(w)
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
zap.L().Error("panic!", zap.Any("error", e), zap.Any("id", sessionID))
|
||||
if err, ok := e.(error); ok {
|
||||
s.errorHandler(writer, request, err)
|
||||
if !writer.IsWritten() {
|
||||
if err, ok := e.(error); ok {
|
||||
s.errorHandler(writer, request, err)
|
||||
} else {
|
||||
s.errorHandler(writer, request, errors.New("panic"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := s.Serve(writer, request)
|
||||
if err != nil {
|
||||
zap.L().Debug("错误请求", zap.Error(err), zap.Any("request", request.RequestURI), zap.Any("id", sessionID))
|
||||
s.errorHandler(writer, request, err)
|
||||
zap.L().Debug("bad request.", zap.Error(err), zap.Any("request", request.RequestURI), zap.Any("id", sessionID))
|
||||
if !writer.IsWritten() {
|
||||
s.errorHandler(writer, request, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Serve(writer http.ResponseWriter, request *http.Request) error {
|
||||
func (s *Server) Serve(writer *utils.WrittenResponseWriter, request *http.Request) error {
|
||||
ctx := request.Context()
|
||||
domain := portExp.ReplaceAllString(strings.ToLower(request.Host), "")
|
||||
meta, err := s.meta.ParseDomainMeta(ctx, domain, request.URL.Path, request.URL.Query().Get("branch"))
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package utils
|
||||
|
||||
func ClearDuplicates[T comparable](slice []T) []T {
|
||||
seen := make(map[T]bool)
|
||||
for _, val := range slice {
|
||||
seen[val] = true
|
||||
}
|
||||
|
||||
var result []T
|
||||
for key := range seen {
|
||||
result = append(result, key)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
33
pkg/utils/resp.go
Normal file
33
pkg/utils/resp.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package utils
|
||||
|
||||
import "net/http"
|
||||
|
||||
type WrittenResponseWriter struct {
|
||||
write bool
|
||||
base http.ResponseWriter
|
||||
}
|
||||
|
||||
func NewWrittenResponseWriter(base http.ResponseWriter) *WrittenResponseWriter {
|
||||
return &WrittenResponseWriter{
|
||||
base: base,
|
||||
write: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WrittenResponseWriter) Header() http.Header {
|
||||
return w.base.Header()
|
||||
}
|
||||
|
||||
func (w *WrittenResponseWriter) Write(b []byte) (int, error) {
|
||||
w.write = true
|
||||
return w.base.Write(b)
|
||||
}
|
||||
|
||||
func (w *WrittenResponseWriter) WriteHeader(statusCode int) {
|
||||
w.write = true
|
||||
w.base.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *WrittenResponseWriter) IsWritten() bool {
|
||||
return w.write
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package tests
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -62,8 +61,34 @@ routes:
|
||||
assert.Equal(t, "POST /api/v1/fetch", string(data))
|
||||
}
|
||||
|
||||
func Test_GoJa_Async(t *testing.T) {
|
||||
server := core.NewDefaultTestServer()
|
||||
defer server.Close()
|
||||
server.AddFile("org1/repo1/gh-pages/index.html", "hello world")
|
||||
server.AddFile("org1/repo1/gh-pages/index.js", `
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
(async()=>{
|
||||
await sleep(1000)
|
||||
response.write('abc')
|
||||
})()
|
||||
|
||||
`)
|
||||
server.AddFile("org1/repo1/gh-pages/.pages.yaml", `
|
||||
routes:
|
||||
- path: "**"
|
||||
js:
|
||||
exec: "index.js"
|
||||
`)
|
||||
|
||||
data, _, err := server.OpenFile("https://org1.example.com/repo1/api/v1/fetch")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "abc", string(data))
|
||||
}
|
||||
|
||||
func Benchmark_GoJa_Request(b *testing.B) {
|
||||
_ = os.Setenv("BM", "1")
|
||||
b.Setenv("BM", "1")
|
||||
server := core.NewDefaultTestServer()
|
||||
defer server.Close()
|
||||
server.AddFile("org1/repo1/gh-pages/index.html", "hello world")
|
||||
|
||||
Reference in New Issue
Block a user