This repository has been archived by the owner on Feb 27, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 773
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: define api register of supernode to add api
Signed-off-by: lowzj <[email protected]>
- Loading branch information
Showing
6 changed files
with
536 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
* 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 name == "" { | ||
return nil | ||
} | ||
if c, ok := apiCategories[name]; ok && c != nil { | ||
return c | ||
} | ||
|
||
apiCategories[name] = &category{ | ||
name: name, | ||
prefix: prefix, | ||
} | ||
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 != "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* 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" | ||
"fmt" | ||
"math/rand" | ||
"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(context.Context, http.ResponseWriter, *http.Request) error { | ||
return nil | ||
}, | ||
} | ||
} | ||
|
||
func (s *APISuite) SetupTest() { | ||
for _, v := range apiCategories { | ||
v.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) | ||
} | ||
} | ||
} | ||
|
||
func (s *APISuite) TestCategory_others() { | ||
for k, v := range apiCategories { | ||
s.Equal(k, v.name) | ||
s.Equal(v.Name(), v.name) | ||
s.Equal(v.Prefix(), v.prefix) | ||
s.Equal(v.Handlers(), v.handlerSpecs) | ||
} | ||
} | ||
|
||
func (s *APISuite) TestNewCategory() { | ||
// don't create a category with the same name | ||
for k, v := range apiCategories { | ||
c := newCategory(k, "") | ||
s.Equal(c, v) | ||
} | ||
|
||
// don't create a category with empty name | ||
c := newCategory("", "x") | ||
s.Nil(c) | ||
|
||
// create a new category | ||
name := fmt.Sprintf("%v", rand.Float64()) | ||
c = newCategory(name, "/") | ||
defer delete(apiCategories, name) | ||
s.Equal(c.name, name) | ||
s.Equal(c.prefix, "/") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* | ||
* 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" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/go-openapi/strfmt" | ||
"github.com/sirupsen/logrus" | ||
|
||
"github.com/dragonflyoss/Dragonfly/apis/types" | ||
"github.com/dragonflyoss/Dragonfly/pkg/errortypes" | ||
"github.com/dragonflyoss/Dragonfly/pkg/util" | ||
) | ||
|
||
// ValidateFunc validates the request parameters. | ||
type ValidateFunc func(registry strfmt.Registry) error | ||
|
||
// ParseJSONRequest parses the request JSON parameter to a target object. | ||
func ParseJSONRequest(req io.Reader, target interface{}, validator ValidateFunc) error { | ||
if util.IsNil(target) { | ||
return errortypes.NewHTTPError(http.StatusInternalServerError, "nil target") | ||
} | ||
if err := json.NewDecoder(req).Decode(target); err != nil { | ||
if err == io.EOF { | ||
return errortypes.NewHTTPError(http.StatusBadRequest, "empty request") | ||
} | ||
return errortypes.NewHTTPError(http.StatusBadRequest, err.Error()) | ||
} | ||
if validator != nil { | ||
if err := validator(strfmt.Default); err != nil { | ||
return errortypes.NewHTTPError(http.StatusBadRequest, err.Error()) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// EncodeResponse encodes response in json. | ||
// The response body is empty if the data is nil or empty value. | ||
func EncodeResponse(w http.ResponseWriter, code int, data interface{}) error { | ||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(code) | ||
if util.IsNil(data) || data == "" { | ||
return nil | ||
} | ||
return json.NewEncoder(w).Encode(data) | ||
} | ||
|
||
// HandleErrorResponse handles err from server side and constructs response | ||
// for client side. | ||
func HandleErrorResponse(w http.ResponseWriter, err error) { | ||
switch e := err.(type) { | ||
case *errortypes.HTTPError: | ||
_ = EncodeResponse(w, e.Code, errResp(e.Code, e.Msg)) | ||
default: | ||
// By default, server side returns code 500 if error happens. | ||
_ = EncodeResponse(w, http.StatusInternalServerError, | ||
errResp(http.StatusInternalServerError, e.Error())) | ||
} | ||
} | ||
|
||
// WrapHandler converts the 'api.HandlerFunc' into type 'http.HandlerFunc' and | ||
// format the error response if any error happens. | ||
func WrapHandler(handler HandlerFunc) http.HandlerFunc { | ||
pCtx := context.Background() | ||
|
||
return func(w http.ResponseWriter, req *http.Request) { | ||
ctx, cancel := context.WithCancel(pCtx) | ||
defer cancel() | ||
|
||
// Start to handle request. | ||
err := handler(ctx, w, req) | ||
if err != nil { | ||
// Handle error if request handling fails. | ||
HandleErrorResponse(w, err) | ||
} | ||
logrus.Debugf("%s %v err:%v", req.Method, req.URL, err) | ||
} | ||
} | ||
|
||
func errResp(code int, msg string) *types.ErrorResponse { | ||
return &types.ErrorResponse{ | ||
Code: int64(code), | ||
Message: msg, | ||
} | ||
} |
Oops, something went wrong.