开场
上一篇学习了Gin框架的总体流程,但是自己查看源代码中被许多的零散小功能影响了主体流程的研究。所以觉得可以模仿Gin框架,自己写一个最简单仅仅含有主流程的demo。可以让我们更加深入了解Gin自己也可以再模仿的过程中更加了解源码。
功能
这个demo是我自己对Gin初步理解之后模仿写的,其中只包含主要功能
- 创建路由
- 新建group
- 添加中间件
- 启动http服务,接受客户端请求并返回简单数据
Demo中的路由并没有使用Gin的tree只是简单的使用了map来实现,基数树是一个相对独立逻辑准备之后单独再学习
代码
package mygin
import (
"fmt"
"net/http"
"path"
"sync"
)
//上下文context 简单的他添加response request engine指针 isabort就可以支持最简单的流程
type Context struct {
Request *http.Request
ResponseWrite http.ResponseWriter
engine *Engine
isAbort bool
}
type HandlerFun func(ctx *Context)
type HandlerList []HandlerFun
type Engine struct {
RouterGroup
Handlers []HandlerFun
router map[string]HandlerList
pool sync.Pool
}
type Message struct {
Message string
}
type IRouter interface {
Use(...HandlerFun) IRouter
GET(string, ...HandlerFun) IRouter
Group(string, ...HandlerFun) *RouterGroup
}
type RouterGroup struct {
Handlers []HandlerFun
engine *Engine
basePath string
}
func NewEngine()(*Engine){
en := new(Engine)
en.router = make(map[string]HandlerList)
en.pool.New = func() interface{} {
return en.allocateContext()
}
en.RouterGroup = RouterGroup{
basePath:"/",
Handlers:nil,
engine:en,
}
return en
}
func (engine *Engine)Run(addr string)(err error){
fmt.Println("Listening and serving HTTP on", addr)
err = http.ListenAndServe(addr, engine)
return
}
//继承http包中的handler接口,在run中即可传入engine
func (engine *Engine)ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.ResponseWrite = w
c.Request = req
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
//客户端请求之后具体执行的函数 之前文章所说的获取所有handler 一个个执行
// 这里简单用了for循环 判断isabort属性来判断是否停止
func (engine *Engine) handleHTTPRequest(c *Context){
httpMethod := c.Request.Method
path := c.Request.URL.Path
if handlers,ok := engine.router[httpMethod + "^" + path];ok{
for _,fu := range handlers{
fu(c)
if c.isAbort{
return
}
}
}
}
func (engine *Engine) allocateContext() *Context{
return &Context{engine:engine}
}
func (engine *Engine)addRoute(httpMethod, absolutePath string, handlers HandlerList){
engine.router[httpMethod + "^" + absolutePath] = handlers
}
//添加group方法 设置group的basepath 和handler
func (routerGroup *RouterGroup)Group(path string,handlers ...HandlerFun) *RouterGroup{
rg := RouterGroup{}
rg.Handlers = routerGroup.CombineHandlers(handlers)
rg.basePath = path
rg.engine = routerGroup.engine
return &rg
}
func (routerGroup *RouterGroup)Use(handlers ...HandlerFun) IRouter{
routerGroup.Handlers = append(routerGroup.Handlers, handlers...)
return routerGroup
}
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
}
func joinPaths(absolutePath, relativePath string) string {
if relativePath == ""{
return absolutePath
}
finalPath := path.Join(absolutePath,relativePath)
appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath)!='/'
if appendSlash{
return finalPath + "/"
}
return finalPath
}
//工具方法 获取字符串最后一个字符
func lastChar(str string) uint8 {
if str ==""{
panic("The length of the string can't be 0")
}
return str[len(str)-1]
}
//计算路径合并handler 然后添加到map中
func (group *RouterGroup)handle(httpMethod, relativePath string, handlers HandlerList) IRouter{
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.CombineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group
}
//合并handler 之后返回
func (group *RouterGroup)CombineHandlers(handlers HandlerList)HandlerList{
finalSize := len(group.Handlers) + len(handlers)
mergedHandler := make(HandlerList, finalSize)
copy(mergedHandler, group.Handlers)
copy(mergedHandler[len(group.Handlers):], handlers)
return mergedHandler
}
//添加get method路由
func (group *RouterGroup)GET(path string, handlers ...HandlerFun)(IRouter){
group.handle("GET", path, handlers)
return group
}
测试
func TestEngine_Run(t *testing.T) {
router := NewEngine()
router.GET("/test", func(ctx *Context) {
fmt.Println("get request")
//这边可以尝试拿一下参数 在gin中获取参数提供了很多的方法
//这些不是主流程就没有在这里体现 有兴趣可以看一下源码其实也没有想象中的复杂
//这边就先获取一下get参数
pm := ctx.Request.URL.Query()
if v,ok := pm["id"];ok{
fmt.Println("request url", ctx.Request.URL.String()," parameter id value =",v)
}
ctx.ResponseWrite.WriteHeader(200)
r := render.JSON{Data:"success"}
r.WriteContentType(ctx.ResponseWrite)
if err := r.Render(ctx.ResponseWrite); err != nil{
panic(err)
}
})
router.Run(":2222")
}
结果
//在console中,得到了客户端的请求并打印参数
Listening and serving HTTP on :2222
get request
request url /test?id=2 parameter id value = [2]
//客户端请求之后获取到了 success 的返回值
http://localhost:2222/test?id=2
"success"
总结
这个demo大概100多行代码,仅仅实现了Gin最最小的功能。还有大部分的之前提到的功能都未实现,但是只是这100多行代码就已经能看出Gin的主要流程和主体思路。
有疑问加站长微信联系(非本文作者)