路由注册

单条路由注册

gin: *gin.Engine.GET(relativePath string, handlers ...HandlerFunc)
irismvc: *iris.Application.Get(relativePath string, handlers ...context.Handler)

从单条路由注册上看,gin与iris方式几乎相同

分组路由注册

gin:
r := gin.Default()
gp := r.Group("/v1")
{
	gp.GET("/test", GetHandler)
	gp.POST("/test", PostHandler)
}


func PostHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"message": "POST",
	})
}

func GetHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"message": "GET",
	})
}
irismvc
mvc.Configure(app.Party("/v1")).Handle(controllers.NewMainController())


type MainController struct {
	Ctx iris.Context
}

func NewMainController() *MainController {
	return &MainController{}
}
func (m *MainController) GetTest() interface{} {
	return nil
}
func (m *MainController) PostTest() interface{} {
	return nil
}

可以看到,为了实现/v1/test的get与post的路由,gin的分组路由注册仍需一条条注册,而iris-mvc将子路由路径托管给controller的方法名。

在易用上,irismvc的方式更加简便,本篇介绍通过包装,使得gin的路由可以通过irismvc的方式,以子方法名称的方式自动注册到路由上。

gin路由自动注册

controller托管注册条件:

方法名称需符合MethodLocation的格式,例GetTest代表给test子路径注册get请求的路由

/controller/hello_controller.go

package controller

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

type HelloController struct {
}

func NewHelloController() *HelloController {
	return &HelloController{}
}
func (controller *HelloController) GetTest(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "GetTest",
		"data": nil,
	})
}
func (controller *HelloController) PostTest(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "PostTest",
		"data": nil,
	})
}
func (controller *HelloController) DeleteTest(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "DeleteTest",
		"data": nil,
	})
}
func (controller *HelloController) PutTest(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "PutTest",
		"data": nil,
	})
}

func (controller *HelloController) OptionsTest(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "OptionsTest",
		"data": nil,
	})
}
func GetTest(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "GetTest",
		"data": nil,
	})
}

/route/autoRoute.go

package route

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"reflect"
	"strings"
	"sync"
)

var methodsSync = struct {
	sync.RWMutex
	maps map[string]map[string]reflect.Value
}{maps: make(map[string]map[string]reflect.Value)}
var methods = struct {
	sync.RWMutex
	maps map[string]map[string]map[string]reflect.Value
}{maps: make(map[string]map[string]map[string]reflect.Value)}

func AutoRoute(engine *gin.Engine, relativePath string, controller interface{}) {
	if strings.HasSuffix(relativePath, "/") {
		relativePath = relativePath[1 : len(relativePath)-1]
	}
	if !strings.HasPrefix(relativePath, "/") {
		return
	}
	relativePath = relativePath + "/:action"
	engine.GET(relativePath, AutoHand(controller))
	engine.POST(relativePath, AutoHand(controller))
	engine.DELETE(relativePath, AutoHand(controller))
	engine.PUT(relativePath, AutoHand(controller))
	engine.OPTIONS(relativePath, AutoHand(controller))
}
func AutoHand(controller interface{}) gin.HandlerFunc {
	return func(c *gin.Context) {
		method := strings.Title(strings.ToLower(c.Request.Method))
		realAction := strings.ToLower(c.Param("action"))
		controllerType := reflect.TypeOf(controller)
		pkgName := controllerType.String()
		methods.RLock()
		_, hasMethod := methods.maps[method]
		methods.RUnlock()
		if !hasMethod {
			methods.Lock()
			methods.maps[method] = make(map[string]map[string]reflect.Value)
			methods.Unlock()
		}
		methods.RLock()
		value, hasRealAction := methods.maps[method][pkgName][realAction]
		methods.RUnlock()
		if !hasRealAction {
			methodslen := len(methods.maps[pkgName])
			switch methodslen {
			case 0:
				controllerValue := reflect.ValueOf(controller)
				methods.Lock()
				methods.maps[method][pkgName] = make(map[string]reflect.Value)
				methods.Unlock()
				for i := 0; i < reflect.ValueOf(controller).NumMethod(); i++ {
					subLocation := controllerType.Method(i).Name
					if strings.HasPrefix(subLocation, method) {
						methods.Lock()
						methods.maps[method][pkgName][strings.ToLower(strings.Replace(subLocation, method, "", 1))] = controllerValue.Method(i)
						methods.Unlock()
					}
				}
				methods.RLock()
				v, hasRealAction := methods.maps[method][pkgName][realAction]
				methods.RUnlock()
				value = v
				if !hasRealAction && (len(methods.maps[method]) > 0 || len(methods.maps[method][pkgName]) > 0) {
					http.NotFound(c.Writer, c.Request)
					c.Abort()
					return
				}
				break
			case 1:
				http.NotFound(c.Writer, c.Request)
				c.Abort()
				return
			}
		}
		value.Call([]reflect.Value{reflect.ValueOf(c)})
	}
}

/route/route.go

package route

import (
	"ginRouteAutoRegister/controller"
	"github.com/gin-gonic/gin"
	"net/http"
)

func Options(c *gin.Context) {
	if c.Request.Method != "OPTIONS" {
		c.Next()
	} else {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS")
		c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept")
		c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS")
		c.Header("Content-Type", "application/json")
		c.AbortWithStatus(http.StatusOK)
	}
}

func Secure(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")
	//c.Header("X-Frame-Options", "DENY")
	c.Header("X-Content-Type-Options", "nosniff")
	c.Header("X-XSS-Protection", "1; mode=block")
	if c.Request.TLS != nil {
		c.Header("Strict-Transport-Security", "max-age=31536000")
	}
}
func Route(engine *gin.Engine) {
	engine.Use(Options)
	engine.Use(Secure)
	//原生路由注册方式
	engine.GET("/hello", controller.GetTest)
	//自动注册方式
	AutoRoute(engine, "/api", controller.NewHelloController())
	AutoRoute(engine, "/v1/api2", controller.NewHelloController())
	//兼容原先gin拦截方式,自己写好LoginRequired,在LoginRequired之后的AutoRoute都会被拦截
	//engine.Use(LoginRequired)
	//AutoRoute(engine, "/filter", controller.NewHelloController())

}

/main.go

package main

import (
	"ginRouteAutoRegister/route"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	router := gin.New()
	router.Use(gin.Recovery())
	route.Route(router)
	srv := &http.Server{
		Addr:    ":8888",
		Handler: router,
	}

	go func() {
		// service connections
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	quit := make(chan os.Signal)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	//自定义退出时prestop逻辑,这里简单用等待5s
	time.Sleep(1 * time.Second)
	//ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	//defer cancel()
	//if err := srv.Shutdown(ctx); err != nil {
	//	log.Println("Server Shutdown: ", err)
	//}
	//select {
	//case <-ctx.Done():
	//	log.Println("timeout of 5 seconds.")
	//}
	log.Println("Server exiting")
}

简单性能测试

使用ab测试,在阿里云上简单以1W并发,10W条请求测试自动注册与原生注册响应时间

原生注册方式:

image

自动注册方式:

image-1648199571834

在性能方面,两者区别不大,甚至自动注册方式在.99情况下占优,因此对于1Wpqs的环境下也可以尝试使用此方案

附源码

https://github.com/260721735/ginRouteAutoRegister