feat: standardize REST and RPC routes
This commit is contained in:
@@ -7,7 +7,7 @@ Server:
|
||||
BindIP: 0.0.0.0
|
||||
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
|
||||
# reflection-visible unary methods, or list full service/method names.
|
||||
DynamicRPC:
|
||||
|
||||
@@ -70,12 +70,15 @@ func reflectionTarget(addr string) string {
|
||||
func (g *dynamicGateway) Close() error { return g.conn.Close() }
|
||||
|
||||
func (g *dynamicGateway) handle(c *gin.Context) {
|
||||
fullMethod := strings.TrimSpace(c.Param("method"))
|
||||
serviceName, methodName, ok := splitFullMethod(fullMethod)
|
||||
if !ok {
|
||||
writeDynamicError(c, status.Error(codes.InvalidArgument, "method must be {full.service}.{method}"))
|
||||
moduleName := strings.TrimSpace(c.Param("module"))
|
||||
serviceShortName := strings.TrimSpace(c.Param("service"))
|
||||
methodName := strings.TrimSpace(c.Param("method"))
|
||||
if moduleName == "" || serviceShortName == "" || methodName == "" {
|
||||
writeDynamicError(c, status.Error(codes.InvalidArgument, "path must be /rpc/{module}/{service}/{method}"))
|
||||
return
|
||||
}
|
||||
serviceName := moduleName + "." + serviceShortName
|
||||
fullMethod := serviceName + "." + methodName
|
||||
if !g.isAllowed(serviceName, fullMethod) {
|
||||
writeDynamicError(c, status.Error(codes.PermissionDenied, "dynamic RPC method is not allowed"))
|
||||
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})
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, key := range []string{"*", serviceName, fullMethod} {
|
||||
if _, ok := g.allow[key]; ok {
|
||||
|
||||
@@ -41,8 +41,8 @@ func TestDynamicGatewayInvokesUnaryRPC(t *testing.T) {
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
engine := gin.New()
|
||||
engine.POST("/rpc/:method", gateway.handle)
|
||||
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{"service":""}`))
|
||||
engine.POST("/rpc/:module/:service/:method", gateway.handle)
|
||||
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{"service":""}`))
|
||||
response := httptest.NewRecorder()
|
||||
engine.ServeHTTP(response, request)
|
||||
|
||||
@@ -65,8 +65,8 @@ func TestDynamicGatewayDeniesMethodsByDefault(t *testing.T) {
|
||||
gateway := &dynamicGateway{allow: map[string]struct{}{}}
|
||||
gin.SetMode(gin.TestMode)
|
||||
engine := gin.New()
|
||||
engine.POST("/rpc/:method", gateway.handle)
|
||||
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{}`))
|
||||
engine.POST("/rpc/:module/:service/:method", gateway.handle)
|
||||
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{}`))
|
||||
response := httptest.NewRecorder()
|
||||
engine.ServeHTTP(response, request)
|
||||
if response.Code != http.StatusOK {
|
||||
|
||||
@@ -52,7 +52,7 @@ func (s *Server) Start(grpcAddr, httpAddr string, allow []string) error {
|
||||
_ = httpListener.Close()
|
||||
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) {
|
||||
recorder := newBufferedResponse()
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"bsm/full/module/base/fts/internal/logic"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"bsm/full/module/base/mgt/internal/logic/application"
|
||||
"bsm/full/module/base/mgt/internal/logic/department"
|
||||
"bsm/full/module/base/mgt/internal/logic/hello"
|
||||
@@ -14,9 +16,9 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 完成请求地址: ip:port/srvKey/group/xxx
|
||||
// 完成请求地址: ip:port/rest/srvKey/group/xxx
|
||||
func Register(srvKey string, engine *gin.Engine) {
|
||||
base := "/" + srvKey
|
||||
base := path.Join("/rest", srvKey)
|
||||
registerAnonymous(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