mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-11-06 01:50:26 +08:00
阶段性提交
This commit is contained in:
50
internal/js/console.go
Normal file
50
internal/js/console.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Console struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Console) Log(args ...interface{}) {
|
||||||
|
for index, arg := range args {
|
||||||
|
if arg != nil {
|
||||||
|
switch arg.(type) {
|
||||||
|
case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, string:
|
||||||
|
default:
|
||||||
|
var argType = reflect.TypeOf(arg)
|
||||||
|
|
||||||
|
// 是否有String()方法,如果有直接调用
|
||||||
|
method, ok := argType.MethodByName("String")
|
||||||
|
if ok && method.Type.NumIn() == 1 && method.Type.NumOut() == 1 && method.Type.Out(0).Kind() == reflect.String {
|
||||||
|
args[index] = method.Func.Call([]reflect.Value{reflect.ValueOf(arg)})[0].String()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转为JSON
|
||||||
|
argJSON, err := this.toJSON(arg)
|
||||||
|
if err != nil {
|
||||||
|
if argType.Kind() == reflect.Func {
|
||||||
|
args[index] = "[function]"
|
||||||
|
} else {
|
||||||
|
args[index] = "[object]"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args[index] = string(argJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args[index] = "null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs.Println(append([]interface{}{"[js][console]"}, args...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Console) toJSON(o interface{}) ([]byte, error) {
|
||||||
|
return json.Marshal(o)
|
||||||
|
}
|
||||||
38
internal/js/console_test.go
Normal file
38
internal/js/console_test.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConsole_Log(t *testing.T) {
|
||||||
|
{
|
||||||
|
vm := NewVM()
|
||||||
|
_, err := vm.RunString("console.log('Hello', 'world')")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
vm := NewVM()
|
||||||
|
_, err := vm.RunString("console.log(null, true, false, 10, 10.123)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
vm := NewVM()
|
||||||
|
_, err := vm.RunString("console.log({ a:1, b:2 })")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
vm := NewVM()
|
||||||
|
_, err := vm.RunString("console.log(console.log)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
internal/js/http.go
Normal file
36
internal/js/http.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
type HTTP struct {
|
||||||
|
r RequestInterface
|
||||||
|
|
||||||
|
req *Request
|
||||||
|
resp *Response
|
||||||
|
|
||||||
|
onRequest func(req *Request, resp *Response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTP(r RequestInterface) *HTTP {
|
||||||
|
return &HTTP{
|
||||||
|
req: NewRequest(r),
|
||||||
|
resp: NewResponse(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTP) OnRequest(callback func(req *Request, resp *Response)) {
|
||||||
|
// TODO 考虑是否支持多个callback
|
||||||
|
this.onRequest = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTP) OnData(callback func(req *Request, resp *Response)) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTP) OnResponse(callback func(req *Request, resp *Response)) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTP) TriggerRequest() {
|
||||||
|
this.onRequest(this.req, this.resp)
|
||||||
|
}
|
||||||
82
internal/js/request.go
Normal file
82
internal/js/request.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
r RequestInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequest(r RequestInterface) *Request {
|
||||||
|
return &Request{
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) Proto() string {
|
||||||
|
return this.r.JSRequest().Proto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) Method() string {
|
||||||
|
return this.r.JSRequest().Method
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) Header() map[string][]string {
|
||||||
|
return this.r.JSRequest().Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) AddHeader(name string, value string) {
|
||||||
|
this.r.JSRequest().Header[name] = append(this.r.JSRequest().Header[name], value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) SetHeader(name string, value string) {
|
||||||
|
this.r.JSRequest().Header[name] = []string{value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) RemoteAddr() string {
|
||||||
|
var remoteAddr = this.r.JSRequest().RemoteAddr
|
||||||
|
host, _, err := net.SplitHostPort(remoteAddr)
|
||||||
|
if err == nil {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
return remoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) Url() *URL {
|
||||||
|
return NewURL(this.r.JSRequest().URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) ContentLength() int64 {
|
||||||
|
return this.r.JSRequest().ContentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) Body() []byte {
|
||||||
|
var bodyReader = this.r.JSRequest().Body
|
||||||
|
if bodyReader == nil {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
this.r.JSLog("read body failed: " + err.Error())
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Request) CopyBody() []byte {
|
||||||
|
var bodyReader = this.r.JSRequest().Body
|
||||||
|
if bodyReader == nil {
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(bodyReader)
|
||||||
|
if err != nil {
|
||||||
|
this.r.JSLog("read body failed: " + err.Error())
|
||||||
|
}
|
||||||
|
this.r.JSRequest().Body = ioutil.NopCloser(bytes.NewReader(data))
|
||||||
|
return data
|
||||||
|
}
|
||||||
19
internal/js/request_interface.go
Normal file
19
internal/js/request_interface.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type RequestInterface interface {
|
||||||
|
// JSRequest 请求
|
||||||
|
JSRequest() *http.Request
|
||||||
|
|
||||||
|
// JSWriter 响应
|
||||||
|
JSWriter() http.ResponseWriter
|
||||||
|
|
||||||
|
// JSStop 中止请求
|
||||||
|
JSStop()
|
||||||
|
|
||||||
|
// JSLog 打印日志
|
||||||
|
JSLog(msg ...interface{})
|
||||||
|
}
|
||||||
124
internal/js/request_test.go
Normal file
124
internal/js/request_test.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/js"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testRequest struct {
|
||||||
|
rawRequest *http.Request
|
||||||
|
rawResponse *testResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *testRequest) JSRequest() *http.Request {
|
||||||
|
if this.rawRequest != nil {
|
||||||
|
return this.rawRequest
|
||||||
|
}
|
||||||
|
req, _ := http.NewRequest(http.MethodGet, "https://iwind:123456@goedge.cn/docs?name=Libai&age=20", nil)
|
||||||
|
req.Header.Set("Server", "edgejs/1.0")
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader([]byte("123456")))
|
||||||
|
this.rawRequest = req
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *testRequest) JSWriter() http.ResponseWriter {
|
||||||
|
if this.rawResponse != nil {
|
||||||
|
return this.rawResponse
|
||||||
|
}
|
||||||
|
this.rawResponse = &testResponse{}
|
||||||
|
return this.rawResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *testRequest) JSStop() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *testRequest) JSLog(msg ...interface{}) {
|
||||||
|
logs.Println(msg...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testResponse struct {
|
||||||
|
statusCode int
|
||||||
|
header http.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *testResponse) Header() http.Header {
|
||||||
|
if this.header == nil {
|
||||||
|
this.header = http.Header{}
|
||||||
|
}
|
||||||
|
return this.header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *testResponse) Write(p []byte) (int, error) {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *testResponse) WriteHeader(statusCode int) {
|
||||||
|
this.statusCode = statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest(t *testing.T) {
|
||||||
|
vm := js.NewVM()
|
||||||
|
vm.SetRequest(&testRequest{})
|
||||||
|
|
||||||
|
// 事件监听
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
http.onRequest(function (req, resp) {
|
||||||
|
console.log(req.proto())
|
||||||
|
|
||||||
|
let url = req.url()
|
||||||
|
console.log(url, "port:", url.port(), "args:", url.args())
|
||||||
|
console.log("username:", url.username(), "password:", url.password())
|
||||||
|
console.log("uri:", url.uri(), "path:", url.path())
|
||||||
|
|
||||||
|
req.addHeader("Server", "1.0")
|
||||||
|
|
||||||
|
|
||||||
|
resp.write("this is response")
|
||||||
|
console.log(resp)
|
||||||
|
|
||||||
|
console.log(req.body())
|
||||||
|
})
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发事件
|
||||||
|
_, err = vm.RunString(`http.triggerRequest()`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest_Header(t *testing.T) {
|
||||||
|
var req = js.NewRequest(&testRequest{})
|
||||||
|
logs.PrintAsJSON(req.Header(), t)
|
||||||
|
|
||||||
|
req.AddHeader("Content-Length", "10")
|
||||||
|
req.AddHeader("Vary", "1.0")
|
||||||
|
req.AddHeader("Vary", "2.0")
|
||||||
|
logs.PrintAsJSON(req.Header(), t)
|
||||||
|
|
||||||
|
req.SetHeader("Vary", "3.0")
|
||||||
|
logs.PrintAsJSON(req.Header(), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest_Body(t *testing.T) {
|
||||||
|
var req = js.NewRequest(&testRequest{})
|
||||||
|
t.Log(string(req.Body()))
|
||||||
|
t.Log(string(req.Body()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest_CopyBody(t *testing.T) {
|
||||||
|
var req = js.NewRequest(&testRequest{})
|
||||||
|
t.Log(string(req.CopyBody()))
|
||||||
|
t.Log(string(req.CopyBody()))
|
||||||
|
}
|
||||||
39
internal/js/response.go
Normal file
39
internal/js/response.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
r RequestInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponse(r RequestInterface) *Response {
|
||||||
|
return &Response{
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Response) Write(s string) error {
|
||||||
|
_, err := this.r.JSWriter().Write([]byte(s))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Response) Reply(status int) {
|
||||||
|
this.SetStatus(status)
|
||||||
|
this.r.JSStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Response) Header() map[string][]string {
|
||||||
|
return this.r.JSWriter().Header()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Response) AddHeader(name string, value string) {
|
||||||
|
this.r.JSWriter().Header()[name] = append(this.r.JSWriter().Header()[name], value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Response) SetHeader(name string, value string) {
|
||||||
|
this.r.JSWriter().Header()[name] = []string{value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Response) SetStatus(statusCode int) {
|
||||||
|
this.r.JSWriter().WriteHeader(statusCode)
|
||||||
|
}
|
||||||
16
internal/js/response_test.go
Normal file
16
internal/js/response_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeNode/internal/js"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewResponse(t *testing.T) {
|
||||||
|
var resp = js.NewResponse(&testRequest{})
|
||||||
|
resp.AddHeader("Vary", "1.0")
|
||||||
|
resp.AddHeader("Vary", "2.0")
|
||||||
|
resp.SetHeader("Server", "edgejs/1.0")
|
||||||
|
t.Logf("%#v", resp.Header())
|
||||||
|
}
|
||||||
90
internal/js/url.go
Normal file
90
internal/js/url.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type URL struct {
|
||||||
|
u *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewURL(u *url.URL) *URL {
|
||||||
|
return &URL{
|
||||||
|
u: u,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) JSNew(args []goja.Value) *URL {
|
||||||
|
var urlString = ""
|
||||||
|
if len(args) == 1 {
|
||||||
|
urlString = args[0].String()
|
||||||
|
}
|
||||||
|
u, _ := url.Parse(urlString)
|
||||||
|
if u == nil {
|
||||||
|
u = &url.URL{}
|
||||||
|
}
|
||||||
|
return NewURL(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Port() int {
|
||||||
|
return types.Int(this.u.Port())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Args() map[string][]string {
|
||||||
|
return this.u.Query()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Arg(name string) string {
|
||||||
|
return this.u.Query().Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Username() string {
|
||||||
|
if this.u.User != nil {
|
||||||
|
return this.u.User.Username()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Password() string {
|
||||||
|
if this.u.User != nil {
|
||||||
|
password, _ := this.u.User.Password()
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Uri() string {
|
||||||
|
return this.u.RequestURI()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Path() string {
|
||||||
|
return this.u.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Host() string {
|
||||||
|
return this.u.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Fragment() string {
|
||||||
|
return this.u.Fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Hash() string {
|
||||||
|
if len(this.u.Fragment) > 0 {
|
||||||
|
return "#" + this.u.Fragment
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) Scheme() string {
|
||||||
|
return this.u.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *URL) String() string {
|
||||||
|
return this.u.String()
|
||||||
|
}
|
||||||
18
internal/js/url_test.go
Normal file
18
internal/js/url_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURL(t *testing.T) {
|
||||||
|
raw, err := url.Parse("https://iwind:123456@goedge.cn/docs?name=Libai&age=20#a=b")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var u = NewURL(raw)
|
||||||
|
t.Log("host:", u.Host())
|
||||||
|
t.Log("hash:", u.Hash())
|
||||||
|
}
|
||||||
153
internal/js/vm.go
Normal file
153
internal/js/vm.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedPrograms []*goja.Program
|
||||||
|
var sharedConsole = &Console{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// compile programs
|
||||||
|
}
|
||||||
|
|
||||||
|
type VM struct {
|
||||||
|
vm *goja.Runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVM() *VM {
|
||||||
|
vm := goja.New()
|
||||||
|
vm.SetFieldNameMapper(goja.TagFieldNameMapper("json", true))
|
||||||
|
|
||||||
|
// programs
|
||||||
|
for _, program := range sharedPrograms {
|
||||||
|
_, _ = vm.RunProgram(program)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := &VM{vm: vm}
|
||||||
|
v.initVM()
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *VM) Set(name string, obj interface{}) error {
|
||||||
|
return this.vm.Set(name, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *VM) AddConstructor(name string, instance interface{}) error {
|
||||||
|
objType := reflect.TypeOf(instance)
|
||||||
|
|
||||||
|
if objType.Kind() != reflect.Ptr {
|
||||||
|
return errors.New("instance should be pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct
|
||||||
|
newMethod, ok := objType.MethodByName("JSNew")
|
||||||
|
if !ok {
|
||||||
|
return errors.New("can not find 'JSNew()' method in '" + objType.Elem().Name() + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err = this.Set(name, func(call goja.ConstructorCall) *goja.Object {
|
||||||
|
if newMethod.Type.NumIn() != 2 {
|
||||||
|
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if newMethod.Type.In(1).String() != "[]goja.Value" {
|
||||||
|
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should accept a '[]goja.Value' argument"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// new
|
||||||
|
var results = newMethod.Func.Call([]reflect.Value{reflect.ValueOf(instance), reflect.ValueOf(call.Arguments)})
|
||||||
|
if len(results) == 0 {
|
||||||
|
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a valid instance"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var result = results[0]
|
||||||
|
if result.Type() != objType {
|
||||||
|
this.throw(errors.New(objType.Elem().Name() + ".JSNew() should return a same instance"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods
|
||||||
|
var resultType = result.Type()
|
||||||
|
var numMethod = result.NumMethod()
|
||||||
|
for i := 0; i < numMethod; i++ {
|
||||||
|
var method = resultType.Method(i)
|
||||||
|
var methodName = strings.ToLower(method.Name[:1]) + method.Name[1:]
|
||||||
|
err := call.This.Set(methodName, result.MethodByName(method.Name).Interface())
|
||||||
|
if err != nil {
|
||||||
|
this.throw(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持属性
|
||||||
|
var numField = result.Elem().Type().NumField()
|
||||||
|
for i := 0; i < numField; i++ {
|
||||||
|
var field = result.Elem().Field(i)
|
||||||
|
if !field.CanInterface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var fieldType = objType.Elem().Field(i)
|
||||||
|
tag, ok := fieldType.Tag.Lookup("json")
|
||||||
|
if !ok {
|
||||||
|
tag = fieldType.Name
|
||||||
|
tag = strings.ToLower(tag[:1]) + tag[1:]
|
||||||
|
} else {
|
||||||
|
// TODO 校验tag是否符合变量语法
|
||||||
|
}
|
||||||
|
err := call.This.Set(tag, field.Interface())
|
||||||
|
if err != nil {
|
||||||
|
this.throw(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *VM) RunString(str string) (goja.Value, error) {
|
||||||
|
defer func() {
|
||||||
|
e := recover()
|
||||||
|
if e != nil {
|
||||||
|
// TODO 需要打印trace
|
||||||
|
logs.Println("panic:", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return this.vm.RunString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *VM) SetRequest(req RequestInterface) {
|
||||||
|
{
|
||||||
|
err := this.vm.Set("http", NewHTTP(req))
|
||||||
|
if err != nil {
|
||||||
|
this.throw(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *VM) initVM() {
|
||||||
|
{
|
||||||
|
err := this.vm.Set("console", sharedConsole)
|
||||||
|
if err != nil {
|
||||||
|
this.throw(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *VM) throw(err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
logs.Println("js:VM:error: " + err.Error())
|
||||||
|
}
|
||||||
158
internal/js/vm_test.go
Normal file
158
internal/js/vm_test.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewVM(t *testing.T) {
|
||||||
|
before := time.Now()
|
||||||
|
defer func() {
|
||||||
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
}()
|
||||||
|
|
||||||
|
vm := NewVM()
|
||||||
|
{
|
||||||
|
v, err := vm.RunString("JSON.stringify({\"a\":\"b\"})")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("JSON.stringify():", v)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
v, err := vm.RunString(`JSON.parse('{\"a\":\"b\"}')`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log("JSON.parse():", v)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
err := vm.AddConstructor("Url", &URL{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("add constructor error:", err)
|
||||||
|
}
|
||||||
|
_, err = vm.RunString(`
|
||||||
|
{
|
||||||
|
let u = new Url("https://goedge.cn/docs?v=1")
|
||||||
|
console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||||
|
console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url()
|
||||||
|
console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url("a", "b", "c")
|
||||||
|
console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("add constructor error:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVM_Program(t *testing.T) {
|
||||||
|
var s = `
|
||||||
|
{
|
||||||
|
let u = new Url("https://goedge.cn/docs?v=1")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url()
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url("a", "b", "c")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
`
|
||||||
|
program := goja.MustCompile("s", s, true)
|
||||||
|
|
||||||
|
before := time.Now()
|
||||||
|
defer func() {
|
||||||
|
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||||
|
}()
|
||||||
|
|
||||||
|
vm := NewVM()
|
||||||
|
err := vm.AddConstructor("Url", &URL{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("add constructor error:", err)
|
||||||
|
}
|
||||||
|
//_, err = vm.RunString(s)
|
||||||
|
_, err = vm.vm.RunProgram(program)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("add constructor error:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Program(b *testing.B) {
|
||||||
|
var s = `
|
||||||
|
{
|
||||||
|
let u = new Url("https://goedge.cn/docs?v=1")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url()
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url("a", "b", "c")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let u = new Url("https://goedge.cn/docs?v=1")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let u = new Url("https://teaos.cn/downloads?v=1")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url()
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let u = new Url("a", "b", "c")
|
||||||
|
//console.log("host:", u.host(), u.uri())
|
||||||
|
}
|
||||||
|
`
|
||||||
|
program := goja.MustCompile("s", s, true)
|
||||||
|
|
||||||
|
vm := NewVM()
|
||||||
|
|
||||||
|
err := vm.AddConstructor("Url", &URL{})
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("add constructor error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
//_, err = vm.RunString(s)
|
||||||
|
_, err = vm.vm.RunProgram(program)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal("add constructor error:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user