diff --git a/cmd/rpc_saft_guard/main.go b/cmd/rpc_saft_guard/main.go new file mode 100644 index 0000000..6c57345 --- /dev/null +++ b/cmd/rpc_saft_guard/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "net/http" + "rpc_safe_guard_01/pkg/handler" + "rpc_safe_guard_01/pkg/otto" +) + +func main() { + otto.InitOtto() + + http.HandleFunc("/", handler.Handler) + err := http.ListenAndServe("localhost:8081", nil) + if err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..10cde65 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module rpc_safe_guard_01 + +go 1.20 + +require ( + github.com/robertkrimen/otto v0.2.1 + github.com/stretchr/testify v1.8.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/text v0.4.0 // indirect + gopkg.in/sourcemap.v1 v1.0.5 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3fb3f43 --- /dev/null +++ b/go.sum @@ -0,0 +1,23 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0= +github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/handler/excutor.go b/pkg/handler/excutor.go new file mode 100644 index 0000000..368ce5a --- /dev/null +++ b/pkg/handler/excutor.go @@ -0,0 +1,19 @@ +package handler + +import ( + "rpc_safe_guard_01/pkg/interpreter" + "rpc_safe_guard_01/pkg/okapi" +) + +func Execute(req okapi.Request) (string, error) { + vm := interpreter.GetInstance() + value, err := vm.Run(req.Body.Params["code"]) + if err != nil { + return "", err + } + data, err := value.ToString() + if err != nil { + return "", err + } + return data, nil +} diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go new file mode 100644 index 0000000..18c47f9 --- /dev/null +++ b/pkg/handler/handler.go @@ -0,0 +1,36 @@ +package handler + +import ( + "net/http" + "rpc_safe_guard_01/pkg/okapi" + "strings" +) + +func Handler(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + contentType := r.Header.Get("Content-Type") + if !strings.Contains(contentType, "application/json") { + http.Error(w, "Unsupported media type", http.StatusUnsupportedMediaType) + return + } + + var req okapi.Request + err := req.Decode(r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data, err := Execute(req) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Write([]byte(data)) +} diff --git a/pkg/interpreter/otto.go b/pkg/interpreter/otto.go new file mode 100644 index 0000000..e68ff6c --- /dev/null +++ b/pkg/interpreter/otto.go @@ -0,0 +1,16 @@ +package interpreter + +import ( + "github.com/robertkrimen/otto" + "sync" +) + +var instance *otto.Otto +var once sync.Once + +func GetInstance() *otto.Otto { + once.Do(func() { + instance = otto.New() + }) + return instance +} diff --git a/pkg/okapi/okapi.go b/pkg/okapi/okapi.go new file mode 100644 index 0000000..73fecac --- /dev/null +++ b/pkg/okapi/okapi.go @@ -0,0 +1,47 @@ +package okapi + +import ( + "bytes" + "encoding/json" + "io" + "net/http" +) + +type Request struct { + AppKey string + ApiName string + ApiVersion string + Body RequestBody +} + +type RequestBody struct { + OaSessionID string `json:"oa-session-id,omitempty"` + OaAppMarketID string `json:"oa-app-market-id,omitempty"` + OaAppVersion string `json:"oa-app-version,omitempty"` + OaDeviceID string `json:"oa-device-id,omitempty"` + OaSign string `json:"oa-sign,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +func (req *Request) Decode(r *http.Request) error { + reqBody, err := io.ReadAll(r.Body) + if err != nil { + return err + } + // Restore the io.ReadCloser to its original state + r.Body = io.NopCloser(bytes.NewBuffer(reqBody)) + if err = r.ParseForm(); err != nil { + return err + } + + req.AppKey = r.Form.Get("appkey") + req.ApiName = r.Form.Get("api") + req.ApiVersion = r.Form.Get("version") + err = json.Unmarshal(reqBody, &req.Body) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/okrpc/okrpc.go b/pkg/okrpc/okrpc.go new file mode 100644 index 0000000..e20d6c3 --- /dev/null +++ b/pkg/okrpc/okrpc.go @@ -0,0 +1,47 @@ +package okrpc + +import ( + "bytes" + "encoding/json" + "io" + "net/http" +) + +const ( + RpcRequestUrl = "http://127.0.0.1:8080" +) + +type Request struct { + Class string `json:"class"` + Method string `json:"method"` + Args []interface{} `json:"args"` +} + +type Response struct { + Success bool `json:"success"` + ErrorCode string `json:"errorCode,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` + Data interface{} `json:"data"` +} + +func (r Request) SendRpcRequest() (string, error) { + data, err := json.Marshal(r) + if err != nil { + return "", err + } + request, err := http.NewRequest("POST", RpcRequestUrl, bytes.NewBuffer(data)) + if err != nil { + return "", err + } + request.Header.Set("Content-Type", "application/json; charset=utf-8") + client := &http.Client{} + resp, err := client.Do(request) + if err != nil { + return "", err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} diff --git a/pkg/okrpc/okrpc_test.go b/pkg/okrpc/okrpc_test.go new file mode 100644 index 0000000..1b8783e --- /dev/null +++ b/pkg/okrpc/okrpc_test.go @@ -0,0 +1,40 @@ +package okrpc + +import ( + "encoding/json" + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSendRpcRequest(t *testing.T) { + request := Request{ + Class: "rpcLinker.SbxQitListService", + Method: "executeChain", + } + + request.Args = append(request.Args, "1") + arg0 := make(map[string]interface{}) + arg0["pagination"] = make(map[string]interface{}) + arg0["pagination"].(map[string]interface{})["current"] = 1 + arg0["pagination"].(map[string]interface{})["pageSize"] = 10 + arg0["pagination"].(map[string]interface{})["returnAll"] = false + request.Args = append(request.Args, arg0) + + resp, err := request.SendRpcRequest() + assert.Nil(t, err) + t.Log(resp) +} + +func TestUnmarshal(t *testing.T) { + str := `[{'pagination':{'returnAll':false,'current':1,'pageSize':10}},1]` + + var data []interface{} + err := json.Unmarshal([]byte(str), &data) + if err != nil { + fmt.Println("JSON unmarshaling failed:", err) + return + } + + fmt.Printf("%#v\n", data) +} diff --git a/pkg/otto/otto.go b/pkg/otto/otto.go new file mode 100644 index 0000000..2951f26 --- /dev/null +++ b/pkg/otto/otto.go @@ -0,0 +1,73 @@ +package otto + +import ( + "encoding/json" + "errors" + "github.com/robertkrimen/otto" + "rpc_safe_guard_01/pkg/interpreter" + "rpc_safe_guard_01/pkg/okrpc" +) + +const ( + RpcServiceName = "rpcRequest" + RpcName = "gw" + ExitName = "exit" +) + +var ErrorCodeMap = map[int]string{ + 10001: "rpc request error", +} + +func InitOtto() { + // 注册通用方法到otto里 + instance := interpreter.GetInstance() + err := instance.Set(RpcName, map[string]interface{}{ + RpcServiceName: call, + ExitName: exit, + }) + if err != nil { + panic(err) + } +} + +func call(call otto.FunctionCall) otto.Value { + if len(call.ArgumentList) != 2 { + value, _ := otto.ToValue(errors.New("argument invalid")) + return value + } + rpcName := call.Argument(0) + param := call.Argument(1) + p, err := param.ToString() + if err != nil { + value, _ := otto.ToValue(err) + return value + } + var args []interface{} + err = json.Unmarshal([]byte(p), &args) + if err != nil { + value, _ := otto.ToValue(err) + return value + } + var req okrpc.Request + req.Class = rpcName.String() + req.Method = "executeChain" + req.Args = args + resp, err := req.SendRpcRequest() + if err != nil { + value, _ := otto.ToValue(err) + return value + } + value, _ := otto.ToValue(resp) + return value +} + +func exit(call otto.FunctionCall) otto.Value { + code, err := call.Argument(0).ToInteger() + if err != nil { + value, _ := otto.ToValue(errors.New("the code is not number")) + return value + } + // 此处传入Api Error Code,先用map代替 + value, _ := otto.ToValue(ErrorCodeMap[int(code)]) + return value +} diff --git a/question.md b/question.md new file mode 100644 index 0000000..d11ea4e --- /dev/null +++ b/question.md @@ -0,0 +1,44 @@ +## 1. 简化思路 + +例如: +```javascript +// 1. 检查用户名是否已经注册 +hasUser = rpc.call("com.swallow.user.checkUserName", user.name) + +if (hasUser != null) { + rpc.error("code", "message") +} +// 2. 检查手机号是否已经注册 +hasPhone = rpc.call("com.swallow.user.checkPhoneNumber", user.phoneNumber) + +if (hasPhone != null) { + rpc.error("code", "message") +} + +// 3. 创建用户 +create = rpc.call("com.swallow.user.create", user) + +if (create) { + rpc.success({ "data": create }) +} +``` +目前的简化思路就是我在后台只注册rpc参数,并给rpc注册call/error等方法,配置文件全部弃掉 + +## 2、call函数使用 + +- 第一个参数是要调用的rpc名字,可以来自于rpc linker也可以来自于java service +- 第二个参数是写成类似于这样的来接受0个或多个参数的请求体,请求体将会传递给rpc方法的请求参数 +```javascript +arg = { + "name": "test", + "age": 12, + "other": "ssss" +} +rpc.call("com.swallow.user.create", arg) +``` + +## 3、条件限定 +- go后台拿到rpc响应后是不是还需要根据条件限定,对响应进行校验 +- 例如HTML或者APP传递条件给我(username != null),我需要根据这个条件加个判断再返回响应给js。 + +## 4、前台传递的条件是单个还是多个? \ No newline at end of file diff --git a/test/rpcLinker.SbxQitListService.http b/test/rpcLinker.SbxQitListService.http new file mode 100644 index 0000000..0b82bec --- /dev/null +++ b/test/rpcLinker.SbxQitListService.http @@ -0,0 +1,14 @@ +POST http://localhost:8081/okapi3?api=rpcLinker.SbxOrderSearchService&version=1.0&appkey=c94ec1fc-58c1-41c6-9980-ead078ae2c97 +Content-Type: application/json + +{ + "oa-session-id": "7E2532FF3A72D64A91B9810EBBDF2441", + "oa-app-market-id": "678", + "oa-app-version": "1.0", + "oa-device-id": "1911151415", + "oa-sign": "1a6f78424a4874bdfb3ee716c372e547", + "timestamp": "1686919817370370", + "params": { + "code": "gw.rpcRequest('rpcLinker.SbxQitListService', '[{\"pagination\":{\"returnAll\":false,\"current\":1,\"pageSize\":10}},1]')" + } +} \ No newline at end of file