Go Gin source learning

Keywords: Go Attribute JSON

Start

In the previous article, I learned the overall process of the Gin framework, but I looked at the research of the main process in the source code which was affected by many scattered and small functions. So I think I can imitate the Gin framework and write a demo with only the main process. We can learn more about the source code in the process of Gin's own imitation.

function

This demo is imitated and written after my initial understanding of Gin. It only contains the main functions

  • Create routing
  • New group
  • Add Middleware
  • Start http service, accept client request and return simple data

The route in Demo is not implemented by using the Gin tree, but simply implemented by using the map. The cardinal tree is a relatively independent logical preparation and can be learned separately

Code

package mygin

import (
    "fmt"
    "net/http"
    "path"
    "sync"
)

//The context is simple. Adding the response request engine pointer isabort can support the simplest process
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
}

//Inherit the handler interface in http package, and pass it into engine in run
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)
}

//The function to be executed after the client request. As mentioned in the previous article, get all handler s to execute one by one
// Here we simply use the for loop to judge the isabort attribute to determine whether to stop
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
}

//Add group method to set basepath and handler of group
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
}

//Tool method gets the last character of the string
func lastChar(str string) uint8 {
    if str ==""{
        panic("The length of the string can't be 0")
    }

    return str[len(str)-1]
}

//Calculate path merge handler and add to 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
}

//Return after merging 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
}

//Add get method route
func (group *RouterGroup)GET(path string, handlers ...HandlerFun)(IRouter){
    group.handle("GET", path, handlers)

    return group
}

test

func TestEngine_Run(t *testing.T) {
    router := NewEngine()
    router.GET("/test", func(ctx *Context) {
        fmt.Println("get request")
        //There are many ways to get parameters in gin
        //These are not mainstream programs. They are not reflected here. If you are interested, you can see that the source code is not as complicated as you think
        //First, get the get parameter
        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")
}

Result

//In console, get the request from the client and print the parameters
Listening and serving HTTP on :2222
get request
request url /test?id=2  parameter id value = [2]
//The return value of success is obtained after the client request
http://localhost:2222/test?id=2
"success"

summary

This demo is about 100 lines of code, and only implements the smallest function of Gin. Most of the previously mentioned functions are not implemented, but only these 100 lines of code can see the main process and main idea of Gin.

Posted by onegative on Fri, 15 Nov 2019 07:47:11 -0800