教你如何搭建自己的go-gin框架

1、使用http包默认启动web服务

Go语言内置了 net/http库,封装了HTTP网络编程的基础的接口,我们实现的ebb Web 框架便是基于net/http的。我们接下来通过一个例子,简单介绍下这个库的使用。

go mode init explame
package main

import (
    "fmt"
    "net/http"
)

func indexHandler(w http.ResponseWriter,r *http.Request){
    fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}

func helloHandler(w http.ResponseWriter,r *http.Request){
    fmt.Fprintf(w,"URL.PATH=%q\n Header=%q\n",r.URL.Path,r.Header)
}

func main(){
    http.HandleFunc("/",indexHandler)
    http.HandleFunc("/hello",helloHandler)
    http.ListenAndServe(":8080",nil)
}
go build .
./examplde.exe 

启动web服务
访问 127.0.0.1:8080/   
访问 127.0.0.1:8080/hello

main 函数的最后一行,是用来启动 Web 服务的,第一个参数是地址,:9999表示在 9999 端口监听。而第二个参数则代表处理所有的HTTP请求的实例,nil 代表使用标准库中的实例处理。第二个参数,则是我们基于net/http标准库实现Web框架的入口

2、接下来我们需要自己实现

  • 新建examplde2目录
  • 新建ebb目录
  • 在ebb目录 执行 go mod init ebb
package ebb

import (
    "net/http"
    "log"
)

//定义框架请求处理方法
type HandlerFunc func(w http.ResponseWriter,req *http.Request)


//核心结构体
type Engine struct{
    router map[string]HandlerFunc //简单使用map记录路由信息
}

//实例化结构体
func New() *Engine{
    engine := &Engine{
        router : make(map[string]HandlerFunc),
    }
    return engine
}

//添加到结构体路由
func (engine *Engine) addRoute(mothod string,pattern string,handler HandlerFunc){
    key := mothod+"-"+pattern
    engine.router[key] = handler
}

func (engine *Engine) GET(pattern string,handler HandlerFunc){
    engine.addRoute("GET",pattern,handler)
}

func (engine *Engine) POST(pattern string,handler HandlerFunc){
    engine.addRoute("POST",pattern,handler)
}

//启动服务
func (engine *Engine) Run(addr string) (err error){
    return http.ListenAndServe(addr,engine)
}

//engine 实现ServeHTTP接口(所有的请求都会走到这)
//查找是否路由映射表存在,如果存在则调用,否则返回404
func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){

    key := req.Method + "-" + req.URL.Path
    if handler, ok := engine.router[key]; ok {
        handler(w, req)
    } else {
        log.Printf("404 NOT FOUND: %s\n", req.URL)
    }
}
  • 在exmpale2目录下新建main.go 并且执行 go mod init example

  • 编辑go.mod(在 go.mod 中使用 replace 将 ebb 指向 ./ebb)

module example

go 1.14

require ebb v0.0.0

replace ebb => ./ebb
  • 编辑main.go
package main

import (
    "ebb"
    "fmt"
    "net/http"
)

func main(){
    r := ebb.New()

    r.GET("/index",func(w http.ResponseWriter,req *http.Request){
        fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
    })

    r.GET("/hello", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintln(w, req.Header)
    })

    r.Run(":8080")
}

框架流程

  • 定义HandlerFunc(框架的请求方法)
  • 定义核心结构体Engine 目前只存储路由信息,路由信息由映射表实现(路由只支持静态路由)
  • GET、POST提供路由注册
  • Run封装http.ListenAndServe
  • 核心结构体Engine实现ServeHTTP接口(所有请求),查找路由并执行

到目前为止,一个简易的框架已经OK、后续我们会将HandlerFunc方法优化,也就是Context.期待下一篇文章

封装context

简单说下本章的重点

抽离路由

  • 新建router.go
 package ebb

type router struct{
    handlers map[string]HandlerFunc
}

func newRouter() *router{
    return &router{handlers:make(map[string]HandlerFunc)}
}

func (r *router) addRoute(method string,pattern string,handler HandlerFunc){
    key := method+"-"+pattern
    r.handlers[key] = handler
}

设计Context的必要性

  • 对外接口简化调用 (封装了writer和request)
  • 提供了扩展性(比如支持动态参数、支持中间件等等)
package ebb


import (
    "net/http"
    "fmt"
    "encoding/json"
)

