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.