feat: standardize REST and RPC routes
This commit is contained in:
@@ -7,7 +7,7 @@ Server:
|
|||||||
BindIP: 0.0.0.0
|
BindIP: 0.0.0.0
|
||||||
Port: 12001
|
Port: 12001
|
||||||
|
|
||||||
# Dynamic JSON-to-protobuf unary RPC endpoint: POST /rpc/{full.service}.{method}
|
# Dynamic JSON-to-protobuf unary RPC endpoint: POST /rpc/{module}/{service}/{method}
|
||||||
# An empty allow list denies every dynamic call. Use "*" to expose all
|
# An empty allow list denies every dynamic call. Use "*" to expose all
|
||||||
# reflection-visible unary methods, or list full service/method names.
|
# reflection-visible unary methods, or list full service/method names.
|
||||||
DynamicRPC:
|
DynamicRPC:
|
||||||
|
|||||||
@@ -70,12 +70,15 @@ func reflectionTarget(addr string) string {
|
|||||||
func (g *dynamicGateway) Close() error { return g.conn.Close() }
|
func (g *dynamicGateway) Close() error { return g.conn.Close() }
|
||||||
|
|
||||||
func (g *dynamicGateway) handle(c *gin.Context) {
|
func (g *dynamicGateway) handle(c *gin.Context) {
|
||||||
fullMethod := strings.TrimSpace(c.Param("method"))
|
moduleName := strings.TrimSpace(c.Param("module"))
|
||||||
serviceName, methodName, ok := splitFullMethod(fullMethod)
|
serviceShortName := strings.TrimSpace(c.Param("service"))
|
||||||
if !ok {
|
methodName := strings.TrimSpace(c.Param("method"))
|
||||||
writeDynamicError(c, status.Error(codes.InvalidArgument, "method must be {full.service}.{method}"))
|
if moduleName == "" || serviceShortName == "" || methodName == "" {
|
||||||
|
writeDynamicError(c, status.Error(codes.InvalidArgument, "path must be /rpc/{module}/{service}/{method}"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
serviceName := moduleName + "." + serviceShortName
|
||||||
|
fullMethod := serviceName + "." + methodName
|
||||||
if !g.isAllowed(serviceName, fullMethod) {
|
if !g.isAllowed(serviceName, fullMethod) {
|
||||||
writeDynamicError(c, status.Error(codes.PermissionDenied, "dynamic RPC method is not allowed"))
|
writeDynamicError(c, status.Error(codes.PermissionDenied, "dynamic RPC method is not allowed"))
|
||||||
return
|
return
|
||||||
@@ -122,14 +125,6 @@ func (g *dynamicGateway) handle(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, dynamicRPCResponse{Code: int32(codes.OK), Message: codes.OK.String(), Data: data})
|
c.JSON(http.StatusOK, dynamicRPCResponse{Code: int32(codes.OK), Message: codes.OK.String(), Data: data})
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitFullMethod(value string) (string, string, bool) {
|
|
||||||
separator := strings.LastIndexByte(value, '.')
|
|
||||||
if separator <= 0 || separator == len(value)-1 {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
return value[:separator], value[separator+1:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *dynamicGateway) isAllowed(serviceName, fullMethod string) bool {
|
func (g *dynamicGateway) isAllowed(serviceName, fullMethod string) bool {
|
||||||
for _, key := range []string{"*", serviceName, fullMethod} {
|
for _, key := range []string{"*", serviceName, fullMethod} {
|
||||||
if _, ok := g.allow[key]; ok {
|
if _, ok := g.allow[key]; ok {
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ func TestDynamicGatewayInvokesUnaryRPC(t *testing.T) {
|
|||||||
|
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
engine := gin.New()
|
engine := gin.New()
|
||||||
engine.POST("/rpc/:method", gateway.handle)
|
engine.POST("/rpc/:module/:service/:method", gateway.handle)
|
||||||
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{"service":""}`))
|
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{"service":""}`))
|
||||||
response := httptest.NewRecorder()
|
response := httptest.NewRecorder()
|
||||||
engine.ServeHTTP(response, request)
|
engine.ServeHTTP(response, request)
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@ func TestDynamicGatewayDeniesMethodsByDefault(t *testing.T) {
|
|||||||
gateway := &dynamicGateway{allow: map[string]struct{}{}}
|
gateway := &dynamicGateway{allow: map[string]struct{}{}}
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
engine := gin.New()
|
engine := gin.New()
|
||||||
engine.POST("/rpc/:method", gateway.handle)
|
engine.POST("/rpc/:module/:service/:method", gateway.handle)
|
||||||
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{}`))
|
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{}`))
|
||||||
response := httptest.NewRecorder()
|
response := httptest.NewRecorder()
|
||||||
engine.ServeHTTP(response, request)
|
engine.ServeHTTP(response, request)
|
||||||
if response.Code != http.StatusOK {
|
if response.Code != http.StatusOK {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func (s *Server) Start(grpcAddr, httpAddr string, allow []string) error {
|
|||||||
_ = httpListener.Close()
|
_ = httpListener.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.HTTP.POST("/rpc/:method", s.dynamic.handle)
|
s.HTTP.POST("/rpc/:module/:service/:method", s.dynamic.handle)
|
||||||
|
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
recorder := newBufferedResponse()
|
recorder := newBufferedResponse()
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"path"
|
||||||
|
|
||||||
"bsm/full/module/base/fts/internal/logic"
|
"bsm/full/module/base/fts/internal/logic"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
swaggerfiles "github.com/swaggo/files"
|
swaggerfiles "github.com/swaggo/files"
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(srvKey string, engine *gin.Engine) {
|
func Register(srvKey string, engine *gin.Engine) {
|
||||||
v1_key := fmt.Sprintf("/%s/%s", srvKey, "v1")
|
v1_key := path.Join("/rest", srvKey)
|
||||||
registerAnonymous(v1_key, engine)
|
registerAnonymous(v1_key, engine)
|
||||||
registerUploader(v1_key, engine)
|
registerUploader(v1_key, engine)
|
||||||
}
|
}
|
||||||
|
|||||||
34
module/base/fts/internal/routers/register_test.go
Normal file
34
module/base/fts/internal/routers/register_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRoutesUseRESTModulePrefix(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
engine := gin.New()
|
||||||
|
Register("fts", engine)
|
||||||
|
|
||||||
|
expected := map[string]bool{
|
||||||
|
"GET /rest/fts/v1/ping": false,
|
||||||
|
"GET /rest/fts/v1/config": false,
|
||||||
|
"POST /rest/fts/v1/uploader": false,
|
||||||
|
}
|
||||||
|
for _, route := range engine.Routes() {
|
||||||
|
key := route.Method + " " + route.Path
|
||||||
|
if _, ok := expected[key]; ok {
|
||||||
|
expected[key] = true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(route.Path, "/fts/") {
|
||||||
|
t.Fatalf("legacy route remains registered: %s", route.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for route, found := range expected {
|
||||||
|
if !found {
|
||||||
|
t.Errorf("route not registered: %s", route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package routers
|
package routers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
"bsm/full/module/base/mgt/internal/logic/application"
|
"bsm/full/module/base/mgt/internal/logic/application"
|
||||||
"bsm/full/module/base/mgt/internal/logic/department"
|
"bsm/full/module/base/mgt/internal/logic/department"
|
||||||
"bsm/full/module/base/mgt/internal/logic/hello"
|
"bsm/full/module/base/mgt/internal/logic/hello"
|
||||||
@@ -14,9 +16,9 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 完成请求地址: ip:port/srvKey/group/xxx
|
// 完成请求地址: ip:port/rest/srvKey/group/xxx
|
||||||
func Register(srvKey string, engine *gin.Engine) {
|
func Register(srvKey string, engine *gin.Engine) {
|
||||||
base := "/" + srvKey
|
base := path.Join("/rest", srvKey)
|
||||||
registerAnonymous(base, engine)
|
registerAnonymous(base, engine)
|
||||||
registerRouters(base, engine)
|
registerRouters(base, engine)
|
||||||
}
|
}
|
||||||
|
|||||||
35
module/base/mgt/internal/routers/register_test.go
Normal file
35
module/base/mgt/internal/routers/register_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRoutesUseRESTModulePrefix(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
engine := gin.New()
|
||||||
|
Register("mgt", engine)
|
||||||
|
|
||||||
|
expected := map[string]bool{
|
||||||
|
"GET /rest/mgt/ping": false,
|
||||||
|
"POST /rest/mgt/login": false,
|
||||||
|
"POST /rest/mgt/user/create": false,
|
||||||
|
"POST /rest/mgt/app/fetch": false,
|
||||||
|
}
|
||||||
|
for _, route := range engine.Routes() {
|
||||||
|
key := route.Method + " " + route.Path
|
||||||
|
if _, ok := expected[key]; ok {
|
||||||
|
expected[key] = true
|
||||||
|
}
|
||||||
|
if route.Path == "/mgt" || strings.HasPrefix(route.Path, "/mgt/") {
|
||||||
|
t.Fatalf("legacy route remains registered: %s", route.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for route, found := range expected {
|
||||||
|
if !found {
|
||||||
|
t.Errorf("route not registered: %s", route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user