diff --git a/README.md b/README.md index 63e627e..6a3c3d8 100644 --- a/README.md +++ b/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 diff --git a/README_zh.md b/README_zh.md index 0f8f90f..2bafeb2 100644 --- a/README_zh.md +++ b/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 diff --git a/examples/HelloWorld/index.html b/examples/HelloWorld/index.html index 719c0fb..301f4ab 100644 --- a/examples/HelloWorld/index.html +++ b/examples/HelloWorld/index.html @@ -5,7 +5,7 @@ - qjs 验证 + js 验证

Hello World

diff --git a/examples/QuickJS/.pages.yaml b/examples/JS/.pages.yaml similarity index 72% rename from examples/QuickJS/.pages.yaml rename to examples/JS/.pages.yaml index 07b9ebf..d2c0ea4 100644 --- a/examples/QuickJS/.pages.yaml +++ b/examples/JS/.pages.yaml @@ -1,8 +1,8 @@ routes: - path: "api/dev/**" - qjs: + js: exec: "index.js" debug: true - path: "api/prod/**" - qjs: + js: exec: "json.js" \ No newline at end of file diff --git a/examples/QuickJS/index.html b/examples/JS/index.html similarity index 86% rename from examples/QuickJS/index.html rename to examples/JS/index.html index 7a6795a..b77ec73 100644 --- a/examples/QuickJS/index.html +++ b/examples/JS/index.html @@ -5,7 +5,7 @@ - Quickjs 概念验证 + JS 概念验证 diff --git a/examples/QuickJS/index.js b/examples/JS/index.js similarity index 65% rename from examples/QuickJS/index.js rename to examples/JS/index.js index d112657..bb52ceb 100644 --- a/examples/QuickJS/index.js +++ b/examples/JS/index.js @@ -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() \ No newline at end of file diff --git a/examples/QuickJS/json.js b/examples/JS/json.js similarity index 100% rename from examples/QuickJS/json.js rename to examples/JS/json.js diff --git a/examples/kv/.pages.yaml b/examples/kv/.pages.yaml new file mode 100644 index 0000000..e7af8f2 --- /dev/null +++ b/examples/kv/.pages.yaml @@ -0,0 +1,10 @@ +routes: + - path: "get" + js: + exec: "index.js" + debug: true + + - path: "put" + js: + exec: "index.js" + debug: true \ No newline at end of file diff --git a/examples/kv/index.html b/examples/kv/index.html new file mode 100644 index 0000000..ccaf75a --- /dev/null +++ b/examples/kv/index.html @@ -0,0 +1,14 @@ + + + + + + + JS 概念验证 + + +查询数据 +更新数据 + + \ No newline at end of file diff --git a/examples/kv/index.js b/examples/kv/index.js new file mode 100644 index 0000000..002570e --- /dev/null +++ b/examples/kv/index.js @@ -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')) diff --git a/go.mod b/go.mod index 15bb4d6..a8a4b22 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 2f15b40..bf8cefb 100644 --- a/go.sum +++ b/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= diff --git a/pkg/filters/common.go b/pkg/filters/common.go index 22c4e91..39fbf1b 100644 --- a/pkg/filters/common.go +++ b/pkg/filters/common.go @@ -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, } } diff --git a/pkg/filters/default.go b/pkg/filters/default.go index bde67e8..0770d67 100644 --- a/pkg/filters/default.go +++ b/pkg/filters/default.go @@ -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 } diff --git a/pkg/filters/goja/debug.tmpl b/pkg/filters/goja/debug.tmpl new file mode 100644 index 0000000..92ed3eb --- /dev/null +++ b/pkg/filters/goja/debug.tmpl @@ -0,0 +1,95 @@ + + + + 调试控制台 + + + + + + +

Debug Output

+ +
+
执行结果
+
+
{{ .Output }}
+
+
+ +
+
控制台日志
+
+
{{- range .Logs }}

{{ .Time.Format "2006/01/02 15:04:05" }} - {{ .Level }} : {{ .Message }}

{{- end }}
+
+
+ +
+
响应头
+
+
{{- range $key, $value := .Headers }}

{{ $key }} = {{ $value }}

