From b25637a5a00f6f1e09661f88e605472279fd2db9 Mon Sep 17 00:00:00 2001 From: lowzj Date: Wed, 13 May 2020 11:19:37 +0800 Subject: [PATCH] refactor: define api register of supernode to add api Signed-off-by: lowzj --- supernode/server/api/api.go | 87 ++++++++++++++++++++++++++++++++ supernode/server/api/api_test.go | 75 +++++++++++++++++++++++++++ supernode/server/api/handler.go | 32 ++++++++++++ supernode/server/api/utils.go | 74 +++++++++++++++++++++++++++ supernode/server/router.go | 1 + 5 files changed, 269 insertions(+) create mode 100644 supernode/server/api/api.go create mode 100644 supernode/server/api/api_test.go create mode 100644 supernode/server/api/handler.go create mode 100644 supernode/server/api/utils.go diff --git a/supernode/server/api/api.go b/supernode/server/api/api.go new file mode 100644 index 000000000..49964dca2 --- /dev/null +++ b/supernode/server/api/api.go @@ -0,0 +1,87 @@ +/* + * Copyright The Dragonfly Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +var ( + // V1 is recommended, any new API should be registered in this category. + V1 = newCategory("v1 api", "/api/v1") + + // Extension allows users to register extension APIs into supernode. + // Customized APIs should be registered by using this category. + // It can distinguish between Dragonfly's core APIs and customized APIs. + // And supernode provides `/api/ext` to list all the registered APIs in this + // category. + Extension = newCategory("extension api", "/api/ext") + + // Legacy is deprecated, just for compatibility with the old version, + // please do not use it to add new API. + Legacy = newCategory("legacy api", "") +) + +var ( + apiCategories = make(map[string]*category) +) + +func newCategory(name, prefix string) *category { + if c, ok := apiCategories[name]; ok && c != nil { + return c + } + + apiCategories[name] = &category{ + name: name, + prefix: prefix, + handlerSpecs: make([]*HandlerSpec, 0), + } + return apiCategories[name] +} + +// category groups the APIs. +type category struct { + name string + prefix string + handlerSpecs []*HandlerSpec +} + +// Register registers an API into this API category. +func (c *category) Register(h *HandlerSpec) *category { + if !validate(h) { + return c + } + c.handlerSpecs = append(c.handlerSpecs, h) + return c +} + +// Name returns the name of this category. +func (c *category) Name() string { + return c.name +} + +// Prefix returns the api prefix of this category. +func (c *category) Prefix() string { + return c.prefix +} + +// Handlers returns all of the APIs registered into this category. +func (c *category) Handlers() []*HandlerSpec { + return c.handlerSpecs +} + +// ----------------------------------------------------------------------------- + +func validate(h *HandlerSpec) bool { + return h != nil && h.HandlerFunc != nil && h.Method != "" +} diff --git a/supernode/server/api/api_test.go b/supernode/server/api/api_test.go new file mode 100644 index 000000000..835f85ea3 --- /dev/null +++ b/supernode/server/api/api_test.go @@ -0,0 +1,75 @@ +/* + * Copyright The Dragonfly Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestSuite(t *testing.T) { + suite.Run(t, new(APISuite)) +} + +type APISuite struct { + suite.Suite + validHandler *HandlerSpec + invalidHandler *HandlerSpec +} + +func (s *APISuite) SetupSuite() { + s.validHandler = &HandlerSpec{ + Method: "GET", + HandlerFunc: func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error { + return nil + }, + } + s.invalidHandler = &HandlerSpec{} +} + +func (s *APISuite) SetupTest() { + for _, c := range []*category{V1, Extension, Legacy} { + c.HandlerSpecs = nil + } +} + +func (s *APISuite) TestCategory_Register() { + var cases = []struct { + c *category + h *HandlerSpec + }{ + {V1, s.invalidHandler}, + {V1, s.validHandler}, + {Extension, s.validHandler}, + {Legacy, s.validHandler}, + } + + for _, v := range cases { + before := v.c.HandlerSpecs + v.c.Register(v.h) + after := v.c.HandlerSpecs + if s.invalidHandler == v.h { + s.Equal(before, after) + } else if s.validHandler == v.h { + s.Equal(len(before)+1, len(after)) + s.Equal(after[len(after)-1], v.h) + } + } +} diff --git a/supernode/server/api/handler.go b/supernode/server/api/handler.go new file mode 100644 index 000000000..f8f33266a --- /dev/null +++ b/supernode/server/api/handler.go @@ -0,0 +1,32 @@ +/* + * Copyright The Dragonfly Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "context" + "net/http" +) + +// HandlerSpec describes an HTTP api +type HandlerSpec struct { + Method string + Path string + HandlerFunc HandlerFunc +} + +// HandlerFunc is the http request handler. +type HandlerFunc func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error diff --git a/supernode/server/api/utils.go b/supernode/server/api/utils.go new file mode 100644 index 000000000..f753b541f --- /dev/null +++ b/supernode/server/api/utils.go @@ -0,0 +1,74 @@ +/* + * Copyright The Dragonfly Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package api + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/dragonflyoss/Dragonfly/apis/types" + "github.com/dragonflyoss/Dragonfly/pkg/errortypes" + + "github.com/go-openapi/strfmt" +) + +// ValidateFunc validates the request parameters. +type ValidateFunc func(registry strfmt.Registry) error + +// ParseJSONRequest parses the request parameter formed by JSON to a object. +func ParseJSONRequest(body io.Reader, request interface{}, validator ValidateFunc) error { + if err := json.NewDecoder(body).Decode(request); err != nil { + if err == io.EOF { + return errortypes.New(http.StatusBadRequest, "empty body") + } + return errortypes.New(http.StatusBadRequest, err.Error()) + } + if validator != nil { + if err := validator(strfmt.NewFormats()); err != nil { + return errortypes.New(http.StatusBadRequest, err.Error()) + } + } + return nil +} + +// EncodeResponse encodes response in json. +func EncodeResponse(rw http.ResponseWriter, statusCode int, data interface{}) error { + rw.Header().Set("Content-Type", "application/json") + rw.WriteHeader(statusCode) + return json.NewEncoder(rw).Encode(data) +} + +// HandleErrorResponse handles err from daemon side and constructs response for client side. +func HandleErrorResponse(w http.ResponseWriter, err error) { + var ( + code int + errMsg string + ) + + // By default, daemon side returns code 500 if error happens. + code = http.StatusInternalServerError + if e, ok := err.(*errortypes.DfError); ok { + code = e.Code + errMsg = e.Msg + } + + _ = EncodeResponse(w, code, types.ErrorResponse{ + Code: int64(code), + Message: errMsg, + }) +} diff --git a/supernode/server/router.go b/supernode/server/router.go index fc34c272a..18397d6f7 100644 --- a/supernode/server/router.go +++ b/supernode/server/router.go @@ -24,6 +24,7 @@ import ( "github.com/dragonflyoss/Dragonfly/apis/types" "github.com/dragonflyoss/Dragonfly/version" + "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp"