Http服务器

Golang有一个很强大的官方http库了,使用上很方便。也有一些很强大的三方http框架, 比如gin,beego等等。 自以为,框架在提供便利,提升开发效率的同时,也限制了我们的想象力。 相对beego而言我更喜欢gin这样的简单高效框架,给我们提供了一些基础设施。剩下的有什么功能需要自己写进去就好了。

在开发过程中,我需要有个注解路由的功能,也就是不用手动的把路由和对应的函数一个一个手写进行绑定,百度无果后,发现gin没有提供这样的功能。于是有了下文。

gin的注解路由

gin是没有注解路由功能的,但是使用golang的reflect包,加一点代码就能实现,代码如下

golang
package net

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

type HttpServer struct {
	version string
	port []string

	Handler interface{}
}

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

func NewHttpServer(version string, port ...string) *HttpServer {
	return &HttpServer{version: version, port: port}
}

func  (h *HttpServer)HandlerFuncObj(tvl, obj reflect.Value) gin.HandlerFunc {
	return func(c *gin.Context) {
		v := tvl.Call([]reflect.Value{obj, reflect.ValueOf(c)})
		if len(v) != 2 {
			c.JSON(http.StatusNotFound, gin.H{"code": -100, "data": ""})
			return
		}
		c.JSON(http.StatusOK, gin.H{"code": v[0].Interface(), "data": v[1].Interface()})
	}
}

func GetRoutePath(objName, objFunc string) string  {
	return strings.ToLower(objName + "/" + objFunc)
}

func (h *HttpServer) BindHandler(handler interface{})  {
	h.Handler = handler
}

func (h *HttpServer) Start() error {
	//gin初始化
	r := gin.Default()
	r.GET("/ping", Pong)
	typ := reflect.TypeOf(h.Handler)
	val := reflect.ValueOf(h.Handler)
	//t := reflect.Indirect(val).Type()
	//objectName := t.Name()

	numOfMethod := val.NumMethod()
	for i := 0; i < numOfMethod; i++ {
		method := typ.Method(i)
		r.GET(GetRoutePath(h.version, method.Name), h.HandlerFuncObj(method.Func, val))
	}
	return r.Run(h.port...) // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")

主要逻辑在 h.Start()

  • h.Handler 是从外部绑定过来的Handler,也就是逻辑处理的Handler
  • typ := reflect.TypeOf(h.Handler) 反射Handler类型
  • val := reflect.ValueOf(h.Handler) 反射Handler的reflect.Value
  • val.NumMethod(): 获取Handler的函数个数
  • for i := 0; i < numOfMethod; i++ {}: 遍历获得函数, method := typ.Method(i)
  • 根据 版本号+函数名 得到路由,用函数h.HandlerFuncObj(method.Func, val)得到 gin.HandlerFunc
  • 把路由 ip:port/version/methodName 绑定到 medhod.Func 上。

主要函数HandlerFuncObj(), 把需要绑定的函数转化为gin.HandlerFunc

func  (h *HttpServer)HandlerFuncObj(tvl, obj reflect.Value) gin.HandlerFunc {
	return func(c *gin.Context) {
		v := tvl.Call([]reflect.Value{obj, reflect.ValueOf(c)})
		if len(v) != 2 {
			c.JSON(http.StatusNotFound, gin.H{"code": -100, "data": ""})
			return
		}
		c.JSON(http.StatusOK, gin.H{"code": v[0].Interface(), "data": v[1].Interface()})
	}
}
  • tvl reflect.Value: 实际上是method.Func,

  • obj reflect.Value: 实际上是reflect.ValueOf(h.Handler)

  • reflect.Call用法: 函数用于使用输入参数in调用函数v; func (v Value) Call(in []Value) []Value

使用Http服务器

声明一个结构体 HttpAction struct, 成员函数名字的小写就是路由,函数就是路由对应逻辑处理的地方。

golang
package main

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

type HttpAction struct {
}

func (h *HttpAction) PrintA(c *gin.Context) (int, interface{}) {
	return 0, "I'm A"
}
func (h *HttpAction) PrintB(c *gin.Context) (int, interface{}) {
	return 0, "I'm B"
}

func main() {
	web := NewHttpServer("v1")
	web.BindHandler(&HttpAction{})
	web.Start()
}

  • 逻辑处理只用写在HttpAction的成员函数中
  • 不用一个一个手动的把路由和函数进行绑定。

至此,一个结合gin框架的注解路由就写好了。

--完--