type H map[string]interface{}

type Context struct{
    //write and request
    Writer http.ResponseWriter
    Request *http.Request
    //request info
    Method string
    Path string
    //response
    HttpCode int
}


func newContext(w http.ResponseWriter,r *http.Request) *Context{
    context := &Context{
        Writer:w,
        Request:r,
        Path:   r.URL.Path,
        Method: r.Method,
    }

    return context
} 

func (c *Context) PostForm(key string) string {
    return c.Request.FormValue(key)
}

func (c *Context) Query(key string) string {
    return c.Request.URL.Query().Get(key)
}

func (c *Context) Status(code int) {
    c.HttpCode = code
    c.Writer.WriteHeader(code)
}

func (c *Context) SetHeader(key string, value string) {
    c.Writer.Header().Set(key, value)
}

func (c *Context) Write(data []byte){
    c.Writer.Write(data)
}


func (c *Context) String(code int,message string,v ...interface{}){
    c.SetHeader("Content-Type", "text/plain")
    c.Status(code)
    c.Write([]byte(fmt.Sprintf(message, v...)))
}

func (c *Context) JSON(code int, obj interface{}) {
    c.SetHeader("Content-Type", "application/json")
    c.Status(code)
    data,err:= json.Marshal(obj)
    if err!=nil {
        http.Error(c.Writer, err.Error(), 500)
    }
    c.Write(data)
}

func (c *Context) HTML(code int,html string){
    c.SetHeader("Content-Type", "text/html")
    c.Status(code)
    c.Write([]byte(html))
}
  • 修改ebb.go
package ebb

import (
    "net/http"
)

//重新定义框架请求处理方法
type HandlerFunc func(*Context)

//核心结构体
type Engine struct{
    router *router 
}

//实例化结构体
func New() *Engine{
    engine := &Engine{
        router : newRouter(),
    }
    return engine
}

//添加到结构体路由
func (engine *Engine) addRoute(mothod string,pattern string,handler HandlerFunc){
    engine.router.addRoute(mothod,pattern,handler)
}

func (engine *Engine) GET(pattern string,handler HandlerFunc){
    engine.addRoute("GET",pattern,handler)
}

func (engine *Engine) POST(pattern string,handler HandlerFunc){
    engine.addRoute("POST",pattern,handler)
}

//启动服务
func (engine *Engine) Run(addr string) (err error){
    return http.ListenAndServe(addr,engine)
}

//engine 实现ServeHTTP接口(所有的请求都会走到这)
//查找是否路由映射表存在,如果存在则调用,否则返回404
func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){
    c := newContext(w, req)
    engine.handleHTTPRequest(c)
}

//v2 新增
func (engine *Engine) handleHTTPRequest(c *Context){
    key := c.Method + "-" + c.Path
    if handler, ok := engine.router.handlers[key]; ok {
        handler(c)
    } else {
        c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
    }
}

入口文件应用

package main

import (
    "ebb"
)


func main(){
    r := ebb.New()

    r.GET("/", func(c *ebb.Context) {
        c.HTML(200, "<h1>Hello ebb</h1>")
    })
    r.GET("/hello", func(c *ebb.Context) {
        c.String(200, "hello %s, you from %s\n", c.Query("name"), c.Path)
    })

    r.POST("/login", func(c *ebb.Context) {
        c.JSON(200, ebb.H{
            "name": c.PostForm("name"),
        })
    })


    r.Run(":8080")
}

动态路由设计

简单说下本章的重点

1、Trie 前缀树实现

package ebb

import (
    //"fmt"
    "strings"
)

type node struct {
    pattern  string // 待匹配路由,例如 /p/:lang
    part     string // 路由中的一部分,例如 :lang
    children []*node // 子节点,例如 [doc, tutorial, intro]
    isWild   bool // 是否精确匹配,part 含有 : 或 * 时为true
}

func (n *node) matchChild(part string) *node {
    for _, child := range n.children {
        if child.part == part || child.isWild {
            return child
        }
    }
    return nil
}
// 所有匹配成功的节点,用于查找
func (n *node) matchChildren(part string) []*node {
    nodes := make([]*node, 0)
    for _, child := range n.children {
        if child.part == part || child.isWild {
            nodes = append(nodes, child)
        }
    }
    return nodes
}

