Go Gin源码学习(二)

大二小的宝 · · 806 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

开场

上一篇学习了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的主要流程和主体思路。


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:大二小的宝

查看原文:Go Gin源码学习(二)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

806 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传