This is an automated email from the ASF dual-hosted git repository.
tianxiaoliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-service-center.git
The following commit(s) were added to refs/heads/master by this push:
new e640a6f SCB-2176 Refactor rest router (#994)
e640a6f is described below
commit e640a6f5daf51ddf8fdf2fc77c684289515236cc
Author: little-cui <[email protected]>
AuthorDate: Fri May 21 09:17:18 2021 +0800
SCB-2176 Refactor rest router (#994)
---
pkg/rest/common.go | 28 +++
pkg/rest/{roa.go => handler.go} | 48 ++--
pkg/rest/{roa.go => rest.go} | 16 +-
pkg/rest/route.go | 268 ---------------------
pkg/rest/router.go | 149 ++++++++++++
pkg/rest/{roa.go => types.go} | 31 +--
.../roa.go => server/handler/response/response.go | 23 +-
7 files changed, 233 insertions(+), 330 deletions(-)
diff --git a/pkg/rest/common.go b/pkg/rest/common.go
index ffe5e8d..1b96d6e 100644
--- a/pkg/rest/common.go
+++ b/pkg/rest/common.go
@@ -64,3 +64,31 @@ func isValidMethod(method string) bool {
return false
}
}
+
+func match(s string, f func(c byte) bool, exclude byte, i int) (matched
string, next byte, j int) {
+ j = i
+ for j < len(s) && f(s[j]) && s[j] != exclude {
+ j++
+ }
+
+ if j < len(s) {
+ next = s[j]
+ }
+ return s[i:j], next, j
+}
+
+func matchParticipial(c byte) bool {
+ return c != '/'
+}
+
+func isAlpha(ch byte) bool {
+ return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_'
+}
+
+func isDigit(ch byte) bool {
+ return '0' <= ch && ch <= '9'
+}
+
+func isAlnum(ch byte) bool {
+ return isAlpha(ch) || isDigit(ch)
+}
diff --git a/pkg/rest/roa.go b/pkg/rest/handler.go
similarity index 53%
copy from pkg/rest/roa.go
copy to pkg/rest/handler.go
index d28e569..1881b32 100644
--- a/pkg/rest/roa.go
+++ b/pkg/rest/handler.go
@@ -19,24 +19,42 @@ package rest
import (
"net/http"
+ "net/url"
)
-var (
- serverHandler *ROAServerHandler // serverHandler is the default handler
-)
-
-func init() {
- serverHandler = NewROAServerHander()
- serverHandler.setChainName(ServerChainName)
+type urlPatternHandler struct {
+ Name string
+ Path string
+ http.Handler
}
-// RegisterServant registers a ROAServantService into serverHandler
-// servant must be an pointer to service object
-func RegisterServant(servant interface{}) {
- serverHandler.RegisterServant(servant)
-}
+func (roa *urlPatternHandler) try(path string) (p string, _ bool) {
+ var i, j int
+ l, sl := len(roa.Path), len(path)
+ for i < sl {
+ switch {
+ case j >= l:
+ if roa.Path != "/" && l > 0 && roa.Path[l-1] == '/' {
+ return p, true
+ }
+ return "", false
+ case roa.Path[j] == ':':
+ var val string
+ var nextc byte
+ o := j
+ _, nextc, j = match(roa.Path, isAlnum, 0, j+1)
+ val, _, i = match(path, matchParticipial, nextc, i)
-//GetRouter return the router fo REST service
-func GetRouter() http.Handler {
- return serverHandler
+ p += url.QueryEscape(roa.Path[o:j]) + "=" +
url.QueryEscape(val) + "&"
+ case path[i] == roa.Path[j]:
+ i++
+ j++
+ default:
+ return "", false
+ }
+ }
+ if j != l {
+ return "", false
+ }
+ return p, true
}
diff --git a/pkg/rest/roa.go b/pkg/rest/rest.go
similarity index 74%
copy from pkg/rest/roa.go
copy to pkg/rest/rest.go
index d28e569..f978dd8 100644
--- a/pkg/rest/roa.go
+++ b/pkg/rest/rest.go
@@ -21,22 +21,20 @@ import (
"net/http"
)
-var (
- serverHandler *ROAServerHandler // serverHandler is the default handler
-)
+var router *Router // router is the default handler
func init() {
- serverHandler = NewROAServerHander()
- serverHandler.setChainName(ServerChainName)
+ router = NewRouter()
+ router.setChainName(ServerChainName)
}
-// RegisterServant registers a ROAServantService into serverHandler
+// RegisterServant registers a RouteGroup into router
// servant must be an pointer to service object
-func RegisterServant(servant interface{}) {
- serverHandler.RegisterServant(servant)
+func RegisterServant(group RouteGroup) {
+ router.RegisterServant(group)
}
//GetRouter return the router fo REST service
func GetRouter() http.Handler {
- return serverHandler
+ return router
}
diff --git a/pkg/rest/route.go b/pkg/rest/route.go
deleted file mode 100644
index a0b66d5..0000000
--- a/pkg/rest/route.go
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 rest
-
-import (
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "reflect"
- "strings"
-
- "github.com/apache/servicecomb-service-center/pkg/chain"
- errorsEx "github.com/apache/servicecomb-service-center/pkg/errors"
- "github.com/apache/servicecomb-service-center/pkg/log"
- "github.com/apache/servicecomb-service-center/pkg/util"
-)
-
-// URLPattern defines an uri pattern
-type URLPattern struct {
- Method string
- Path string
-}
-
-type urlPatternHandler struct {
- Name string
- Path string
- http.Handler
-}
-
-// Route is a http route
-type Route struct {
- // Method is one of the following: GET,PUT,POST,DELETE
- Method string
- // Path contains a path pattern
- Path string
- // rest callback function for the specified Method and Path
- Func func(w http.ResponseWriter, r *http.Request)
-}
-
-// ROAServantService defines a group of Routes
-type ROAServantService interface {
- URLPatterns() []Route
-}
-
-// ROAServerHandler is a HTTP request multiplexer
-// Attention:
-// 1. not thread-safe, must be initialized completely before serve http
request
-// 2. redirect not supported
-type ROAServerHandler struct {
- handlers map[string][]*urlPatternHandler
- chainName string
-}
-
-// NewROAServerHander news an ROAServerHandler
-func NewROAServerHander() *ROAServerHandler {
- return &ROAServerHandler{
- handlers: make(map[string][]*urlPatternHandler),
- }
-}
-
-// RegisterServant registers a ROAServantService
-// servant must be an pointer to service object
-func (roa *ROAServerHandler) RegisterServant(servant interface{}) {
- val := reflect.ValueOf(servant)
- ind := reflect.Indirect(val)
- typ := ind.Type()
- name := util.FileLastName(typ.PkgPath() + "." + typ.Name())
- if val.Kind() != reflect.Ptr {
- log.Errorf(nil, "<rest.RegisterServant> cannot use non-ptr
servant struct `%s`", name)
- return
- }
-
- urlPatternFunc := val.MethodByName("URLPatterns")
- if !urlPatternFunc.IsValid() {
- log.Errorf(nil, "<rest.RegisterServant> no 'URLPatterns'
function in servant struct `%s`", name)
- return
- }
-
- vals := urlPatternFunc.Call([]reflect.Value{})
- if len(vals) <= 0 {
- log.Errorf(nil, "<rest.RegisterServant> call 'URLPatterns'
function failed in servant struct `%s`", name)
- return
- }
-
- val0 := vals[0]
- if !val.CanInterface() {
- log.Errorf(nil, "<rest.RegisterServant> result of 'URLPatterns'
function not interface type in servant struct `%s`", name)
- return
- }
-
- if routes, ok := val0.Interface().([]Route); ok {
- log.Infof("register servant %s", name)
- for _, route := range routes {
- err := roa.addRoute(&route)
- if err != nil {
- log.Errorf(err, "register route failed.")
- }
- }
- } else {
- log.Errorf(nil, "<rest.RegisterServant> result of 'URLPatterns'
function not []*Route type in servant struct `%s`", name)
- }
-}
-
-func (roa *ROAServerHandler) setChainName(name string) {
- roa.chainName = name
-}
-
-func (roa *ROAServerHandler) addRoute(route *Route) (err error) {
- method := strings.ToUpper(route.Method)
- if !isValidMethod(method) || !strings.HasPrefix(route.Path, "/") ||
route.Func == nil {
- message := fmt.Sprintf("Invalid route parameters(method: %s,
path: %s)", method, route.Path)
- log.Errorf(nil, message)
- return errors.New(message)
- }
-
- roa.handlers[method] = append(roa.handlers[method], &urlPatternHandler{
- util.FormatFuncName(util.FuncName(route.Func)), route.Path,
http.HandlerFunc(route.Func)})
- log.Infof("register route %s(%s)", route.Path, method)
-
- return nil
-}
-
-// ServeHTTP implements http.Handler
-func (roa *ROAServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
{
- for _, ph := range roa.handlers[r.Method] {
- if params, ok := ph.try(r.URL.Path); ok {
- if len(params) > 0 {
- r.URL.RawQuery = params + r.URL.RawQuery
- }
-
- roa.serve(ph, w, r)
- return
- }
- }
-
- allowed := make([]string, 0, len(roa.handlers))
- for method, handlers := range roa.handlers {
- if method == r.Method {
- continue
- }
-
- for _, ph := range handlers {
- if _, ok := ph.try(r.URL.Path); ok {
- allowed = append(allowed, method)
- }
- }
- }
-
- if len(allowed) == 0 {
- http.NotFound(w, r)
- return
- }
-
- w.Header().Add(HeaderAllow, util.StringJoin(allowed, ", "))
- http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
-}
-
-func (roa *ROAServerHandler) serve(ph *urlPatternHandler, w
http.ResponseWriter, r *http.Request) {
- ctx := util.NewStringContext(r.Context())
- if ctx != r.Context() {
- nr := r.WithContext(ctx)
- *r = *nr
- }
-
- inv := chain.NewInvocation(ctx, chain.NewChain(roa.chainName,
chain.Handlers(roa.chainName)))
- inv.WithContext(CtxResponse, w).
- WithContext(CtxRequest, r).
- WithContext(CtxMatchPattern, ph.Path).
- WithContext(CtxMatchFunc, ph.Name).
- Invoke(
- func(ret chain.Result) {
- defer func() {
- err := ret.Err
- itf := recover()
- if itf != nil {
- log.Panic(itf)
-
- err = errorsEx.RaiseError(itf)
- }
- if _, ok :=
err.(errorsEx.InternalError); ok {
- http.Error(w, err.Error(),
http.StatusInternalServerError)
- return
- }
- if err != nil {
- http.Error(w, err.Error(),
http.StatusBadRequest)
- return
- }
- }()
- if ret.OK {
- ph.ServeHTTP(w, r)
- }
- })
-}
-
-func (roa *urlPatternHandler) try(path string) (p string, _ bool) {
- var i, j int
- l, sl := len(roa.Path), len(path)
- for i < sl {
- switch {
- case j >= l:
- if roa.Path != "/" && l > 0 && roa.Path[l-1] == '/' {
- return p, true
- }
- return "", false
- case roa.Path[j] == ':':
- var val string
- var nextc byte
- o := j
- _, nextc, j = match(roa.Path, isAlnum, 0, j+1)
- val, _, i = match(path, matchParticial, nextc, i)
-
- p += url.QueryEscape(roa.Path[o:j]) + "=" +
url.QueryEscape(val) + "&"
- case path[i] == roa.Path[j]:
- i++
- j++
- default:
- return "", false
- }
- }
- if j != l {
- return "", false
- }
- return p, true
-}
-
-func match(s string, f func(c byte) bool, exclude byte, i int) (matched
string, next byte, j int) {
- j = i
- for j < len(s) && f(s[j]) && s[j] != exclude {
- j++
- }
-
- if j < len(s) {
- next = s[j]
- }
- return s[i:j], next, j
-}
-
-func matchParticial(c byte) bool {
- return c != '/'
-}
-
-func isAlpha(ch byte) bool {
- return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_'
-}
-
-func isDigit(ch byte) bool {
- return '0' <= ch && ch <= '9'
-}
-
-func isAlnum(ch byte) bool {
- return isAlpha(ch) || isDigit(ch)
-}
diff --git a/pkg/rest/router.go b/pkg/rest/router.go
new file mode 100644
index 0000000..890b145
--- /dev/null
+++ b/pkg/rest/router.go
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 rest
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/apache/servicecomb-service-center/pkg/chain"
+ errorsEx "github.com/apache/servicecomb-service-center/pkg/errors"
+ "github.com/apache/servicecomb-service-center/pkg/log"
+ "github.com/apache/servicecomb-service-center/pkg/util"
+)
+
+// Router is a HTTP request multiplexer
+// Attention:
+// 1. not thread-safe, must be initialized completely before serve http
request
+// 2. redirect not supported
+type Router struct {
+ handlers map[string][]*urlPatternHandler
+ chainName string
+}
+
+// RegisterServant registers a RouteGroup
+// servant must be an pointer to service object
+func (router *Router) RegisterServant(servant RouteGroup) {
+ log.Infof("register servant %s", util.Reflect(servant).Name())
+ for _, route := range servant.URLPatterns() {
+ err := router.addRoute(&route)
+ if err != nil {
+ log.Errorf(err, "register route failed.")
+ }
+ }
+}
+
+func (router *Router) setChainName(name string) {
+ router.chainName = name
+}
+
+func (router *Router) addRoute(route *Route) (err error) {
+ method := strings.ToUpper(route.Method)
+ if !isValidMethod(method) || !strings.HasPrefix(route.Path, "/") ||
route.Func == nil {
+ message := fmt.Sprintf("Invalid route parameters(method: %s,
path: %s)", method, route.Path)
+ log.Errorf(nil, message)
+ return errors.New(message)
+ }
+
+ router.handlers[method] = append(router.handlers[method],
&urlPatternHandler{
+ util.FormatFuncName(util.FuncName(route.Func)), route.Path,
http.HandlerFunc(route.Func)})
+ log.Infof("register route %s(%s)", route.Path, method)
+
+ return nil
+}
+
+// ServeHTTP implements http.Handler
+func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ for _, ph := range router.handlers[r.Method] {
+ if params, ok := ph.try(r.URL.Path); ok {
+ if len(params) > 0 {
+ r.URL.RawQuery = params + r.URL.RawQuery
+ }
+
+ router.serve(ph, w, r)
+ return
+ }
+ }
+
+ allowed := make([]string, 0, len(router.handlers))
+ for method, handlers := range router.handlers {
+ if method == r.Method {
+ continue
+ }
+
+ for _, ph := range handlers {
+ if _, ok := ph.try(r.URL.Path); ok {
+ allowed = append(allowed, method)
+ }
+ }
+ }
+
+ if len(allowed) == 0 {
+ http.NotFound(w, r)
+ return
+ }
+
+ w.Header().Add(HeaderAllow, util.StringJoin(allowed, ", "))
+ http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
+}
+
+func (router *Router) serve(ph *urlPatternHandler, w http.ResponseWriter, r
*http.Request) {
+ ctx := util.NewStringContext(r.Context())
+ if ctx != r.Context() {
+ nr := r.WithContext(ctx)
+ *r = *nr
+ }
+
+ inv := chain.NewInvocation(ctx, chain.NewChain(router.chainName,
chain.Handlers(router.chainName)))
+ inv.WithContext(CtxResponse, w).
+ WithContext(CtxRequest, r).
+ WithContext(CtxMatchPattern, ph.Path).
+ WithContext(CtxMatchFunc, ph.Name).
+ Invoke(
+ func(ret chain.Result) {
+ defer func() {
+ err := ret.Err
+ itf := recover()
+ if itf != nil {
+ log.Panic(itf)
+
+ err = errorsEx.RaiseError(itf)
+ }
+ if _, ok :=
err.(errorsEx.InternalError); ok {
+ http.Error(w, err.Error(),
http.StatusInternalServerError)
+ return
+ }
+ if err != nil {
+ http.Error(w, err.Error(),
http.StatusBadRequest)
+ return
+ }
+ }()
+ if ret.OK {
+ ph.ServeHTTP(w, r)
+ }
+ })
+}
+
+// NewRouter news an Router
+func NewRouter() *Router {
+ return &Router{
+ handlers: make(map[string][]*urlPatternHandler),
+ }
+}
diff --git a/pkg/rest/roa.go b/pkg/rest/types.go
similarity index 61%
copy from pkg/rest/roa.go
copy to pkg/rest/types.go
index d28e569..4b7b4b8 100644
--- a/pkg/rest/roa.go
+++ b/pkg/rest/types.go
@@ -17,26 +17,19 @@
package rest
-import (
- "net/http"
-)
+import "net/http"
-var (
- serverHandler *ROAServerHandler // serverHandler is the default handler
-)
-
-func init() {
- serverHandler = NewROAServerHander()
- serverHandler.setChainName(ServerChainName)
-}
-
-// RegisterServant registers a ROAServantService into serverHandler
-// servant must be an pointer to service object
-func RegisterServant(servant interface{}) {
- serverHandler.RegisterServant(servant)
+// Route is a http route
+type Route struct {
+ // Method is one of the following: GET,PUT,POST,DELETE
+ Method string
+ // Path contains a path pattern
+ Path string
+ // rest callback function for the specified Method and Path
+ Func func(w http.ResponseWriter, r *http.Request)
}
-//GetRouter return the router fo REST service
-func GetRouter() http.Handler {
- return serverHandler
+// RouteGroup defines a group of Routes
+type RouteGroup interface {
+ URLPatterns() []Route
}
diff --git a/pkg/rest/roa.go b/server/handler/response/response.go
similarity index 61%
rename from pkg/rest/roa.go
rename to server/handler/response/response.go
index d28e569..3b83aee 100644
--- a/pkg/rest/roa.go
+++ b/server/handler/response/response.go
@@ -15,28 +15,13 @@
* limitations under the License.
*/
-package rest
+package response
-import (
- "net/http"
-)
+import "github.com/apache/servicecomb-service-center/pkg/chain"
-var (
- serverHandler *ROAServerHandler // serverHandler is the default handler
-)
-
-func init() {
- serverHandler = NewROAServerHander()
- serverHandler.setChainName(ServerChainName)
+type Handler struct {
}
-// RegisterServant registers a ROAServantService into serverHandler
-// servant must be an pointer to service object
-func RegisterServant(servant interface{}) {
- serverHandler.RegisterServant(servant)
-}
+func (l *Handler) Handle(i *chain.Invocation) {
-//GetRouter return the router fo REST service
-func GetRouter() http.Handler {
- return serverHandler
}