func parsePattern(pattern string) []string {
    vs := strings.Split(pattern, "/")
    parts := make([]string, 0)
    for _, item := range vs {
        if item != "" {
            parts = append(parts, item)
            if item[0] == '*' {
                break
            }
        }
    }

    return parts
}


func (n *node) insert(pattern string, parts []string, height int) {
    if len(parts) == height {
        n.pattern = pattern
        return
    }

    part := parts[height]
    child := n.matchChild(part)
    if child == nil {
        child = &node{part: part, isWild: part[0] == ':' || part[0] == '*'}
        n.children = append(n.children, child)
    }
    child.insert(pattern, parts, height+1)
}

func (n *node) search(parts []string, height int) *node {
    if len(parts) == height || strings.HasPrefix(n.part, "*") {
        if n.pattern == "" {
            return nil

        }
        return n
    }

    part := parts[height]
    children := n.matchChildren(part)

    for _, child := range children {
        result := child.search(parts, height+1)
        if result != nil {
            return result
        }
    }

    return nil
}

2、优化路由类

package ebb

import (
    "strings"
)

type router struct{
    roots map[string]*node
    handlers map[string]HandlerFunc
}

func newRouter() *router{
    return &router{
        roots:make(map[string]*node),
        handlers:make(map[string]HandlerFunc),
    }
}

func (r *router) addRoute(method string,pattern string,handler HandlerFunc){
    parts := parsePattern(pattern)
    key := method + "-" + pattern
    _, ok := r.roots[method]
    if !ok {
        r.roots[method] = &node{}
    }
    r.roots[method].insert(pattern, parts, 0)
    r.handlers[key] = handler
}

func (r *router) getRoute(method string, pattern string) (*node, map[string]interface{}) {
    searchParts := parsePattern(pattern)
    params := make(map[string]interface{})
    root, ok := r.roots[method]

    if !ok {
        return nil, nil
    }

    n := root.search(searchParts, 0)

    if n != nil {
        parts := parsePattern(n.pattern)
        for index, part := range parts {
            if part[0] == ':' {
                params[part[1:]] = searchParts[index]
            }
            if part[0] == '*' && len(part) > 1 {
                params[part[1:]] = strings.Join(searchParts[index:], "/")
                break
            }
        }
        return n, params
    }

    return nil, nil
}

3、优化核心ebb的handleHTTPRequest

func (engine *Engine) handleHTTPRequest(c *Context){
    node,params := engine.router.getRoute(c.Method,c.Path)
    if node != nil {
        c.Params = params //将params的值存储到Context
        key := c.Method + "-" + node.pattern
        //调用实际的方法
        engine.router.handlers[key](c)
    }else{
        c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
    }
}

4、优化Context将动态参数存储以及获取

package ebb


import (
    "net/http"
    "fmt"
    "encoding/json"
)

type H map[string]interface{}

type Context struct{
    //write and request
    Writer http.ResponseWriter
    Request *http.Request
    //request info
    Method string
    Path string
    Params map[string]interface{}
}


func newContext(w http.ResponseWriter,r *http.Request) *Context{
    context := &Context{
        Writer:w,
        Request:r,
        Path:   r.URL.Path,
        Method: r.Method,
        Params: make(map[string]interface{}),
    }

    return context
} 

func (c *Context) Param(key string) string{
    value, _ := c.Params[key].(string)
    return value
}

func (c *Context) PostForm(key string) string {
    return c.Request.FormValue(key)
}

func (c *Context) Query(key string) string {
    return c.Request.URL.Query().Get(key)
}

func (c *Context) Status(code int) {
    c.Writer.WriteHeader(code)
}

func (c *Context) SetHeader(key string, value string) {
    if value == "" {
        c.Writer.Header().Del(key)
        return
    }
    c.Writer.Header().Set(key, value)
}

func (c *Context) GetHeader(key string) string{
    return c.Request.Header.Get(key)
}

func (c *Context) Write(data []byte){
    c.Writer.Write(data)
}


func (c *Context) String(code int,message string,v ...interface{}){
    c.SetHeader("Content-Type", "text/plain")
    c.Status(code)
    c.Write([]byte(fmt.Sprintf(message, v...)))
}

func (c *Context) JSON(code int, obj interface{}) {
    c.SetHeader("Content-Type", "application/json")
    c.Status(code)
    data,err:= json.Marshal(obj)
    if err!=nil {
        http.Error(c.Writer, err.Error(), 500)
    }
    c.Write(data)
}

