go-gin框架路由自动注册(iris-mvc方式)附源码
0 条评论路由注册
单条路由注册
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条请求测试自动注册与原生注册响应时间
原生注册方式:
自动注册方式:
在性能方面,两者区别不大,甚至自动注册方式在.99情况下占优,因此对于1Wpqs的环境下也可以尝试使用此方案