From db6c387fe94d4ca9753ac7256fd7e6cef8e67067 Mon Sep 17 00:00:00 2001 From: Umputun Date: Sun, 23 Jun 2019 22:46:23 -0500 Subject: [PATCH] support remote server group handler --- backend/app/store/remote/client.go | 2 +- backend/app/store/remote/server.go | 22 ++++++++++ backend/app/store/remote/server_test.go | 56 +++++++++++++++++++++---- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/backend/app/store/remote/client.go b/backend/app/store/remote/client.go index 5317ed45..e200e03f 100644 --- a/backend/app/store/remote/client.go +++ b/backend/app/store/remote/client.go @@ -59,7 +59,7 @@ func (r *Client) Call(method string, args ...interface{}) (*Response, error) { } defer resp.Body.Close() if resp.StatusCode != 200 { - return nil, errors.Errorf("bad status %d for %s", resp.StatusCode, method) + return nil, errors.Errorf("bad status %s for %s", resp.Status, method) } cr := Response{} diff --git a/backend/app/store/remote/server.go b/backend/app/store/remote/server.go index 49082af3..ba1b59dc 100644 --- a/backend/app/store/remote/server.go +++ b/backend/app/store/remote/server.go @@ -40,6 +40,9 @@ 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. type ServerFn func(id uint64, params json.RawMessage) Response @@ -107,10 +110,29 @@ func (s *Server) Shutdown() error { // Add method handler 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) + return + } + s.funcs.once.Do(func() { s.funcs.m = map[string]ServerFn{} }) + s.funcs.m[method] = fn + log.Printf("[INFO] add handler for %s", method) +} + +// HandlersGroup alias for map of handlers +type HandlersGroup map[string]ServerFn + +// Group of handlers with common prefix +func (s *Server) Group(prefix string, m HandlersGroup) { + for k, v := range m { + s.Add(prefix+"."+k, v) + } } func (s *Server) handler(w http.ResponseWriter, r *http.Request) { diff --git a/backend/app/store/remote/server_test.go b/backend/app/store/remote/server_test.go index ae0f38a0..49342c99 100644 --- a/backend/app/store/remote/server_test.go +++ b/backend/app/store/remote/server_test.go @@ -40,6 +40,7 @@ func TestServerPrimitiveTypes(t *testing.T) { }) go func() { s.Run(9091) }() + defer func() { assert.NoError(t, s.Shutdown()) }() time.Sleep(10 * time.Millisecond) // check with direct http call @@ -64,7 +65,6 @@ func TestServerPrimitiveTypes(t *testing.T) { err = json.Unmarshal(*r.Result, &res) assert.Equal(t, respData{Res1: "res blah", Res2: true}, res) assert.Equal(t, uint64(1), r.ID) - assert.NoError(t, s.Shutdown()) } func TestServerWithObject(t *testing.T) { @@ -94,6 +94,7 @@ func TestServerWithObject(t *testing.T) { }) 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{}} @@ -104,8 +105,6 @@ func TestServerWithObject(t *testing.T) { res := respData{} err = json.Unmarshal(*r.Result, &res) assert.Equal(t, respData{Res1: "res blah", Res2: true}, res) - - assert.NoError(t, s.Shutdown()) } func TestServerMethodNotImplemented(t *testing.T) { @@ -148,6 +147,7 @@ func TestServerWithAuth(t *testing.T) { 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) @@ -160,9 +160,7 @@ func TestServerWithAuth(t *testing.T) { 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 for test") - - assert.NoError(t, s.Shutdown()) + assert.EqualError(t, err, "bad status 401 Unauthorized for test") } func TestServerErrReturn(t *testing.T) { @@ -186,13 +184,57 @@ func TestServerErrReturn(t *testing.T) { }) 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") +} - assert.NoError(t, s.Shutdown()) +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) {