func (c *Context) HTML(code int,html string){
    c.SetHeader("Content-Type", "text/html")
    c.Status(code)
    c.Write([]byte(html))
}

5、编写单元测试

package ebb

import (
    "net/http/httptest"
    "testing"
)


func PerformRequest(mothod string,url string ,body io.Reader,e *Engine) (w *httptest.ResponseRecorder){
    w = httptest.NewRecorder()
    r := httptest.NewRequest(mothod, url, body)
    r.Header.Set("Content-Type", "application/json")
    e.ServeHTTP(w,r)
    return w
}
package ebb


import (
    "testing"
    "bytes"
    "encoding/json"
    "github.com/stretchr/testify/assert"
)

func TestGetRoute(t *testing.T) {
    r := newRouter()
    r.addRoute("GET", "/", nil)
    r.addRoute("GET", "/hello/:name", nil)
    r.addRoute("GET", "/hello/b/c", nil)
    n, params := r.getRoute("GET", "/index/baibai")

    if assert.NotNil(t, n,"404 not found ") {
        assert.Equal(t,n.pattern,"/hello/:name","should match /hello/:name")
        assert.Equal(t,params["name"],"baibai","name should be equel to 'baibai'")
    }

}
func TestParsePattern(t *testing.T) {
    assert.Equal(t,parsePattern("/p/:name"),[]string{"p",":name"},"not parsePattern :name")
    assert.Equal(t,parsePattern("/p/*"),[]string{"p","*"},"not parsePattern *")
    assert.Equal(t,parsePattern("/p/*name/*"),[]string{"p","*name"},"parsePattern not truncation")
}
func TestRouter(t *testing.T){
    r := New()

    r.POST("/login/*filepath", func(c *Context) {
        c.JSON(200, H{
            "name": c.PostForm("name"),
        })
    })

    param := `{"name":"56789","state":3}`

    w := PerformRequest("POST","/login/123213?name=1233",bytes.NewBufferString(param),r)

    s := struct{
        Name string `json:"name"`
    }{}
    json.Unmarshal([]byte(w.Body.String()),&s)
    assert.Equal(t,s.Name,"1233","PostForm error")
}

6、在本章节我们主要优化了路由,并支持动态参数

接下来我们会陆续实现路由的分组、中间件等等

路由分组

简单说下本章的重点

1、路由分组的概念

如果没有路由分组,我们需要针对每一个路由进行控制。但是真实的业务场景中,往往某一组路由需要相似的处理,大部分的分组是由前缀来区分的、并且可以支持分组的嵌套

2、定义路由分组

package ebb

type RouterGroup struct {
    prefix      string
    middlewares []HandlerFunc // support middleware
    parent      *RouterGroup  // support nesting
    engine      *Engine       // all groups share a Engine instance
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
    engine := group.engine
    newGroup := &RouterGroup{
        prefix: group.prefix + prefix,//支持分组嵌套
        parent: group,
        engine: engine,
    }
    engine.groups = append(engine.groups, newGroup)
    return newGroup
}

func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) {
    pattern := group.prefix + comp
    group.engine.router.addRoute(method, pattern, handler)
}

// GET defines the method to add GET request
func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
    group.addRoute("GET", pattern, handler)
}

// POST defines the method to add POST request
func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
    group.addRoute("POST", pattern, handler)
}

2、优化engine结构

//核心结构体
type Engine struct{
    *RouterGroup //v4新增  顶级路由组
    router *router 
    groups []*RouterGroup // v4新增
}

//实例化结构体
func New() *Engine{
    engine := &Engine{
        router : newRouter(),
    }
    engine.RouterGroup = &RouterGroup{engine: engine}
    engine.groups = []*RouterGroup{engine.RouterGroup}
    return engine
}

3、单元测试

package ebb


import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestRouteGroup(t *testing.T) {
    r := New()
    v2 := r.Group("/v2")
    {
        v2.GET("/hello/:name", func(c *Context) {
            // expect /hello/ebbktutu
            c.String(200, "hello %s, you're at %s\n", c.Param("name"), c.Path)
        })
        v2.POST("/login", func(c *Context) {
            c.JSON(200, H{
                "username": c.PostForm("username"),
                "password": c.PostForm("password"),
            })
        })

    }
    n, params := r.router.getRoute("GET", "v2/hello/baibai")
    if assert.NotNil(t, n,"404 not found ") {
        assert.Equal(t,n.pattern,"/v2/hello/:name","should match /hello/:name")
        assert.Equal(t,params["name"],"baibai","name should be equel to 'baibai'")
    }

}

