diff --git a/internal/js/.gitignore b/internal/js/.gitignore new file mode 100644 index 0000000..472fecd --- /dev/null +++ b/internal/js/.gitignore @@ -0,0 +1 @@ +*.go \ No newline at end of file diff --git a/internal/js/console.go b/internal/js/console.go deleted file mode 100644 index e44ed03..0000000 --- a/internal/js/console.go +++ /dev/null @@ -1,50 +0,0 @@ -// 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) -} diff --git a/internal/js/console_test.go b/internal/js/console_test.go deleted file mode 100644 index d2fadb6..0000000 --- a/internal/js/console_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// 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) - } - } -} diff --git a/internal/js/http.go b/internal/js/http.go deleted file mode 100644 index b0464ae..0000000 --- a/internal/js/http.go +++ /dev/null @@ -1,36 +0,0 @@ -// 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) -} diff --git a/internal/js/request.go b/internal/js/request.go deleted file mode 100644 index aae2399..0000000 --- a/internal/js/request.go +++ /dev/null @@ -1,82 +0,0 @@ -// 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 -} diff --git a/internal/js/request_interface.go b/internal/js/request_interface.go deleted file mode 100644 index 6f31242..0000000 --- a/internal/js/request_interface.go +++ /dev/null @@ -1,19 +0,0 @@ -// 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{}) -} diff --git a/internal/js/request_test.go b/internal/js/request_test.go deleted file mode 100644 index 116fe88..0000000 --- a/internal/js/request_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// 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())) -} diff --git a/internal/js/response.go b/internal/js/response.go deleted file mode 100644 index ad3d4cf..0000000 --- a/internal/js/response.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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) -} diff --git a/internal/js/response_test.go b/internal/js/response_test.go deleted file mode 100644 index 1f41f1e..0000000 --- a/internal/js/response_test.go +++ /dev/null @@ -1,16 +0,0 @@ -// 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()) -} diff --git a/internal/js/url.go b/internal/js/url.go deleted file mode 100644 index 3dc6a24..0000000 --- a/internal/js/url.go +++ /dev/null @@ -1,90 +0,0 @@ -// 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() -} diff --git a/internal/js/url_test.go b/internal/js/url_test.go deleted file mode 100644 index def511a..0000000 --- a/internal/js/url_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// 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()) -} diff --git a/internal/js/vm.go b/internal/js/vm.go deleted file mode 100644 index b1d2715..0000000 --- a/internal/js/vm.go +++ /dev/null @@ -1,153 +0,0 @@ -// 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()) -} diff --git a/internal/js/vm_test.go b/internal/js/vm_test.go deleted file mode 100644 index 5024875..0000000 --- a/internal/js/vm_test.go +++ /dev/null @@ -1,158 +0,0 @@ -// 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()) - } - } -}