From 3c21cba9c1eca96e416256be1410be9bec252a8c Mon Sep 17 00:00:00 2001 From: Umputun Date: Wed, 31 Jul 2019 18:38:53 -0500 Subject: [PATCH] switch core to go-pkgz/jrpc --- backend/app/rpc/client_test.go | 97 ------- backend/app/rpc/remote.go | 23 -- backend/app/rpc/server_test.go | 245 ------------------ backend/app/store/engine/remote.go | 5 +- backend/app/store/engine/remote_test.go | 30 +-- backend/go.mod | 5 +- backend/go.sum | 7 + .../github.com/didip/tollbooth/README.md | 8 +- .../github.com/didip/tollbooth/tollbooth.go | 32 ++- .../vendor/github.com/go-pkgz/jrpc/.gitignore | 12 + .../github.com/go-pkgz/jrpc/.golangci.yml | 70 +++++ .../github.com/go-pkgz/jrpc/.travis.yml | 21 ++ .../vendor/github.com/go-pkgz/jrpc/LICENSE | 21 ++ .../vendor/github.com/go-pkgz/jrpc/README.md | 99 +++++++ .../github.com/go-pkgz/jrpc}/client.go | 21 +- backend/vendor/github.com/go-pkgz/jrpc/go.mod | 16 ++ backend/vendor/github.com/go-pkgz/jrpc/go.sum | 32 +++ .../vendor/github.com/go-pkgz/jrpc/jrpc.go | 40 +++ .../github.com/go-pkgz/jrpc}/server.go | 82 +++--- backend/vendor/modules.txt | 6 +- 20 files changed, 420 insertions(+), 452 deletions(-) delete mode 100644 backend/app/rpc/client_test.go delete mode 100644 backend/app/rpc/remote.go delete mode 100644 backend/app/rpc/server_test.go create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/.gitignore create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/.travis.yml create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/LICENSE create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/README.md rename backend/{app/rpc => vendor/github.com/go-pkgz/jrpc}/client.go (65%) create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/go.mod create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/go.sum create mode 100644 backend/vendor/github.com/go-pkgz/jrpc/jrpc.go rename backend/{app/rpc => vendor/github.com/go-pkgz/jrpc}/server.go (58%) diff --git a/backend/app/rpc/client_test.go b/backend/app/rpc/client_test.go deleted file mode 100644 index 12b718ce..00000000 --- a/backend/app/rpc/client_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package rpc - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestClient_Call(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - resp, err := c.Call("test", 123, "abc") - assert.NoError(t, err) - res := "" - err = json.Unmarshal(*resp.Result, &res) - assert.NoError(t, err) - assert.Equal(t, "12345", res) - t.Logf("%v %T", res, res) -} - -func TestClient_CallWithObject(t *testing.T) { - ts := testServer(t, `{"method":"test","params":{"F1":123,"F2":"abc","F3":"2019-06-09T23:03:55Z"},"id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - obj := struct { - F1 int - F2 string - F3 time.Time - }{ - F1: 123, - F2: "abc", - F3: time.Date(2019, 6, 9, 23, 3, 55, 0, time.UTC), - } - - resp, err := c.Call("test", obj) - assert.NoError(t, err) - res := "" - err = json.Unmarshal(*resp.Result, &res) - assert.NoError(t, err) - assert.Equal(t, "12345", res) - t.Logf("%v %T", res, res) -} - -func TestClient_CallWithNoParams(t *testing.T) { - ts := testServer(t, `{"method":"test","id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - resp, err := c.Call("test") - assert.NoError(t, err) - res := "" - err = json.Unmarshal(*resp.Result, &res) - assert.NoError(t, err) - assert.Equal(t, "12345", res) - t.Logf("%v %T", res, res) -} - -func TestClient_CallError(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"error":"some error"}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - _, err := c.Call("test", 123, "abc") - assert.EqualError(t, err, "some error") -} - -func TestClient_CallBadResponse(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"result":"12345 invalid}`) - defer ts.Close() - c := Client{API: ts.URL, Client: http.Client{}} - _, err := c.Call("test", 123, "abc") - assert.NotNil(t, err) -} - -func TestClient_CallBadRemote(t *testing.T) { - ts := testServer(t, `{"method":"test","params":[123,"abc"],"id":1}`, `{"result":"12345"}`) - defer ts.Close() - c := Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}} - _, err := c.Call("test", 123) - assert.NotNil(t, err) -} - -func testServer(t *testing.T, req, resp string) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - require.NoError(t, err) - assert.Equal(t, req, string(body)) - t.Logf("req: %s", string(body)) - fmt.Fprintf(w, resp) - })) -} diff --git a/backend/app/rpc/remote.go b/backend/app/rpc/remote.go deleted file mode 100644 index 48d8ac50..00000000 --- a/backend/app/rpc/remote.go +++ /dev/null @@ -1,23 +0,0 @@ -// Package rpc implements client ans server for RPC-like communication with remote storage. -// The protocol is somewhat simplified version of json-rpc with a single POST call sending -// Request json (method name and the list of parameters) and receiving back json Response with "result" json -// and error string -package rpc - -import ( - "encoding/json" -) - -// Request encloses method name and all params -type Request struct { - Method string `json:"method"` - Params interface{} `json:"params,omitempty"` - ID uint64 `json:"id"` -} - -// Response encloses result and error received from remote server -type Response struct { - Result *json.RawMessage `json:"result,omitempty"` - Error string `json:"error,omitempty"` - ID uint64 `json:"id"` -} diff --git a/backend/app/rpc/server_test.go b/backend/app/rpc/server_test.go deleted file mode 100644 index c39fe51a..00000000 --- a/backend/app/rpc/server_test.go +++ /dev/null @@ -1,245 +0,0 @@ -package rpc - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestServerPrimitiveTypes(t *testing.T) { - s := Server{API: "/v1/cmd"} - - type respData struct { - Res1 string - Res2 bool - } - - s.Add("test", func(id uint64, params json.RawMessage) Response { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", args) - - assert.Equal(t, 3, len(args)) - assert.Equal(t, "blah", args[0].(string)) - assert.Equal(t, 42., args[1].(float64)) - assert.Equal(t, true, args[2].(bool)) - - r, err := s.EncodeResponse(id, respData{"res blah", true}, nil) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - // check with direct http call - clientReq := Request{Method: "test", Params: []interface{}{"blah", 42, true}, ID: 123} - b := bytes.Buffer{} - require.NoError(t, json.NewEncoder(&b).Encode(clientReq)) - resp, err := http.Post("http://127.0.0.1:9091/v1/cmd", "application/json", &b) - require.NoError(t, err) - defer resp.Body.Close() - assert.Equal(t, 200, resp.StatusCode) - data, err := ioutil.ReadAll(resp.Body) - assert.NoError(t, err) - assert.Equal(t, `{"result":{"Res1":"res blah","Res2":true},"id":123}`+"\n", string(data)) - - // check with client call - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - r, err := c.Call("test", "blah", 42, true) - assert.NoError(t, err) - assert.Equal(t, "", r.Error) - - res := respData{} - err = json.Unmarshal(*r.Result, &res) - assert.NoError(t, err) - assert.Equal(t, respData{Res1: "res blah", Res2: true}, res) - assert.Equal(t, uint64(1), r.ID) -} - -func TestServerWithObject(t *testing.T) { - s := Server{API: "/v1/cmd"} - - type respData struct { - Res1 string - Res2 bool - } - - type reqData struct { - Time time.Time - F1 string - F2 time.Duration - } - - s.Add("test", func(id uint64, params json.RawMessage) Response { - arg := reqData{} - if err := json.Unmarshal(params, &arg); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", arg) - - r, err := s.EncodeResponse(id, respData{"res blah", true}, nil) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - r, err := c.Call("test", reqData{Time: time.Now(), F1: "sawert", F2: time.Minute}) - assert.NoError(t, err) - assert.Equal(t, "", r.Error) - - res := respData{} - err = json.Unmarshal(*r.Result, &res) - assert.NoError(t, err) - assert.Equal(t, respData{Res1: "res blah", Res2: true}, res) -} - -func TestServerMethodNotImplemented(t *testing.T) { - s := Server{} - ts := httptest.NewServer(http.HandlerFunc(s.handler)) - defer ts.Close() - s.Add("test", func(id uint64, params json.RawMessage) Response { - return Response{} - }) - - r := Request{Method: "blah"} - buf := bytes.Buffer{} - assert.NoError(t, json.NewEncoder(&buf).Encode(r)) - resp, err := http.Post(ts.URL, "application/json", &buf) - require.NoError(t, err) - assert.Equal(t, http.StatusNotImplemented, resp.StatusCode) - - assert.EqualError(t, s.Shutdown(), "http server is not running") -} - -func TestServerWithAuth(t *testing.T) { - s := Server{API: "/v1/cmd", AuthUser: "user", AuthPasswd: "passwd"} - - s.Add("test", func(id uint64, params json.RawMessage) Response { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", args) - - assert.Equal(t, 3, len(args)) - assert.Equal(t, "blah", args[0].(string)) - assert.Equal(t, 42., args[1].(float64)) - assert.Equal(t, true, args[2].(bool)) - - r, err := s.EncodeResponse(id, "res blah", nil) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - time.Sleep(10 * time.Millisecond) - defer func() { assert.NoError(t, s.Shutdown()) }() - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}, AuthUser: "user", AuthPasswd: "passwd"} - r, err := c.Call("test", "blah", 42, true) - assert.NoError(t, err) - assert.Equal(t, "", r.Error) - val := "" - err = json.Unmarshal(*r.Result, &val) - assert.NoError(t, err) - assert.Equal(t, "res blah", val) - - c = Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - _, err = c.Call("test", "blah", 42, true) - assert.EqualError(t, err, "bad status 401 Unauthorized for test") -} - -func TestServerErrReturn(t *testing.T) { - s := Server{API: "/v1/cmd", AuthUser: "user", AuthPasswd: "passwd"} - - s.Add("test", func(id uint64, params json.RawMessage) Response { - args := []interface{}{} - if err := json.Unmarshal(params, &args); err != nil { - return Response{Error: err.Error()} - } - t.Logf("%+v", args) - - assert.Equal(t, 3, len(args)) - assert.Equal(t, "blah", args[0].(string)) - assert.Equal(t, 42., args[1].(float64)) - assert.Equal(t, true, args[2].(bool)) - - r, err := s.EncodeResponse(id, "res blah", errors.New("some error")) - assert.NoError(t, err) - return r - }) - - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}, AuthUser: "user", AuthPasswd: "passwd"} - _, err := c.Call("test", "blah", 42, true) - assert.EqualError(t, err, "some error") -} - -func TestServerGroup(t *testing.T) { - s := Server{API: "/v1/cmd"} - s.Group("pre", HandlersGroup{ - "fn1": func(id uint64, params json.RawMessage) Response { - return Response{} - }, - "fn2": func(id uint64, params json.RawMessage) Response { - return Response{} - }, - }) - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - _, err := c.Call("fn1") - assert.EqualError(t, err, "bad status 501 Not Implemented for fn1") - - _, err = c.Call("pre.fn1") - assert.NoError(t, err) - _, err = c.Call("pre.fn2") - assert.NoError(t, err) -} - -func TestServerAddLate(t *testing.T) { - s := Server{API: "/v1/cmd"} - s.Add("fn1", func(id uint64, params json.RawMessage) Response { - return Response{} - }) - go func() { _ = s.Run(9091) }() - defer func() { assert.NoError(t, s.Shutdown()) }() - time.Sleep(10 * time.Millisecond) - - // too late, ignored after run - s.Add("fn2", func(id uint64, params json.RawMessage) Response { - return Response{} - }) - - c := Client{API: "http://127.0.0.1:9091/v1/cmd", Client: http.Client{}} - _, err := c.Call("fn1") - assert.NoError(t, err) - _, err = c.Call("fn2") - assert.EqualError(t, err, "bad status 501 Not Implemented for fn2") -} - -func TestServerNoHandlers(t *testing.T) { - s := Server{API: "/v1/cmd", AuthUser: "user", AuthPasswd: "passwd"} - assert.EqualError(t, s.Run(9091), "nothing mapped for dispatch, Add has to be called prior to Run") -} diff --git a/backend/app/store/engine/remote.go b/backend/app/store/engine/remote.go index bb084295..186ff9f0 100644 --- a/backend/app/store/engine/remote.go +++ b/backend/app/store/engine/remote.go @@ -3,13 +3,14 @@ package engine import ( "encoding/json" - "github.com/umputun/remark/backend/app/rpc" + "github.com/go-pkgz/jrpc" + "github.com/umputun/remark/backend/app/store" ) // RPC implements remote engine and delegates all Calls to remote http server type RPC struct { - rpc.Client + jrpc.Client } // Create comment and return ID diff --git a/backend/app/store/engine/remote_test.go b/backend/app/store/engine/remote_test.go index 160f6d29..b97b637c 100644 --- a/backend/app/store/engine/remote_test.go +++ b/backend/app/store/engine/remote_test.go @@ -9,10 +9,10 @@ import ( "testing" "time" + "github.com/go-pkgz/jrpc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/umputun/remark/backend/app/rpc" "github.com/umputun/remark/backend/app/store" ) @@ -20,7 +20,7 @@ func TestRemote_Create(t *testing.T) { ts := testServer(t, `{"method":"store.create","params":{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"},"id":1}`, `{"result":"12345","id":1}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} var eng Interface = &c _ = eng @@ -35,7 +35,7 @@ func TestRemote_Create(t *testing.T) { func TestRemote_Get(t *testing.T) { ts := testServer(t, `{"method":"store.get","params":{"locator":{"url":"http://example.com/url"},"comment_id":"site"},"id":1}`, `{"result":{"id":"123","pid":"","text":"msg","delete":true}}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} res, err := c.Get(req) @@ -47,7 +47,7 @@ func TestRemote_Get(t *testing.T) { func TestRemote_GetWithErrorResult(t *testing.T) { ts := testServer(t, `{"method":"store.get","params":{"locator":{"url":"http://example.com/url"},"comment_id":"site"},"id":1}`, `{"error":"failed"}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -57,7 +57,7 @@ func TestRemote_GetWithErrorResult(t *testing.T) { func TestRemote_GetWithErrorDecode(t *testing.T) { ts := testServer(t, `{"method":"store.get","params":{"locator":{"url":"http://example.com/url"},"comment_id":"site"},"id":1}`, ``) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -65,7 +65,7 @@ func TestRemote_GetWithErrorDecode(t *testing.T) { } func TestRemote_GetWithErrorRemote(t *testing.T) { - c := RPC{Client: rpc.Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}}} + c := RPC{Client: jrpc.Client{API: "http://127.0.0.2", Client: http.Client{Timeout: 10 * time.Millisecond}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -81,7 +81,7 @@ func TestRemote_FailedStatus(t *testing.T) { w.WriteHeader(400) })) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} req := GetRequest{Locator: store.Locator{URL: "http://example.com/url"}, CommentID: "site"} _, err := c.Get(req) @@ -91,7 +91,7 @@ func TestRemote_FailedStatus(t *testing.T) { func TestRemote_Update(t *testing.T) { ts := testServer(t, `{"method":"store.update","params":{"id":"123","pid":"","text":"msg","user":{"name":"","id":"","picture":"","admin":false},"locator":{"site":"site123","url":"http://example.com/url"},"score":0,"vote":0,"time":"0001-01-01T00:00:00Z"},"id":1}`, `{}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} err := c.Update(store.Comment{ID: "123", Locator: store.Locator{URL: "http://example.com/url", SiteID: "site123"}, Text: "msg"}) @@ -102,7 +102,7 @@ func TestRemote_Update(t *testing.T) { func TestRemote_Find(t *testing.T) { ts := testServer(t, `{"method":"store.find","params":{"locator":{"url":"http://example.com/url"},"sort":"-time","since":"0001-01-01T00:00:00Z","limit":10},"id":1}`, `{"result":[{"text":"1"},{"text":"2"}]}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Find(FindRequest{Locator: store.Locator{URL: "http://example.com/url"}, Sort: "-time", Limit: 10}) assert.NoError(t, err) @@ -112,7 +112,7 @@ func TestRemote_Find(t *testing.T) { func TestRemote_Info(t *testing.T) { ts := testServer(t, `{"method":"store.info","params":{"locator":{"url":"http://example.com/url"},"limit":10,"skip":5,"ro_age":10},"id":1}`, `{"result":[{"url":"u1","count":22},{"url":"u2","count":33}]}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Info(InfoRequest{Locator: store.Locator{URL: "http://example.com/url"}, Limit: 10, Skip: 5, ReadOnlyAge: 10}) @@ -123,7 +123,7 @@ func TestRemote_Info(t *testing.T) { func TestRemote_Flag(t *testing.T) { ts := testServer(t, `{"method":"store.flag","params":{"flag":"verified","locator":{"url":"http://example.com/url"}},"id":1}`, `{"result":false}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Flag(FlagRequest{Locator: store.Locator{URL: "http://example.com/url"}, Flag: Verified}) assert.NoError(t, err) @@ -133,7 +133,7 @@ func TestRemote_Flag(t *testing.T) { func TestRemote_ListFlag(t *testing.T) { ts := testServer(t, `{"method":"store.list_flags","params":{"flag":"blocked","locator":{"site":"site_id","url":""}},"id":1}`, `{"result":[{"ID":"id1"},{"ID":"id2"}]}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.ListFlags(FlagRequest{Locator: store.Locator{SiteID: "site_id"}, Flag: Blocked}) assert.NoError(t, err) assert.Equal(t, []interface{}{map[string]interface{}{"ID": "id1"}, map[string]interface{}{"ID": "id2"}}, res) @@ -142,7 +142,7 @@ func TestRemote_ListFlag(t *testing.T) { func TestRemote_Count(t *testing.T) { ts := testServer(t, `{"method":"store.count","params":{"locator":{"url":"http://example.com/url"},"since":"0001-01-01T00:00:00Z"},"id":1}`, `{"result":11}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} res, err := c.Count(FindRequest{Locator: store.Locator{URL: "http://example.com/url"}}) assert.NoError(t, err) @@ -153,7 +153,7 @@ func TestRemote_Delete(t *testing.T) { ts := testServer(t, `{"method":"store.delete","params":{"locator":{"url":"http://example.com/url"},"del_mode":0},"id":1}`, `{}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} err := c.Delete(DeleteRequest{Locator: store.Locator{URL: "http://example.com/url"}}) assert.NoError(t, err) @@ -162,7 +162,7 @@ func TestRemote_Delete(t *testing.T) { func TestRemote_Close(t *testing.T) { ts := testServer(t, `{"method":"store.close","id":1}`, `{}`) defer ts.Close() - c := RPC{Client: rpc.Client{API: ts.URL, Client: http.Client{}}} + c := RPC{Client: jrpc.Client{API: ts.URL, Client: http.Client{}}} err := c.Close() assert.NoError(t, err) } diff --git a/backend/go.mod b/backend/go.mod index f8fdac22..4b78ea1f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -9,12 +9,13 @@ require ( github.com/PuerkitoBio/goquery v1.5.0 github.com/coreos/bbolt v1.3.3 github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/didip/tollbooth v4.0.0+incompatible + github.com/didip/tollbooth v4.0.2+incompatible github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/cors v1.0.0 github.com/go-chi/render v1.0.1 github.com/go-pkgz/auth v0.7.2 + github.com/go-pkgz/jrpc v0.1.0 github.com/go-pkgz/lcw v0.3.1 github.com/go-pkgz/lgr v0.6.3 github.com/go-pkgz/repeater v1.1.2 @@ -35,7 +36,7 @@ require ( github.com/stretchr/testify v1.3.0 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 - golang.org/x/net v0.0.0-20190628185345-da137c7871d7 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect gopkg.in/russross/blackfriday.v2 v2.0.1 ) diff --git a/backend/go.sum b/backend/go.sum index 56a7f941..2936f18e 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -21,6 +21,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/didip/tollbooth v4.0.0+incompatible h1:ayQZYuF5QOxx3NdYRNuRVFLv9/2b64JtSUlewb+0TMo= github.com/didip/tollbooth v4.0.0+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= +github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= +github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d h1:vs5Nf6IE0N/PwGJ8//zRed4gpCdcr99K2HzX7RuLOQ8= github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M= github.com/globalsign/mgo v0.0.0-20180615134936-113d3961e731/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -34,6 +36,8 @@ github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= github.com/go-pkgz/auth v0.7.2 h1:+LvAgqwQtYuWphpZE8qLtspVd65+VgreJkFLsKrtmmk= github.com/go-pkgz/auth v0.7.2/go.mod h1:ibOpZYISiaOvAHe2bsKj2s3v4AkMam2WxxIFn+zhulo= +github.com/go-pkgz/jrpc v0.1.0 h1:hNg/IyfEqJcSWOKkuHw0ZwcuGc9TDp7QZREsD2ycmiM= +github.com/go-pkgz/jrpc v0.1.0/go.mod h1:JxZsvoBklA50DNhELVJnJ567Rt+KrMH9rR3u515wvE8= github.com/go-pkgz/lcw v0.3.1 h1:PhfB0xNUawLMlx5rXvOTIc7d5LMrr1GM9vIzmG96aUI= github.com/go-pkgz/lcw v0.3.1/go.mod h1:k+PY1CkCMTLXILtFoJOyK65Qqi9rkoTYunFH1vE/C0I= github.com/go-pkgz/lgr v0.2.2/go.mod h1:hBM1NM/SoYdlrykgdgJWGrZ/TM/XaZIjRbJfx7NkMm8= @@ -115,6 +119,7 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/umputun/remark v1.4.0 h1:rJf4ndpvRDS7tOrtIpVkacItTxF5pVpXvDqtb1dX40M= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -153,6 +158,8 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= diff --git a/backend/vendor/github.com/didip/tollbooth/README.md b/backend/vendor/github.com/didip/tollbooth/README.md index d1971b84..f6145879 100644 --- a/backend/vendor/github.com/didip/tollbooth/README.md +++ b/backend/vendor/github.com/didip/tollbooth/README.md @@ -7,9 +7,7 @@ This is a generic middleware to rate-limit HTTP requests. **NOTE 1:** This library is considered finished. -**NOTE 2:** In the coming weeks, I will be removing thirdparty modules and moving them to their own dedicated repos. - -**NOTE 3:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API. +**NOTE 2:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API. ## Versions @@ -21,7 +19,7 @@ This is a generic middleware to rate-limit HTTP requests. **v3.x.x:** Apparently we have been using golang.org/x/time/rate incorrectly. See issue #48. It always limit X number per 1 second. The time duration is not changeable, so it does not make sense to pass TTL to tollbooth. -## Five Minutes Tutorial +## Five Minute Tutorial ```go package main @@ -161,4 +159,6 @@ Sometimes, other frameworks require a little bit of shim to use Tollbooth. These * [Stopwatch](https://github.com/didip/stopwatch): A small library to measure latency of things. Useful if you want to report latency data to Graphite. +* [LaborUnion](https://github.com/didip/laborunion): A dynamic worker pool library. + * [Gomet](https://github.com/didip/gomet): Simple HTTP client & server long poll library for Go. Useful for receiving live updates without needing Websocket. diff --git a/backend/vendor/github.com/didip/tollbooth/tollbooth.go b/backend/vendor/github.com/didip/tollbooth/tollbooth.go index c023db26..b4f7b3e8 100644 --- a/backend/vendor/github.com/didip/tollbooth/tollbooth.go +++ b/backend/vendor/github.com/didip/tollbooth/tollbooth.go @@ -6,10 +6,11 @@ import ( "strings" "fmt" + "math" + "github.com/didip/tollbooth/errors" "github.com/didip/tollbooth/libstring" "github.com/didip/tollbooth/limiter" - "math" ) // setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration @@ -58,18 +59,21 @@ func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string { if libstring.StringInSlice(lmtMethods, r.Method) { for headerKey, headerValues := range lmtHeaders { if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" { - // If header values are empty, rate-limit all request with headerKey. + // If header values are empty, rate-limit all request containing headerKey. username, _, ok := r.BasicAuth() if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) { - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, username}) + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, r.Header.Get(headerKey), username}) } } else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" { // If header values are not empty, rate-limit all request with headerKey and headerValues. for _, headerValue := range headerValues { - username, _, ok := r.BasicAuth() - if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) { - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username}) + if r.Header.Get(headerKey) == headerValue { + username, _, ok := r.BasicAuth() + if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) { + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username}) + } + break } } } @@ -82,12 +86,15 @@ func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string { for headerKey, headerValues := range lmtHeaders { if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" { // If header values are empty, rate-limit all request with headerKey. - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey}) + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, r.Header.Get(headerKey)}) } else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" { - // If header values are not empty, rate-limit all request with headerKey and headerValues. + // We are only limiting if request's header value is defined inside `headerValues`. for _, headerValue := range headerValues { - sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue}) + if r.Header.Get(headerKey) == headerValue { + sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue}) + break + } } } } @@ -113,12 +120,15 @@ func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string { for headerKey, headerValues := range lmtHeaders { if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" { // If header values are empty, rate-limit all request with headerKey. - sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey}) + sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, r.Header.Get(headerKey)}) } else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" { // If header values are not empty, rate-limit all request with headerKey and headerValues. for _, headerValue := range headerValues { - sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue}) + if r.Header.Get(headerKey) == headerValue { + sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue}) + break + } } } } diff --git a/backend/vendor/github.com/go-pkgz/jrpc/.gitignore b/backend/vendor/github.com/go-pkgz/jrpc/.gitignore new file mode 100644 index 00000000..f1c181ec --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/.gitignore @@ -0,0 +1,12 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml b/backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml new file mode 100644 index 00000000..bb83e9ae --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/.golangci.yml @@ -0,0 +1,70 @@ +linters-settings: + govet: + check-shadowing: true + golint: + min-confidence: 0 + gocyclo: + min-complexity: 15 + maligned: + suggest-new: true + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 2 + misspell: + locale: US + lll: + line-length: 140 + gocritic: + enabled-tags: + - performance + - style + - experimental + disabled-checks: + - wrapperFunc + +linters: + disable-all: true + enable: + - megacheck + - golint + - govet + - unconvert + - megacheck + - structcheck + - gas + - gocyclo + - dupl + - misspell + - unparam + - varcheck + - deadcode + - typecheck + - ineffassign + - varcheck + - stylecheck + - gochecknoinits + - scopelint + - gocritic + - golint + - nakedret + - gosimple + - prealloc + fast: false + + +run: +# modules-download-mode: vendor + skip-dirs: + - vendor + tests: true + +issues: + exclude-rules: + - text: "weak cryptographic primitive" + linters: + - gosec + +service: + golangci-lint-version: 1.17.x \ No newline at end of file diff --git a/backend/vendor/github.com/go-pkgz/jrpc/.travis.yml b/backend/vendor/github.com/go-pkgz/jrpc/.travis.yml new file mode 100644 index 00000000..0f0c5bdc --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/.travis.yml @@ -0,0 +1,21 @@ +language: go + +go: + - "1.12.x" + +install: true + +before_install: + - export TZ=America/Chicago + - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1 + - go get github.com/mattn/goveralls + - export PATH=$(pwd)/bin:$PATH + +script: + - GO111MODULE=on go get ./... + - GO111MODULE=on go mod vendor + - GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1; + - GO111MODULE=on go test -v -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1; + - golangci-lint run -v || travis_terminate 1; + - $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci + diff --git a/backend/vendor/github.com/go-pkgz/jrpc/LICENSE b/backend/vendor/github.com/go-pkgz/jrpc/LICENSE new file mode 100644 index 00000000..ac540250 --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Umputun + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/vendor/github.com/go-pkgz/jrpc/README.md b/backend/vendor/github.com/go-pkgz/jrpc/README.md new file mode 100644 index 00000000..a3e36c9b --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/README.md @@ -0,0 +1,99 @@ +# jrpc - rpc with json [![Build Status](https://travis-ci.org/go-pkgz/jrpc.svg?branch=master)](https://travis-ci.org/go-pkgz/jrpc) [![Go Report Card](https://goreportcard.com/badge/github.com/go-pkgz/jrpc)](https://goreportcard.com/report/github.com/go-pkgz/jrpc) [![Coverage Status](https://coveralls.io/repos/github/go-pkgz/jrpc/badge.svg?branch=master)](https://coveralls.io/github/go-pkgz/jrpc?branch=master) [![godoc](https://godoc.org/github.com/go-pkgz/jrpc?status.svg)](https://godoc.org/github.com/go-pkgz/jrpc) + +jrpc library provides client and server for RPC-like communication over HTTP with json encoded messages. +The protocol is a somewhat simplified version of json-rpc with a single POST call sending Request json +(method name and the list of parameters) moreover, receiving json Response with result data and an error string. + +## Usage + +### Plugin (server) + +```go +// Server wraps jrpc.Server and adds synced map to store data +type Puglin struct { + *jrpc.Server +} + +// create plugin (jrpc server) +plugin := jrpcServer{ + Server: &jrpc.Server{ + API: "/command", // base url for rpc calls + AuthUser: "user", // basic auth user name + AuthPasswd: "password", // basic auth password + AppName: "jrpc-example", // plugin name for headers + Logger: logger, + }, +} + +plugin.Add("mycommand", func(id uint64, params json.RawMessage) Response { + return jrpc.EncodeResponse(id, "hello, it works", nil) +}) +``` + +### Application (client) + +```go +// Client makes jrpc.Client and invoke remote call +rpcClient := jrpc.Client{ + API: "http://127.0.0.1:8080/command", + Client: http.Client{}, + AuthUser: "user", + AuthPasswd: "password", +} + +resp, err := rpcClient.Call("mycommand") +var message string +if err = json.Unmarshal(*resp.Result, &message); err != nil { + panic(err) +} +``` + +*for functional examples for both plugin and application see [_example](https://github.com/go-pkgz/jrpc/tree/master/_example)* + +## Technical details + + * `jrpc.Server` runs on user-defined port as a regular http server + * Server accepts a single POST request on user-defined url with [Request](https://github.com/go-pkgz/jrpc/blob/master/jrpc.go#L12) sent as json payload +
request details and an example: + + ```go + type Request struct { + Method string `json:"method"` + Params interface{} `json:"params,omitempty"` + ID uint64 `json:"id"` + } + ``` + example: + + ```json + { + "method":"test", + "params":[123,"abc"], + "id":1 + } + ``` +
+* Params can be a struct, primitive type or slice of values, even with different types. +* Server defines `ServerFn` handler function to react on a POST request. The handler provided by the user. +* Communication between the server and the caller can be protected with basic auth. +* [Client](https://github.com/go-pkgz/jrpc/blob/master/client.go) provides a single method `Call` and return `Response` + +
response details: + + ```go + // Response encloses result and error received from remote server + type Response struct { + Result *json.RawMessage `json:"result,omitempty"` + Error string `json:"error,omitempty"` + ID uint64 `json:"id"` + } + ``` +
+* User should encode and decode json payloads on the application level, see provided [examples](https://github.com/go-pkgz/jrpc/tree/master/_example) +* `jrpc.Server` doesn't support https internally (yet). If used on exposed or non-private networks, should be proxied with something providing https termination (nginx and others). + +## Status + +The code was extracted from [remark42](https://github.com/umputun/remark) and still under development. Until v1.x released the + API & protocol may change. + \ No newline at end of file diff --git a/backend/app/rpc/client.go b/backend/vendor/github.com/go-pkgz/jrpc/client.go similarity index 65% rename from backend/app/rpc/client.go rename to backend/vendor/github.com/go-pkgz/jrpc/client.go index 5cef2a16..b9a40573 100644 --- a/backend/app/rpc/client.go +++ b/backend/vendor/github.com/go-pkgz/jrpc/client.go @@ -1,26 +1,29 @@ -package rpc +package jrpc import ( "bytes" "encoding/json" "net/http" - "reflect" "sync/atomic" "github.com/pkg/errors" ) // Client implements remote engine and delegates all calls to remote http server +// if AuthUser and AuthPasswd defined will be used for basic auth in each call to server type Client struct { - API string - Client http.Client - AuthUser string - AuthPasswd string + API string // URL to jrpc server with entrypoint, i.e. http://127.0.0.1:8080/command + Client http.Client // http client injected by user + AuthUser string // basic auth user name, should match Server.AuthUser, optional + AuthPasswd string // basic auth password, should match Server.AuthPasswd, optional - id uint64 + id uint64 // used with atomic to populate unique id to Request.ID } -// Call remote server with given method and arguments +// Call remote server with given method and arguments. +// Empty args will be ignored, single arg will be marshaled as-us and multiple args marshaled as []interface{}. +// Returns Response and error. Note: Response has it's own Error field, but that onw controlled by server. +// Returned error represent client-level errors, like failed http call, failed marshaling and so on. func (r *Client) Call(method string, args ...interface{}) (*Response, error) { var b []byte @@ -32,7 +35,7 @@ func (r *Client) Call(method string, args ...interface{}) (*Response, error) { if err != nil { return nil, errors.Wrapf(err, "marshaling failed for %s", method) } - case len(args) == 1 && reflect.TypeOf(args[0]).Kind() == reflect.Struct: + case len(args) == 1: b, err = json.Marshal(Request{Method: method, Params: args[0], ID: atomic.AddUint64(&r.id, 1)}) if err != nil { return nil, errors.Wrapf(err, "marshaling failed for %s", method) diff --git a/backend/vendor/github.com/go-pkgz/jrpc/go.mod b/backend/vendor/github.com/go-pkgz/jrpc/go.mod new file mode 100644 index 00000000..1e3fdb2a --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/go.mod @@ -0,0 +1,16 @@ +module github.com/go-pkgz/jrpc + +go 1.12 + +require ( + github.com/didip/tollbooth v4.0.2+incompatible + github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d + github.com/go-chi/chi v4.0.2+incompatible + github.com/go-chi/render v1.0.1 + github.com/go-pkgz/rest v1.4.1 + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pkg/errors v0.8.1 + github.com/stretchr/testify v1.3.0 + golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 // indirect + golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect +) diff --git a/backend/vendor/github.com/go-pkgz/jrpc/go.sum b/backend/vendor/github.com/go-pkgz/jrpc/go.sum new file mode 100644 index 00000000..9a20171c --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/go.sum @@ -0,0 +1,32 @@ +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/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= +github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= +github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d h1:vs5Nf6IE0N/PwGJ8//zRed4gpCdcr99K2HzX7RuLOQ8= +github.com/didip/tollbooth_chi v0.0.0-20170928041846-6ab5f3083f3d/go.mod h1:YWyIfq3y4ArRfWZ9XksmuusP+7Mad+T0iFZ0kv0XG/M= +github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= +github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= +github.com/go-pkgz/rest v1.4.1 h1:DmaVLPH2O7yLehrWOW0uz01d2mVHz9fBR/iuTiPRzaw= +github.com/go-pkgz/rest v1.4.1/go.mod h1:COazNj35u3RXAgQNBr6neR599tYP3URiOpsu9p0rOtk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/backend/vendor/github.com/go-pkgz/jrpc/jrpc.go b/backend/vendor/github.com/go-pkgz/jrpc/jrpc.go new file mode 100644 index 00000000..86063690 --- /dev/null +++ b/backend/vendor/github.com/go-pkgz/jrpc/jrpc.go @@ -0,0 +1,40 @@ +// Package jrpc implements client and server for RPC-like communication over HTTP with json encoded messages. +// The protocol is somewhat simplified version of json-rpc with a single POST call sending Request json +// (method name and the list of parameters) and receiving back json Response with "result" json +// and error string +package jrpc + +import ( + "encoding/json" +) + +// Request encloses method name and all params +type Request struct { + Method string `json:"method"` // method (function) name + Params interface{} `json:"params,omitempty"` // function arguments + ID uint64 `json:"id"` // unique call id +} + +// Response encloses result and error received from remote server +type Response struct { + Result *json.RawMessage `json:"result,omitempty"` // response json + Error string `json:"error,omitempty"` // optional remote (server side / plugin side) error + ID uint64 `json:"id"` // unique call id, echoed Request.ID to allow calls tracing +} + +// EncodeResponse convert anything (type interface{}) and incoming error (if any) to Response +func EncodeResponse(id uint64, resp interface{}, e error) Response { + v, err := json.Marshal(&resp) + if err != nil { + return Response{Error: err.Error()} + } + if e != nil { + return Response{ID: id, Result: nil, Error: e.Error()} // pass input error + } + raw := json.RawMessage{} + if err := raw.UnmarshalJSON(v); err != nil { + return Response{Error: err.Error()} + } + + return Response{ID: id, Result: &raw} +} diff --git a/backend/app/rpc/server.go b/backend/vendor/github.com/go-pkgz/jrpc/server.go similarity index 58% rename from backend/app/rpc/server.go rename to backend/vendor/github.com/go-pkgz/jrpc/server.go index 55d95866..a52e1434 100644 --- a/backend/app/rpc/server.go +++ b/backend/vendor/github.com/go-pkgz/jrpc/server.go @@ -1,4 +1,4 @@ -package rpc +package jrpc import ( "context" @@ -13,21 +13,19 @@ import ( "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/render" - log "github.com/go-pkgz/lgr" - R "github.com/go-pkgz/rest" + "github.com/go-pkgz/rest" "github.com/go-pkgz/rest/logger" "github.com/pkg/errors" - - "github.com/umputun/remark/backend/app/rest" ) // Server is json-rpc server with an optional basic auth type Server struct { - API string - AuthUser string - AuthPasswd string - Version string - AppName string + API string // url path, i.e. "/command" or "/rpc" etc. + AuthUser string // basic auth user name, should match Client.AuthUser, optional + AuthPasswd string // basic auth password, should match Client.AuthPasswd, optional + Version string // server version, injected from main and used for informational headers only + AppName string // plugin name, injected from main and used for informational headers only + Logger L // logger, if nil will default to NoOpLogger funcs struct { m map[string]ServerFn @@ -40,26 +38,26 @@ type Server struct { } } -// Encoder is a function to encode call's result to Response -type Encoder func(id uint64, resp interface{}, e error) (Response, error) - -// ServerFn handler registered for each method with Add -// Implementations provided by consumer and define response logic. +// ServerFn handler registered for each method with Add or Group. +// Implementations provided by consumer and defines response logic. type ServerFn func(id uint64, params json.RawMessage) Response // Run http server on given port func (s *Server) Run(port int) error { + if s.Logger == nil { + s.Logger = NoOpLogger + } if s.AuthUser == "" || s.AuthPasswd == "" { - log.Print("[WARN] extension server runs without auth") + s.Logger.Logf("[WARN] extension server runs without auth") } if s.funcs.m == nil && len(s.funcs.m) == 0 { return errors.Errorf("nothing mapped for dispatch, Add has to be called prior to Run") } router := chi.NewRouter() - router.Use(middleware.Throttle(1000), middleware.RealIP, R.Recoverer(log.Default())) - router.Use(R.AppInfo(s.AppName, "umputun", s.Version), R.Ping) - logInfoWithBody := logger.New(logger.Log(log.Default()), logger.WithBody, logger.Prefix("[INFO]")).Handler + router.Use(middleware.Throttle(1000), middleware.RealIP, rest.Recoverer(s.Logger)) + router.Use(rest.AppInfo(s.AppName, "umputun", s.Version), rest.Ping) + logInfoWithBody := logger.New(logger.Log(s.Logger), logger.WithBody, logger.Prefix("[INFO]")).Handler router.Use(middleware.Timeout(5 * time.Second)) router.Use(logInfoWithBody, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(1000, nil)), middleware.NoCache) router.Use(s.basicAuth) @@ -76,26 +74,10 @@ func (s *Server) Run(port int) error { } s.httpServer.Unlock() - log.Printf("[INFO] listen on %d", port) + s.Logger.Logf("[INFO] listen on %d", port) return s.httpServer.ListenAndServe() } -// EncodeResponse convert anything to Response -func (s *Server) EncodeResponse(id uint64, resp interface{}, e error) (Response, error) { - v, err := json.Marshal(&resp) - if err != nil { - return Response{}, err - } - if e != nil { - return Response{ID: id, Result: nil, Error: e.Error()}, nil - } - raw := json.RawMessage{} - if err = raw.UnmarshalJSON(v); err != nil { - return Response{}, err - } - return Response{ID: id, Result: &raw}, nil -} - // Shutdown http server func (s *Server) Shutdown() error { s.httpServer.Lock() @@ -108,12 +90,12 @@ func (s *Server) Shutdown() error { return s.httpServer.Shutdown(ctx) } -// Add method handler +// Add method handler. Handler will be called on matching method (Request.Method) func (s *Server) Add(method string, fn ServerFn) { s.httpServer.Lock() defer s.httpServer.Unlock() if s.httpServer.Server != nil { - log.Printf("[WARN] ignored method %s, can't be added to activated server", method) + s.Logger.Logf("[WARN] ignored method %s, can't be added to activated server", method) return } @@ -122,19 +104,20 @@ func (s *Server) Add(method string, fn ServerFn) { }) s.funcs.m[method] = fn - log.Printf("[INFO] add handler for %s", method) + s.Logger.Logf("[INFO] add handler for %s", method) } // HandlersGroup alias for map of handlers type HandlersGroup map[string]ServerFn -// Group of handlers with common prefix +// Group of handlers with common prefix, match on group.method func (s *Server) Group(prefix string, m HandlersGroup) { for k, v := range m { s.Add(prefix+"."+k, v) } } +// handler is http handler multiplexing calls by req.Method func (s *Server) handler(w http.ResponseWriter, r *http.Request) { req := struct { ID uint64 `json:"id"` @@ -143,12 +126,12 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) { }{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - rest.SendErrorJSON(w, r, http.StatusBadRequest, err, req.Method, 0) + rest.SendErrorJSON(w, r, s.Logger, http.StatusBadRequest, err, req.Method) return } fn, ok := s.funcs.m[req.Method] if !ok { - rest.SendErrorJSON(w, r, http.StatusNotImplemented, errors.New("unsupported method"), req.Method, 0) + rest.SendErrorJSON(w, r, s.Logger, http.StatusNotImplemented, errors.New("unsupported method"), req.Method) return } @@ -160,6 +143,7 @@ func (s *Server) handler(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, fn(req.ID, params)) } +// basicAuth middleware. enabled only if both AuthUser and AuthPasswd defined. func (s *Server) basicAuth(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -177,3 +161,17 @@ func (s *Server) basicAuth(h http.Handler) http.Handler { h.ServeHTTP(w, r) }) } + +// L defined logger interface used for an optional rest logging +type L interface { + Logf(format string, args ...interface{}) +} + +// LoggerFunc type is an adapter to allow the use of ordinary functions as Logger. +type LoggerFunc func(format string, args ...interface{}) + +// Logf calls f(id) +func (f LoggerFunc) Logf(format string, args ...interface{}) { f(format, args...) } + +// NoOpLogger logger does nothing +var NoOpLogger = LoggerFunc(func(format string, args ...interface{}) {}) diff --git a/backend/vendor/modules.txt b/backend/vendor/modules.txt index 582f708d..f501d4fb 100644 --- a/backend/vendor/modules.txt +++ b/backend/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/coreos/bbolt github.com/davecgh/go-spew/spew # github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go -# github.com/didip/tollbooth v4.0.0+incompatible +# github.com/didip/tollbooth v4.0.2+incompatible github.com/didip/tollbooth github.com/didip/tollbooth/errors github.com/didip/tollbooth/libstring @@ -38,6 +38,8 @@ github.com/go-pkgz/auth/provider/sender github.com/go-pkgz/auth/token github.com/go-pkgz/auth/logger github.com/go-pkgz/auth/middleware +# github.com/go-pkgz/jrpc v0.1.0 +github.com/go-pkgz/jrpc # github.com/go-pkgz/lcw v0.3.1 github.com/go-pkgz/lcw # github.com/go-pkgz/lgr v0.6.3 @@ -98,7 +100,7 @@ golang.org/x/crypto/acme # golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 golang.org/x/image/draw golang.org/x/image/math/f64 -# golang.org/x/net v0.0.0-20190628185345-da137c7871d7 +# golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 golang.org/x/net/html golang.org/x/net/idna golang.org/x/net/html/atom