4、分组使用案例

package main

import (
    "ebb"
)


func main(){
    r := ebb.New()

    r.GET("/index", func(c *ebb.Context) {
        c.HTML(200, "<h1>Index Page</h1>")
    })
    v1 := r.Group("/v1")
    {
        v1.GET("/", func(c *ebb.Context) {
            c.HTML(200, "<h1>Hello ebb</h1>")
        })

        v1.GET("/hello", func(c *ebb.Context) {
            c.String(200, "hello %s, you're at %s\n", c.Query("name"), c.Path)
        })
    }
    v2 := r.Group("/v2")
    {
        v2.GET("/hello/:name", func(c *ebb.Context) {
            // expect /hello/ebbktutu
            c.String(200, "hello %s, you're at %s\n", c.Param("name"), c.Path)
        })
        v2.POST("/login", func(c *ebb.Context) {
            c.JSON(200, ebb.H{
                "username": c.PostForm("username"),
                "password": c.PostForm("password"),
            })
        })

    }    
    r.Run(":8080")
}

5、本章节我们实现了路由分组(路由加前缀)

接下来到了重头戏,中间件的实现以及具体案例

路由支持中间件

简单说下本章的重点

1、重点

  • 中间件的目的是为了让web框架灵活支持用户某些自定义行为,对外开放一个接口
  • 核心的点在什么时候插入、以及中间件传入的支持参数

2、思路

  • 1、作用于每一条路由规则
  • 2、作用于Context、依次执行该路由说匹配的中间件
  • 3、中间件也和控制器层一样传入Context、接入点是框架接收到请求初始化Context对象后,允许用户使用自己定义的中间件做一些额外的处理、另外通过调用(*Context).Next()函数,中间件可等待用户自己定义的 Handler处理结束后,再做一些额外的操作

3、Context支持

package ebb


import (
    "net/http"
    "fmt"
    "encoding/json"
)


type Context struct{
    //write and request
    Writer http.ResponseWriter
    Request *http.Request
    //request info
    Method string
    Path string
    Params map[string]interface{}
    HttpCode int
    //middleware  新增
    handlers HandlersChain
    index    int
}


func newContext(w http.ResponseWriter,r *http.Request) *Context{
    context := &Context{
        Writer:w,
        Request:r,
        Path:   r.URL.Path,
        Method: r.Method,
        Params: make(map[string]interface{}),
        index:  -1,
    }

    return context
} 

//本次新增
func (c *Context) Next() {
    c.index++
    for c.index < len(c.handlers) {
        c.handlers[c.index](c)
        c.index++
    }
}

4、routerGroup支持

package ebb

type HandlersChain []HandlerFunc //新增

type RouterGroup struct {
    prefix      string
    middlewares HandlersChain // support middleware
    parent      *RouterGroup  // support nesting
    engine      *Engine       // all groups share a Engine instance
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
    engine := group.engine
    newGroup := &RouterGroup{
        prefix: group.prefix + prefix,//支持分组嵌套
        parent: group,
        engine: engine,
    }
    engine.groups = append(engine.groups, newGroup)
    return newGroup
}

//添加中间件 v5新增
func (group *RouterGroup) Use(middlewares ...HandlerFunc){
    group.middlewares = append(group.middlewares, middlewares...)
}

//合并handlers
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
    finalSize := len(group.middlewares) + len(handlers)
    mergedHandlers := make(HandlersChain, finalSize)
    copy(mergedHandlers, group.middlewares)
    copy(mergedHandlers[len(group.middlewares):], handlers)
    return mergedHandlers
}

func (group *RouterGroup) addRoute(method string, comp string, handler ...HandlerFunc) {
    pattern := group.prefix + comp
        //添加的路由的时候将中间件合并到 路由handles
    handlers := group.combineHandlers(handler)
    group.engine.router.addRoute(method, pattern, handlers)
}

// GET defines the method to add GET request
func (group *RouterGroup) GET(pattern string, handler ...HandlerFunc) {
    group.addRoute("GET", pattern, handler...)
}

// POST defines the method to add POST request
func (group *RouterGroup) POST(pattern string, handler ...HandlerFunc) {
    group.addRoute("POST", pattern, handler...)
}

