feat: standardize REST and RPC routes

This commit is contained in:
2026-08-11 18:55:36 +08:00
parent 53e953c3fc
commit eed82bcf38
8 changed files with 89 additions and 22 deletions

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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()