{{- end }}
+
+
+ +
+
执行状态
+
+ {{- if .Error }} +
{{ .Error }}
+ {{- else }} +
执行成功
+ {{- end }} +
+
+ + \ No newline at end of file diff --git a/pkg/filters/goja/goja.go b/pkg/filters/goja/goja.go index bf434a6..d244445 100644 --- a/pkg/filters/goja/goja.go +++ b/pkg/filters/goja/goja.go @@ -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 } diff --git a/pkg/filters/goja/var_debug.go b/pkg/filters/goja/var_debug.go new file mode 100644 index 0000000..323f9d0 --- /dev/null +++ b/pkg/filters/goja/var_debug.go @@ -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 +} diff --git a/pkg/filters/goja/var_kv.go b/pkg/filters/goja/var_kv.go new file mode 100644 index 0000000..741d95e --- /dev/null +++ b/pkg/filters/goja/var_kv.go @@ -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 + }, + }) + } +} diff --git a/pkg/filters/goja/var_request.go b/pkg/filters/goja/var_request.go index 9ac468f..b78e7ec 100644 --- a/pkg/filters/goja/var_request.go +++ b/pkg/filters/goja/var_request.go @@ -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, + }) +} diff --git a/pkg/filters/goja/var_response.go b/pkg/filters/goja/var_response.go new file mode 100644 index 0000000..51ba7ab --- /dev/null +++ b/pkg/filters/goja/var_response.go @@ -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) + }, + }) +} diff --git a/pkg/filters/quickjs/debug.go b/pkg/filters/quickjs/debug.go deleted file mode 100644 index 49d8708..0000000 --- a/pkg/filters/quickjs/debug.go +++ /dev/null @@ -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 := ` - - - QuickJS Debug - - - -

QuickJS Debug Output

- -
-
执行结果
-
-
` - - // 转义输出内容 - output := outputBuffer.String() - if output == "" { - output = "(无输出)" - } - html += htmlEscape(output) - - html += `
-
-
- -
-
控制台日志
-
-
` - - // 转义日志内容 - logs := logBuffer.String() - if logs == "" { - logs = "(无日志)" - } - html += htmlEscape(logs) - - html += `
-
-
- -
-
执行状态
-
` - - if jsError != nil { - html += `
Message:
` - var q *quickjs.Error - if errors.As(jsError, &q) { - html += q.Message + "

" - html += `Stack:
` + q.Stack - } else { - html += jsError.Error() - } - - html += `
` - } else { - html += `
执行成功
` - } - - html += `
-
- -` - - _, err := writer.Write([]byte(html)) - return err -} - -// htmlEscape 转义 HTML 特殊字符 -func htmlEscape(s string) string { - return strings.NewReplacer( - "&", "&", - "<", "<", - ">", ">", - `"`, """, - "'", "'", - ).Replace(s) -} diff --git a/pkg/filters/quickjs/inject.js b/pkg/filters/quickjs/inject.js deleted file mode 100644 index dba97ca..0000000 --- a/pkg/filters/quickjs/inject.js +++ /dev/null @@ -1,2 +0,0 @@ -const req = request; -const resp = response; \ No newline at end of file diff --git a/pkg/filters/quickjs/quickjs.go b/pkg/filters/quickjs/quickjs.go deleted file mode 100644 index caf6c0f..0000000 --- a/pkg/filters/quickjs/quickjs.go +++ /dev/null @@ -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 -} diff --git a/pkg/filters/quickjs/var_console.go b/pkg/filters/quickjs/var_console.go deleted file mode 100644 index 33c4915..0000000 --- a/pkg/filters/quickjs/var_console.go +++ /dev/null @@ -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 -} diff --git a/pkg/filters/quickjs/var_kv.go b/pkg/filters/quickjs/var_kv.go deleted file mode 100644 index bb9dd4d..0000000 --- a/pkg/filters/quickjs/var_kv.go +++ /dev/null @@ -1 +0,0 @@ -package quickjs diff --git a/pkg/filters/quickjs/var_request.go b/pkg/filters/quickjs/var_request.go deleted file mode 100644 index b0a3ad0..0000000 --- a/pkg/filters/quickjs/var_request.go +++ /dev/null @@ -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 -} diff --git a/pkg/filters/quickjs/var_response.go b/pkg/filters/quickjs/var_response.go deleted file mode 100644 index 21fedb1..0000000 --- a/pkg/filters/quickjs/var_response.go +++ /dev/null @@ -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 -} diff --git a/tests/core/test.go b/tests/core/test.go index 274f607..746590c 100644 --- a/tests/core/test.go +++ b/tests/core/test.go @@ -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) diff --git a/tests/filter_qjs_test.go b/tests/filter_goja_test.go similarity index 87% rename from tests/filter_qjs_test.go rename to tests/filter_goja_test.go index 614f707..1f69510 100644 --- a/tests/filter_qjs_test.go +++ b/tests/filter_goja_test.go @@ -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" `)