5、路由支持

package ebb


import (
    "strings"
)

type router struct{
    roots map[string]*node
    handlers map[string]HandlersChain  //新增
}

func newRouter() *router{
    return &router{
        roots:make(map[string]*node),
        handlers:make(map[string]HandlersChain),
    }
}

func (r *router) addRoute(method string,pattern string,handlers HandlersChain){
    parts := parsePattern(pattern)
    key := method + "-" + pattern
    _, ok := r.roots[method]
    if !ok {
        r.roots[method] = &node{}
    }
    r.roots[method].insert(pattern, parts, 0)
    r.handlers[key] = append(r.handlers[key],handlers...) //合并中间件和handlers
}

6、ebb支持

package ebb

import (
    "net/http"
)

//定义框架请求处理方法
type HandlerFunc func(*Context)

//核心结构体
type Engine struct{
    *RouterGroup //v4新增  顶级路由组
    router *router 
    groups []*RouterGroup // v4新增
}

//实例化结构体
func New() *Engine{
    engine := &Engine{
        router : newRouter(),
    }
    engine.RouterGroup = &RouterGroup{engine: engine}
    engine.groups = []*RouterGroup{engine.RouterGroup}
    return engine
}

//新增
func Default() *Engine{
    engine := New()
    engine.Use(Logger(),Recovery())
    return engine
}

//启动服务
func (engine *Engine) Run(addr string) (err error){
    return http.ListenAndServe(addr,engine)
}

//engine 实现ServeHTTP接口(所有的请求都会走到这)
//查找是否路由映射表存在,如果存在则调用,否则返回404
func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){
    c := newContext(w, req)
    engine.handleHTTPRequest(c)
}

//v2 新增
func (engine *Engine) handleHTTPRequest(c *Context){
    node,params := engine.router.getRoute(c.Method,c.Path)
    if node != nil {
        c.Params = params
        key := c.Method + "-" + node.pattern
        //赋值
        c.handlers = engine.router.handlers[key]
    }else{
        c.handlers = append(c.handlers, func(c *Context) {
            c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
        })
    }
        //真正调度执行中间件以及具体handler
    c.Next()
}

7、新增logger中间件

package ebb

import (
    "time"
    "log"
)

func Logger() HandlerFunc {
    return func(c *Context) {
        // Start timer
        t := time.Now()
        // Process request
        c.Next()
        // Calculate resolution time
        log.Printf("[%d] %s in %v", c.HttpCode, c.Request.RequestURI, time.Since(t))
    }
}

8、新增错误异常中间件

package ebb


import (
    "fmt"
    "log"
    "net/http"
    "runtime"
    "strings"
)

// print stack trace for debug
func trace(message string) string {
    var pcs [32]uintptr
    n := runtime.Callers(3, pcs[:]) // skip first 3 caller

    var str strings.Builder
    str.WriteString(message + "\nTraceback:")
    for _, pc := range pcs[:n] {
        fn := runtime.FuncForPC(pc)
        file, line := fn.FileLine(pc)
        str.WriteString(fmt.Sprintf("\n\t%s:%d", file, line))
    }
    return str.String()
}

func Recovery() HandlerFunc {
    return func(c *Context) {
        defer func() {
            if err := recover(); err != nil {
                message := fmt.Sprintf("%s", err)
                log.Printf("%s\n\n", trace(message))
                c.JSON(http.StatusInternalServerError, "Internal Server Error")
            }
        }()
        c.Next()
    }
}

9、使用案例

package main

import (
    "ebb"
)


func main(){
    r := ebb.Default()
    r.GET("/panic",func(c *ebb.Context) {
        panic("err")
    })

    r.POST("/login/*name",func(c *ebb.Context) {
        c.JSON(200, ebb.H{
            "name": c.Param("name"),
        })
    })
    r.Run(":8080")
}

到目前为止,框架的基本架构已确立,后续就是一些补充性的功能,以及优化点 看起来是不是和gin框架非常类似 下一章节我们实现对go模板的使用封装

模板处理

简单说下本章的重点

1、支持静态服务器

func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
    absolutePath := joinPaths(group.prefix, relativePath)
    fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
    return func(c *Context) {
        file := c.Param("filepath")
        // Check if file exists and/or if we have permission to access it
        f, err := fs.Open(file);
        defer f.Close()
        if  err != nil {
            c.Status(http.StatusNotFound)
            return
        }
        fileServer.ServeHTTP(c.Writer, c.Request)
    }
}

