切换为 goja
This commit is contained in:
16
README.md
16
README.md
@@ -4,9 +4,11 @@
|
||||
|
||||
**This project is part of Dragon's Zone HomeLab**
|
||||
|
||||
This project focuses on functional implementation and does not consider any performance optimizations or large-scale deployment scenarios. Any issues arising from this are not related to the project.
|
||||
This project focuses on functional implementation and does not consider any performance optimizations or large-scale
|
||||
deployment scenarios. Any issues arising from this are not related to the project.
|
||||
|
||||
**Note**: The project recently added custom renderers and reverse proxy functionality, which may lead to serious security and performance issues. If not needed, it can be turned off in the settings.
|
||||
**Note**: The project recently added custom renderers and reverse proxy functionality, which may lead to serious
|
||||
security and performance issues. If not needed, it can be turned off in the settings.
|
||||
|
||||
## Get Started
|
||||
|
||||
@@ -29,15 +31,13 @@ For specific configurations, check [`config.yaml`](./config.yaml).
|
||||
Create `.pages.yaml` in the project's `gh-pages` branch and fill in the following content:
|
||||
|
||||
```yaml
|
||||
v-route: true # Virtual routing
|
||||
alias: # CNAME
|
||||
- "example.com"
|
||||
- "example2.com"
|
||||
templates: # Renderer
|
||||
gotemplate: '**/*.tmpl,**/index.html'
|
||||
proxy:
|
||||
/api: https://github.com/api
|
||||
ignore: .git/**,.pages.yaml
|
||||
routes:
|
||||
- path: "**"
|
||||
js:
|
||||
exec: index.js
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
10
README_zh.md
10
README_zh.md
@@ -29,15 +29,13 @@ make gitea-pages
|
||||
在项目的 `gh-pages` 分支创建 `.pages.yaml`,填入如下内容
|
||||
|
||||
```yaml
|
||||
v-route: true # 虚拟路由
|
||||
alias: # CNAME
|
||||
- "example.com"
|
||||
- "example2.com"
|
||||
templates: # 渲染器
|
||||
gotemplate: '**/*.tmpl,**/index.html'
|
||||
proxy:
|
||||
/api: https://github.com/api
|
||||
ignore: .git/**,.pages.yaml
|
||||
routes:
|
||||
- path: "**"
|
||||
js:
|
||||
exec: index.js
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<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>
|
||||
<title>js 验证</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Hello World</p>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
routes:
|
||||
- path: "api/dev/**"
|
||||
qjs:
|
||||
js:
|
||||
exec: "index.js"
|
||||
debug: true
|
||||
- path: "api/prod/**"
|
||||
qjs:
|
||||
js:
|
||||
exec: "json.js"
|
||||
@@ -5,7 +5,7 @@
|
||||
<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>Quickjs 概念验证</title>
|
||||
<title>JS 概念验证</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
response.write("hello world")
|
||||
console.log("hello world")
|
||||
console.log(req.methaaod)
|
||||
function testError(){
|
||||
throw Error("Method not implemented")
|
||||
}
|
||||
response.setHeader("content-type", "application/json")
|
||||
testError()
|
||||
10
examples/kv/.pages.yaml
Normal file
10
examples/kv/.pages.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
routes:
|
||||
- path: "get"
|
||||
js:
|
||||
exec: "index.js"
|
||||
debug: true
|
||||
|
||||
- path: "put"
|
||||
js:
|
||||
exec: "index.js"
|
||||
debug: true
|
||||
14
examples/kv/index.html
Normal file
14
examples/kv/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!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>
|
||||
<a href="/get">查询数据</a>
|
||||
<a href="/put">更新数据</a>
|
||||
</body>
|
||||
</html>
|
||||
10
examples/kv/index.js
Normal file
10
examples/kv/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
var db = kv.repo("self");
|
||||
if(request.path == "put"){
|
||||
data = db.get('key')
|
||||
if(data == undefined){
|
||||
db.set('key','0')
|
||||
}else {
|
||||
db.set('key',(parseInt(data)+1).toString())
|
||||
}
|
||||
}
|
||||
response.write("当前存储的数值为 " + db.get('key'))
|
||||
28
go.mod
28
go.mod
@@ -5,9 +5,13 @@ go 1.25.3
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.22.1
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
|
||||
github.com/buke/quickjs-go v0.6.6
|
||||
github.com/dop251/goja v0.0.0-20251103141225-af2ceb9156d7
|
||||
github.com/dop251/goja_nodejs v0.0.0-20251015164255-5e94316bedaf
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/zap v1.27.0
|
||||
@@ -17,16 +21,13 @@ require (
|
||||
|
||||
require (
|
||||
github.com/42wim/httpsig v1.2.3 // indirect
|
||||
github.com/buke/quickjs-go v0.6.6 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/dop251/goja v0.0.0-20251103141225-af2ceb9156d7 // indirect
|
||||
github.com/dop251/goja_nodejs v0.0.0-20251015164255-5e94316bedaf // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
@@ -34,10 +35,9 @@ require (
|
||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/klauspost/compress v1.18.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
@@ -49,16 +49,16 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.5.0 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.6 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.6 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/net v0.46.0 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
|
||||
google.golang.org/grpc v1.76.0 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
|
||||
21
go.sum
21
go.sum
@@ -21,6 +21,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dop251/goja v0.0.0-20251103141225-af2ceb9156d7 h1:jxmXU5V9tXxJnydU5v/m9SG8TRUa/Z7IXODBpMs/P+U=
|
||||
github.com/dop251/goja v0.0.0-20251103141225-af2ceb9156d7/go.mod h1:MxLav0peU43GgvwVgNbLAj1s/bSGboKkhuULvq/7hx4=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20251015164255-5e94316bedaf h1:gbmvliZnCut4NjaPSNOQlfqBoZ9C5Dpf72mHMMYhgVE=
|
||||
@@ -55,6 +57,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8 h1:3DsUAV+VNEQa2CUVLxCY3f87278uWfIDhJnbdvDjvmE=
|
||||
github.com/google/pprof v0.0.0-20251114195745-4902fdda35c8/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
@@ -114,10 +118,16 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
|
||||
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
|
||||
go.etcd.io/etcd/api/v3 v3.6.6 h1:mcaMp3+7JawWv69p6QShYWS8cIWUOl32bFLb6qf8pOQ=
|
||||
go.etcd.io/etcd/api/v3 v3.6.6/go.mod h1:f/om26iXl2wSkcTA1zGQv8reJRSLVdoEBsi4JdfMrx4=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6 h1:uoqgzSOv2H9KlIF5O1Lsd8sW+eMLuV6wzE3q5GJGQNs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.6/go.mod h1:YngfUVmvsvOJ2rRgStIyHsKtOt9SZI2aBJrZiWJhCbI=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
|
||||
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6 h1:G5z1wMf5B9SNexoxOHUGBaULurOZPIgGPsW6CN492ec=
|
||||
go.etcd.io/etcd/client/v3 v3.6.6/go.mod h1:36Qv6baQ07znPR3+n7t+Rk5VHEzVYPvFfGmfF4wBHV8=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
@@ -142,6 +152,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -151,6 +163,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -163,10 +177,13 @@ golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@@ -181,10 +198,14 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930 h1:8BWFtrvJRbplrKV5VHlIm4YM726eeBPPAL2QDNWhRrU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251110190251-83f479183930/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930 h1:tK4fkUnnRhig9TsTp4otV1FxwBFYgbKUq1RY0V6KZ4U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251110190251-83f479183930/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
|
||||
@@ -2,7 +2,7 @@ package filters
|
||||
|
||||
import (
|
||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/filters/quickjs"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/filters/goja"
|
||||
)
|
||||
|
||||
func DefaultFilters() map[string]core.FilterInstance {
|
||||
@@ -14,6 +14,6 @@ func DefaultFilters() map[string]core.FilterInstance {
|
||||
"_404_": FilterInstDefaultNotFound,
|
||||
"failback": FilterInstFailback,
|
||||
"template": FilterInstTemplate,
|
||||
"qjs": quickjs.FilterInstQuickJS,
|
||||
"js": goja.FilterInstGoJa,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ var FilterInstDefaultNotFound core.FilterInstance = func(config core.FilterParam
|
||||
}
|
||||
writer.WriteHeader(http.StatusNotFound)
|
||||
_, _ = io.Copy(writer, open.Body)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}, nil
|
||||
}
|
||||
|
||||
95
pkg/filters/goja/debug.tmpl
Normal file
95
pkg/filters/goja/debug.tmpl
Normal file
@@ -0,0 +1,95 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<title>调试控制台</title>
|
||||
<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">
|
||||
<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>Debug Output</h1>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">执行结果</div>
|
||||
<div class="section-content">
|
||||
<pre class="output"><code>{{ .Output }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">响应头</div>
|
||||
<div class="section-content">
|
||||
<div class="log">{{- range $key, $value := .Headers }}<p>{{ $key }} = {{ $value }}</p>{{- end }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">执行状态</div>
|
||||
<div class="section-content">
|
||||
{{- if .Error }}
|
||||
<div class="error">{{ .Error }}</div>
|
||||
{{- else }}
|
||||
<div class="success">执行成功</div>
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +1,20 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"github.com/dop251/goja_nodejs/url"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||
)
|
||||
|
||||
var FilterInstGoja core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) {
|
||||
var FilterInstGoJa core.FilterInstance = func(config core.FilterParams) (core.FilterCall, error) {
|
||||
var param struct {
|
||||
Exec string `json:"exec"`
|
||||
Debug bool `json:"debug"`
|
||||
@@ -22,18 +25,39 @@ var FilterInstGoja core.FilterInstance = func(config core.FilterParams) (core.Fi
|
||||
if param.Exec == "" {
|
||||
return nil, errors.Errorf("no exec specified")
|
||||
}
|
||||
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||
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
|
||||
}
|
||||
newRegistry(ctx)
|
||||
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)
|
||||
vm.GlobalObject().Set("")
|
||||
return nil
|
||||
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()
|
||||
go func() {
|
||||
select {
|
||||
case <-coreCtx.Done():
|
||||
vm.Interrupt(coreCtx.Err())
|
||||
return
|
||||
}
|
||||
}()
|
||||
_, err = vm.RunScript(param.Exec, js)
|
||||
cancel()
|
||||
return debug.Flush(err)
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
118
pkg/filters/goja/var_debug.go
Normal file
118
pkg/filters/goja/var_debug.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed debug.tmpl
|
||||
var errorPageStr string
|
||||
|
||||
var errorPage = template.Must(template.New("error.tmpl").Parse(errorPageStr))
|
||||
|
||||
type DebugData struct {
|
||||
enabled bool
|
||||
status int
|
||||
headers http.Header
|
||||
body *bytes.Buffer
|
||||
logs []debugDataLog
|
||||
|
||||
request *http.Request
|
||||
parent http.ResponseWriter
|
||||
}
|
||||
|
||||
func NewDebug(debug bool, request *http.Request, writer http.ResponseWriter) *DebugData {
|
||||
return &DebugData{
|
||||
enabled: debug,
|
||||
status: http.StatusOK,
|
||||
headers: make(http.Header),
|
||||
body: new(bytes.Buffer),
|
||||
logs: []debugDataLog{},
|
||||
parent: writer,
|
||||
request: request,
|
||||
}
|
||||
}
|
||||
|
||||
type debugDataLog struct {
|
||||
Level string
|
||||
Time time.Time
|
||||
Message string
|
||||
}
|
||||
|
||||
func (d *DebugData) Log(msg string) {
|
||||
if !d.enabled {
|
||||
return
|
||||
}
|
||||
d.logs = append(d.logs, debugDataLog{
|
||||
Level: "log",
|
||||
Time: time.Now(),
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DebugData) Warn(msg string) {
|
||||
if !d.enabled {
|
||||
return
|
||||
}
|
||||
d.logs = append(d.logs, debugDataLog{
|
||||
Level: "warn",
|
||||
Time: time.Now(),
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DebugData) Error(msg string) {
|
||||
if !d.enabled {
|
||||
return
|
||||
}
|
||||
d.logs = append(d.logs, debugDataLog{
|
||||
Level: "error",
|
||||
Time: time.Now(),
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DebugData) Header() http.Header {
|
||||
if d.enabled {
|
||||
return d.headers
|
||||
}
|
||||
return d.parent.Header()
|
||||
}
|
||||
|
||||
func (d *DebugData) Write(i []byte) (int, error) {
|
||||
if d.enabled {
|
||||
return d.body.Write(i)
|
||||
}
|
||||
return d.parent.Write(i)
|
||||
}
|
||||
|
||||
func (d *DebugData) WriteHeader(statusCode int) {
|
||||
if d.enabled {
|
||||
d.status = statusCode
|
||||
} else {
|
||||
d.parent.WriteHeader(statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DebugData) Flush(err error) error {
|
||||
if !d.enabled {
|
||||
return err
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"Logs": d.logs,
|
||||
"Output": d.body.String(),
|
||||
"Headers": d.Header(),
|
||||
}
|
||||
d.body.Reset()
|
||||
if err != nil {
|
||||
d.status = http.StatusInternalServerError
|
||||
data["Error"] = err.Error()
|
||||
}
|
||||
d.parent.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
d.parent.WriteHeader(d.status)
|
||||
_ = errorPage.Execute(d.parent, data)
|
||||
return nil
|
||||
}
|
||||
57
pkg/filters/goja/var_kv.go
Normal file
57
pkg/filters/goja/var_kv.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||
"gopkg.d7z.net/middleware/kv"
|
||||
)
|
||||
|
||||
func KVInject(ctx core.FilterContext, jsCtx *goja.Runtime) error {
|
||||
return jsCtx.GlobalObject().Set("kv", map[string]interface{}{
|
||||
"repo": func(group string) goja.Value {
|
||||
return kvResult(ctx.RepoDB)(ctx, jsCtx, group)
|
||||
},
|
||||
"org": func(group string) goja.Value {
|
||||
return kvResult(ctx.OrgDB)(ctx, jsCtx, group)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func kvResult(db kv.CursorPagedKV) func(ctx core.FilterContext, jsCtx *goja.Runtime, group string) goja.Value {
|
||||
return func(ctx core.FilterContext, jsCtx *goja.Runtime, group string) goja.Value {
|
||||
group = strings.TrimSpace(group)
|
||||
if group == "" {
|
||||
panic("kv: invalid group name")
|
||||
}
|
||||
db := db.Child(group)
|
||||
return jsCtx.ToValue(map[string]interface{}{
|
||||
"get": func(key string) goja.Value {
|
||||
get, err := db.Get(ctx, key)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
panic(err)
|
||||
}
|
||||
return goja.Null()
|
||||
}
|
||||
return jsCtx.ToValue(get)
|
||||
},
|
||||
"set": func(key string, value string) {
|
||||
err := db.Put(ctx, key, value, kv.TTLKeep)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
"delete": func(key string) bool {
|
||||
b, err := db.Delete(ctx, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1 +1,77 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"gopkg.d7z.net/gitea-pages/pkg/core"
|
||||
)
|
||||
|
||||
func RequestInject(ctx core.FilterContext, jsCtx *goja.Runtime, req *http.Request) error {
|
||||
url := *req.URL
|
||||
url.Path = ctx.Path
|
||||
|
||||
// 预计算头信息以提高性能
|
||||
headers := make(map[string]string)
|
||||
headerNames := make([]string, 0, len(req.Header))
|
||||
rawHeaderNames := make([]string, 0, len(req.Header))
|
||||
|
||||
for key, values := range req.Header {
|
||||
if len(values) > 0 {
|
||||
headers[key] = values[0]
|
||||
}
|
||||
headerNames = append(headerNames, strings.ToLower(key))
|
||||
rawHeaderNames = append(rawHeaderNames, key)
|
||||
}
|
||||
|
||||
queryParams := make(map[string]string)
|
||||
for key, values := range url.Query() {
|
||||
if len(values) > 0 {
|
||||
queryParams[key] = values[0]
|
||||
}
|
||||
}
|
||||
return jsCtx.GlobalObject().Set("request", map[string]any{
|
||||
"method": req.Method,
|
||||
"url": url,
|
||||
"rawPath": req.URL.Path,
|
||||
"host": req.Host,
|
||||
"remoteAddr": req.RemoteAddr,
|
||||
"proto": req.Proto,
|
||||
"httpVersion": req.Proto,
|
||||
"path": url.Path,
|
||||
"query": queryParams,
|
||||
"headers": headers,
|
||||
"get": func(key string) any {
|
||||
get := req.Header.Get(key)
|
||||
if get != "" {
|
||||
return get
|
||||
}
|
||||
return nil
|
||||
},
|
||||
"getHeader": func(name string) string {
|
||||
return req.Header.Get(name)
|
||||
},
|
||||
"getHeaderNames": func() []string {
|
||||
return headerNames
|
||||
},
|
||||
"getHeaders": func() map[string]string {
|
||||
return headers
|
||||
},
|
||||
"getRawHeaderNames": func() []string {
|
||||
return rawHeaderNames
|
||||
},
|
||||
"hasHeader": func(name string) bool {
|
||||
return req.Header.Get(name) != ""
|
||||
},
|
||||
"readBody": func() []byte {
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return body
|
||||
},
|
||||
"protocol": req.Proto,
|
||||
})
|
||||
}
|
||||
|
||||
149
pkg/filters/goja/var_response.go
Normal file
149
pkg/filters/goja/var_response.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package goja
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
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) {
|
||||
writer.Header().Set(key, value)
|
||||
},
|
||||
|
||||
"getHeader": func(key string) string {
|
||||
return writer.Header().Get(key)
|
||||
},
|
||||
|
||||
"removeHeader": func(key string) {
|
||||
writer.Header().Del(key)
|
||||
},
|
||||
|
||||
"hasHeader": func(key string) bool {
|
||||
_, exists := writer.Header()[key]
|
||||
return exists
|
||||
},
|
||||
|
||||
// 状态码操作
|
||||
"setStatus": func(statusCode int) {
|
||||
writer.WriteHeader(statusCode)
|
||||
},
|
||||
|
||||
"statusCode": func(statusCode int) {
|
||||
writer.WriteHeader(statusCode)
|
||||
},
|
||||
|
||||
// 写入响应
|
||||
"write": func(data string) {
|
||||
_, err := writer.Write([]byte(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
|
||||
"writeHead": func(statusCode int, headers ...map[string]string) {
|
||||
// 处理可选的 headers 参数
|
||||
if len(headers) > 0 {
|
||||
for key, value := range headers[0] {
|
||||
writer.Header().Set(key, value)
|
||||
}
|
||||
}
|
||||
writer.WriteHeader(statusCode)
|
||||
},
|
||||
|
||||
"end": func(data ...string) {
|
||||
if len(data) > 0 {
|
||||
_, err := writer.Write([]byte(data[0]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// 在实际的 HTTP 处理中,我们通常不手动结束响应
|
||||
},
|
||||
|
||||
// 重定向
|
||||
"redirect": func(location string, statusCode ...int) {
|
||||
code := 302
|
||||
if len(statusCode) > 0 {
|
||||
code = statusCode[0]
|
||||
}
|
||||
http.Redirect(writer, req, location, code)
|
||||
},
|
||||
|
||||
// JSON 响应
|
||||
"json": func(data goja.Value) {
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
|
||||
var jsonStr string
|
||||
export := data.Export()
|
||||
switch v := export.(type) {
|
||||
case string:
|
||||
jsonStr = v
|
||||
default:
|
||||
marshal, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
jsonStr = string(marshal)
|
||||
}
|
||||
_, err := writer.Write([]byte(jsonStr))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
|
||||
// 设置 cookie
|
||||
"setCookie": func(name string, value string, options ...map[string]interface{}) {
|
||||
cookie := &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
// 处理可选参数
|
||||
if len(options) > 0 && options[0] != nil {
|
||||
opts := options[0]
|
||||
|
||||
if maxAge, ok := opts["maxAge"].(int); ok {
|
||||
cookie.MaxAge = maxAge
|
||||
}
|
||||
|
||||
if expires, ok := opts["expires"].(int64); ok {
|
||||
cookie.Expires = time.Unix(expires, 0)
|
||||
}
|
||||
|
||||
if path, ok := opts["path"].(string); ok {
|
||||
cookie.Path = path
|
||||
}
|
||||
|
||||
if domain, ok := opts["domain"].(string); ok {
|
||||
cookie.Domain = domain
|
||||
}
|
||||
|
||||
if secure, ok := opts["secure"].(bool); ok {
|
||||
cookie.Secure = secure
|
||||
}
|
||||
|
||||
if httpOnly, ok := opts["httpOnly"].(bool); ok {
|
||||
cookie.HttpOnly = httpOnly
|
||||
}
|
||||
|
||||
if sameSite, ok := opts["sameSite"].(string); ok {
|
||||
switch sameSite {
|
||||
case "lax":
|
||||
cookie.SameSite = http.SameSiteLaxMode
|
||||
case "strict":
|
||||
cookie.SameSite = http.SameSiteStrictMode
|
||||
case "none":
|
||||
cookie.SameSite = http.SameSiteNoneMode
|
||||
}
|
||||
}
|
||||
}
|
||||
http.SetCookie(writer, cookie)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package quickjs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/buke/quickjs-go"
|
||||
)
|
||||
|
||||
type DebugData struct {
|
||||
Status int `json:"status"`
|
||||
Header http.Header `json:"header"`
|
||||
Body string `json:"body"`
|
||||
Logs []DebugDataLog `json:"logs"`
|
||||
}
|
||||
|
||||
type DebugDataLog struct {
|
||||
Level string `json:"level"`
|
||||
Time time.Time `json:"time"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// debugResponseWriter 用于在 debug 模式下捕获响应输出
|
||||
type debugResponseWriter struct {
|
||||
buffer *strings.Builder
|
||||
header http.Header
|
||||
status int
|
||||
}
|
||||
|
||||
func (w *debugResponseWriter) Header() http.Header {
|
||||
return w.header
|
||||
}
|
||||
|
||||
func (w *debugResponseWriter) Write(data []byte) (int, error) {
|
||||
return w.buffer.Write(data)
|
||||
}
|
||||
|
||||
func (w *debugResponseWriter) WriteHeader(statusCode int) {
|
||||
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)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
const req = request;
|
||||
const resp = response;
|
||||
@@ -1,101 +0,0 @@
|
||||
package quickjs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/buke/quickjs-go"
|
||||
"github.com/pkg/errors"
|
||||
"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 param struct {
|
||||
Exec string `json:"exec"`
|
||||
Debug bool `json:"debug"`
|
||||
}
|
||||
if err := config.Unmarshal(¶m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if param.Exec == "" {
|
||||
return nil, errors.Errorf("no exec specified")
|
||||
}
|
||||
return func(ctx core.FilterContext, writer http.ResponseWriter, request *http.Request, next core.NextCall) error {
|
||||
js, err := ctx.ReadString(ctx, param.Exec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rt := quickjs.NewRuntime()
|
||||
rt.SetExecuteTimeout(5)
|
||||
rt.SetMemoryLimit(10 * 1024 * 1024)
|
||||
defer rt.Close()
|
||||
jsCtx := rt.NewContext()
|
||||
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 模式下,我们需要拦截输出
|
||||
var (
|
||||
outputBuffer strings.Builder
|
||||
logBuffer strings.Builder
|
||||
jsError error
|
||||
)
|
||||
|
||||
global := jsCtx.Globals()
|
||||
global.Set("request", createRequestObject(jsCtx, request, ctx))
|
||||
// 根据是否 debug 模式创建不同的 response 对象
|
||||
if param.Debug {
|
||||
// debug 模式下使用虚假的 writer 来捕获输出
|
||||
global.Set("response", createResponseObject(jsCtx, &debugResponseWriter{
|
||||
buffer: &outputBuffer,
|
||||
header: make(http.Header),
|
||||
}, request))
|
||||
global.Set("console", createConsoleObject(jsCtx, &logBuffer))
|
||||
} else {
|
||||
global.Set("response", createResponseObject(jsCtx, writer, request))
|
||||
global.Set("console", createConsoleObject(jsCtx, nil))
|
||||
}
|
||||
jsCtx.Eval(inject)
|
||||
ret := jsCtx.EvalBytecode(bytecode)
|
||||
defer ret.Free()
|
||||
jsCtx.Loop()
|
||||
|
||||
if ret.IsException() {
|
||||
err := jsCtx.Exception()
|
||||
jsError = err
|
||||
}
|
||||
|
||||
// 如果在 debug 模式下,返回 HTML 调试页面
|
||||
if param.Debug {
|
||||
return renderDebugPage(writer, &outputBuffer, &logBuffer, jsError)
|
||||
}
|
||||
|
||||
return jsError
|
||||
}, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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 +0,0 @@
|
||||
package quickjs
|
||||
@@ -1,64 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -27,7 +27,12 @@ func NewDefaultTestServer() *TestServer {
|
||||
|
||||
func NewTestServer(domain string) *TestServer {
|
||||
atom := zap.NewAtomicLevel()
|
||||
atom.SetLevel(zap.DebugLevel)
|
||||
getenv := os.Getenv("BM")
|
||||
if getenv != "" {
|
||||
atom.SetLevel(zap.ErrorLevel)
|
||||
} else {
|
||||
atom.SetLevel(zap.DebugLevel)
|
||||
}
|
||||
cfg := zap.NewProductionConfig()
|
||||
cfg.Level = atom
|
||||
logger, _ := cfg.Build()
|
||||
@@ -88,7 +93,7 @@ func (t *TestServer) OpenRequest(method, url string, body io.Reader) ([]byte, *h
|
||||
if response.Body != nil {
|
||||
defer response.Body.Close()
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
if response.StatusCode >= 400 {
|
||||
return nil, response, errors.New(response.Status)
|
||||
}
|
||||
all, _ := io.ReadAll(response.Body)
|
||||
|
||||
@@ -2,13 +2,14 @@ package tests
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.d7z.net/gitea-pages/tests/core"
|
||||
)
|
||||
|
||||
func Test_JS(t *testing.T) {
|
||||
func Test_GoJaJS(t *testing.T) {
|
||||
server := core.NewDefaultTestServer()
|
||||
defer server.Close()
|
||||
server.AddFile("org1/repo1/gh-pages/index.html", "hello world")
|
||||
@@ -16,24 +17,28 @@ func Test_JS(t *testing.T) {
|
||||
function get(a,b) {
|
||||
return a + b;
|
||||
}
|
||||
response.writeHead(201,{'X-Cache': 'ignore'});
|
||||
console.log('hello world')
|
||||
response.write('512 + 512 = ' + get(512,512))
|
||||
`)
|
||||
server.AddFile("org1/repo1/gh-pages/.pages.yaml", `
|
||||
routes:
|
||||
- path: "api/v1/**"
|
||||
qjs:
|
||||
js:
|
||||
exec: "index.js"
|
||||
`)
|
||||
data, _, err := server.OpenFile("https://org1.example.com/repo1/")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello world", string(data))
|
||||
|
||||
data, _, err = server.OpenFile("https://org1.example.com/repo1/api/v1/get")
|
||||
data, resp, err := server.OpenFile("https://org1.example.com/repo1/api/v1/get")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 201, resp.StatusCode)
|
||||
assert.Equal(t, "ignore", resp.Header.Get("X-Cache"))
|
||||
assert.Equal(t, "512 + 512 = 1024", string(data))
|
||||
}
|
||||
|
||||
func Test_JS_Request(t *testing.T) {
|
||||
func Test_GoJa_Request(t *testing.T) {
|
||||
server := core.NewDefaultTestServer()
|
||||
defer server.Close()
|
||||
server.AddFile("org1/repo1/gh-pages/index.html", "hello world")
|
||||
@@ -41,7 +46,7 @@ func Test_JS_Request(t *testing.T) {
|
||||
server.AddFile("org1/repo1/gh-pages/.pages.yaml", `
|
||||
routes:
|
||||
- path: "api/v1/**"
|
||||
qjs:
|
||||
js:
|
||||
exec: "index.js"
|
||||
`)
|
||||
data, _, err := server.OpenFile("https://org1.example.com/repo1/")
|
||||
@@ -57,8 +62,8 @@ routes:
|
||||
assert.Equal(t, "POST /api/v1/fetch", string(data))
|
||||
}
|
||||
|
||||
func Benchmark_JS_Request(b *testing.B) {
|
||||
// 初始化服务器(在基准测试外执行,避免计入时间)
|
||||
func Benchmark_GoJa_Request(b *testing.B) {
|
||||
_ = os.Setenv("BM", "1")
|
||||
server := core.NewDefaultTestServer()
|
||||
defer server.Close()
|
||||
server.AddFile("org1/repo1/gh-pages/index.html", "hello world")
|
||||
@@ -66,7 +71,7 @@ func Benchmark_JS_Request(b *testing.B) {
|
||||
server.AddFile("org1/repo1/gh-pages/.pages.yaml", `
|
||||
routes:
|
||||
- path: "api/v1/**"
|
||||
qjs:
|
||||
js:
|
||||
exec: "index.js"
|
||||
`)
|
||||
|
||||
Reference in New Issue
Block a user