// serve static files
func (group *RouterGroup) Static(relativePath string, root string) {
    handler := group.createStaticHandler(relativePath, http.Dir(root))
    urlPattern := joinPaths(relativePath, "/*filepath")
    // Register GET handlers
    group.GET(urlPattern, handler)
}


func joinPaths(absolutePath, relativePath string) string {
    if relativePath == "" {
        return absolutePath
    }
    finalPath := path.Join(absolutePath, relativePath)
    return finalPath
}

2、单元测试

func TestStaticFile(t *testing.T){
    r := Default()
    r.Static("/assets", "./")
    w := PerformRequest("GET","/assets/go.mod",bytes.NewBufferString(""),r)
    fmt.Printf(w.Body.String())
}

3、模板渲染

type Engine struct{
    *RouterGroup //v4新增  顶级路由组
    router *router 
    groups []*RouterGroup // v4新增
    //html/template
    HTMLRender  *template.Template
    HTMLdelimsLeft string
    HTMLdelimsRight string
    funcMap     template.FuncMap
}

func (engine *Engine) SetFuncMap(funcName string,f interface{}) {
    funcMap := template.FuncMap{
        funcName: f,
    }
    engine.funcMap = funcMap
}

func (engine *Engine) LoadHTMLGlob(pattern string) {
    engine.HTMLdelimsLeft = "{"
    engine.HTMLdelimsRight= "}"
    engine.HTMLRender = template.Must(template.New("").Delims(engine.HTMLdelimsLeft,engine.HTMLdelimsRight).Funcs(engine.funcMap).ParseGlob(pattern))
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter,req *http.Request){
    c := newContext(w, req)
    c.engine = engine //新增
    engine.handleHTTPRequest(c)
}

4、改造Context HTML方法

func (c *Context) HTML(code int,TmpName string,data interface{}){
    c.SetHeader("Content-Type", "text/html")
    c.Status(code)
    if err := c.engine.HTMLRender.ExecuteTemplate(c.Writer, TmpName, data); err != nil {
        c.JSON(500, err.Error())
    }
}

5、demo

package main

import (
    "ebb"
    "time"
    "fmt"
)

type student struct {
    Name string
    Age  int8
}

func FormatDate(t time.Time) string {
    year, month, day := t.Date()
    Hour,Minute,Second := t.Hour(),t.Minute(),t.Second()
    return fmt.Sprintf("%d-%02d-%02d %d:%d:%d", year, month, day,Hour,Minute,Second)
}

func main(){
    r := ebb.Default()
    r.GET("/panic",func(c *ebb.Context) {
        panic("err")
    })

    r.POST("/login/*name",func(c *ebb.Context) {
        c.JSON(200, ebb.H{
            "name": c.Param("name"),
        })
    })
    r.Static("/assets", "./static")
    r.SetFuncMap("formatDate",FormatDate)
    r.LoadHTMLGlob("templates/*")
    stu1 := &student{Name: "Geektutu", Age: 20}
    stu2 := &student{Name: "Jack", Age: 22}

    r.GET("/students", func(c *ebb.Context) {
        c.HTML(200, "students.tmpl", ebb.H{
            "title":  "ebb  students",
            "now":   time.Now(),
            "stuArr": [2]*student{stu1, stu2},
        })
    })
    r.Run(":8080")
}
<html>
<body>
    <p>hello, {.title}</p>
    <p>now : {.now |formatDate}</p>
    {range $index, $ele := .stuArr }
    <p>{ $index }: { $ele.Name } is { $ele.Age } years old</p>
    { end }
</body>
</html>

自此带着大家搭建gin已经完结,希望大家有所收获,不白看,多动手

来源:https://studygolang.com/user/18211167516/articles

评论

此博客中的热门博文

Base:一种Acid的替代方案

Mifare Classic card(M1卡)破解过程记录(准备+理论+获取扇区密钥+数据分析)

如何优雅的关闭容器

为什么要建模;怎么建模才合理;“领域”模型具体指什么?(DDD)

小程序框架全面测评

打造一个可国内访问的Blogger(Blogspot)方法

自己制作蔬果汁方法与食谱

设计师别担心,微软雅黑这样使用不侵权

一个没有任何底子的人怎么学唱歌?

开源数据库中间件的对比、选取和搭建