• GoWeb之gin框架


        Gin 是一个 go 写的 web 框架,具有高性能的优点。官方地址:https://github.com/gin-gonic/gin

    一、快速上手

    • 安装

    go mod init
    go get -u github.com/gin-gonic/gin
    • 代码中导入

    import "github.com/gin-gonic/gin"
    • 快速入门

      运行这段代码并在浏览器中访问 http://localhost:9000

      package main
      ​
      import (
          "github.com/gin-gonic/gin"
          "log"
          "net/http"
      )
      ​
      func SayHello(ctx *gin.Context)  {
          ctx.JSON(200,gin.H{
              "message": "hello golang",
          })
      }
      ​
      func main()  {
          r := gin.Default()  // 返回默认的理由引擎
          // 指定用户使用GET请求访问 /hello 执行sayHello函数
          r.GET("/hello", SayHello)
          // 启动服务
          err := r.Run(":9000")  // 默认8080
          if err!= nil {
              log.Println(err)
          }
      }

    • RESTful API

    • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

      推荐阅读阮一峰 理解RESTful架构

      简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

      • GET用来获取资源

      • POST用来新建资源

      • PUT用来更新资源

      • DELETE用来删除资源。

      只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。

      例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们Web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

        同样的需求我们按照RESTful API设计如下:

    func main()  {
    	r := gin.Default()	// 返回默认的理由引擎
    	// restful风格
    	r.GET("/book", func(context *gin.Context) {
    		context.JSON(http.StatusOK, gin.H{
    			"method": "GET",
    		})
    	})
    	r.POST("/book", func(context *gin.Context) {
    		context.JSON(http.StatusOK,gin.H{
    			"method": "POST",
    		})
    	})
    	r.PUT("/book", func(context *gin.Context) {
    		context.JSON(http.StatusOK,gin.H{
    			"method": "PUT",
    		})
    	})
    	r.DELETE("/book", func(context *gin.Context) {
    		context.JSON(http.StatusOK,gin.H{
    			"method": "DELETE",
    		})
    	})
    	r.PATCH("/book", func(context *gin.Context) {
    		context.JSON(http.StatusOK,gin.H{
    			"method": "PATCH",
    		})
    	})
    	r.HEAD("/book", func(context *gin.Context) {
    		context.JSON(http.StatusOK,gin.H{
    			"method": "HEAD",
    		})
    	})
    	r.OPTIONS("/book", func(context *gin.Context) {
    		context.JSON(http.StatusOK,gin.H{
    			"method": "OPTIONS",
    		})
    	})
    	
    	// 启动服务
    	err := r.Run(":9000")   // 默认8080
    	if err!= nil {
    		log.Println(err)
    	}
    }
    

      开发RESTful API的时候通常使用Postman来作为客户端的测试工具。

    二、模板渲染

    2.1 HTML渲染

    ​ 我们首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/index.html文件的内容如下:

    {{define "posts/index.html"}}
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>posts/index</title>
    </head>
    <body>
        {{.title}}
    </body>
    </html>
    {{end}}
    

    users/index.html文件的内容如下:

    {{define "users/index.html"}}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>users/index</title>
    </head>
    <body>
        {{.title}}
    </body>
    </html>
    {{end}}
    

    Gin框架中使用LoadHTMLGlob()或者LoadHTMLFiles()方法进行HTML模板渲染。

    func main() {
    	r := gin.Default()
    	r.LoadHTMLGlob("templates/**/*")
    	//r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html")
    	r.GET("/posts/index", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "posts/index.html", gin.H{
    			"title": "posts/index",
    		})
    	})
    
    	r.GET("users/index", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "users/index.html", gin.H{
    			"title": "users/index",
    		})
    	})
    
    	r.Run(":8080")
    }
    

    2.1.1 自定义模板函数

    定义一个不转义相应内容的safe模板函数如下:

    func main() {
        router := gin.Default()
        router.SetFuncMap(template.FuncMap{
            "safe": func(str string) template.HTML{
                return template.HTML(str)
            },
        })
        router.LoadHTMLFiles("./index.tmpl")
    ​
        router.GET("/index", func(c *gin.Context) {
            c.HTML(http.StatusOK, "index.tmpl", "<a href='https://liwenzhou.com'>李文周的博客</a>")
        })
    ​
        router.Run(":8080")
    }

    index.tmpl中使用定义好的safe模板函数:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <title>修改模板引擎的标识符</title>
    </head>
    <body>
    <div>{{ . | safe }}</div>
    </body>
    </html>

    2.1.2 静态文件处理

    当我们渲染的HTML文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用gin.Static方法即可。

    func main() {
    	r := gin.Default()
    	r.Static("/static", "./static")
    	r.LoadHTMLGlob("templates/**/*")
       // ...
    	r.Run(":8080")
    }
    

    2.1.3 使用模板继承

    Gin框架默认都是使用单模板,如果需要使用block template功能,可以通过"github.com/gin-contrib/multitemplate"库实现,具体示例如下:

    首先,假设我们项目目录下的templates文件夹下有以下模板文件,其中home.tmplindex.tmpl继承了base.tmpl

    templates
    ├── includes
    │   ├── home.tmpl
    │   └── index.tmpl
    ├── layouts
    │   └── base.tmpl
    └── scripts.tmpl

    然后我们定义一个loadTemplates函数如下:

    func loadTemplates(templatesDir string) multitemplate.Renderer {
        r := multitemplate.NewRenderer()
        layouts, err := filepath.Glob(templatesDir + "/layouts/*.tmpl")
        if err != nil {
            panic(err.Error())
        }
        includes, err := filepath.Glob(templatesDir + "/includes/*.tmpl")
        if err != nil {
            panic(err.Error())
        }
        // 为layouts/和includes/目录生成 templates map
        for _, include := range includes {
            layoutCopy := make([]string, len(layouts))
            copy(layoutCopy, layouts)
            files := append(layoutCopy, include)
            r.AddFromFiles(filepath.Base(include), files...)
        }
        return r
    }

    我们在main函数中

    func indexFunc(c *gin.Context){
        c.HTML(http.StatusOK, "index.tmpl", nil)
    }
    ​
    func homeFunc(c *gin.Context){
        c.HTML(http.StatusOK, "home.tmpl", nil)
    }
    ​
    func main(){
        r := gin.Default()
        r.HTMLRender = loadTemplates("./templates")
        r.GET("/index", indexFunc)
        r.GET("/home", homeFunc)
        r.Run()
    }
    

    2.1.4 补充文件路径处理

    关于模板文件和静态文件的路径,我们需要根据公司/项目的要求进行设置。可以使用下面的函数获取当前执行程序的路径。

    func getCurrentPath() string {
    	if ex, err := os.Executable(); err == nil {
    		return filepath.Dir(ex)
    	}
    	return "./"
    }
    

    2.2 JSON渲染

    func main() {
    	r := gin.Default()
    
    	// gin.H 是map[string]interface{}的缩写
    	r.GET("/someJSON", func(c *gin.Context) {
    		// 方式一:自己拼接JSON
    		c.JSON(http.StatusOK, gin.H{"message": "Hello world!"})
    	})
    	r.GET("/moreJSON", func(c *gin.Context) {
    		// 方法二:使用结构体
    		var msg struct {
    			Name    string `json:"user"`
    			Message string
    			Age     int
    		}
    		msg.Name = "小王子"
    		msg.Message = "Hello world!"
    		msg.Age = 18
    		c.JSON(http.StatusOK, msg)
    	})
    	r.Run(":8080")
    }
    

    2.3 XML渲染

    注意需要使用具名的结构体类型。

    func main() {
    	r := gin.Default()
    	// gin.H 是map[string]interface{}的缩写
    	r.GET("/someXML", func(c *gin.Context) {
    		// 方式一:自己拼接JSON
    		c.XML(http.StatusOK, gin.H{"message": "Hello world!"})
    	})
    	r.GET("/moreXML", func(c *gin.Context) {
    		// 方法二:使用结构体
    		type MessageRecord struct {
    			Name    string
    			Message string
    			Age     int
    		}
    		var msg MessageRecord
    		msg.Name = "小王子"
    		msg.Message = "Hello world!"
    		msg.Age = 18
    		c.XML(http.StatusOK, msg)
    	})
    	r.Run(":8080")
    }

    2.4 YAML渲染

    r.GET("/someYAML", func(c *gin.Context) {
    	c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
    })

    2.5 protobuf渲染

    r.GET("/someProtoBuf", func(c *gin.Context) {
    	reps := []int64{int64(1), int64(2)}
    	label := "test"
    	// protobuf 的具体定义写在 testdata/protoexample 文件中。
    	data := &protoexample.Test{
    		Label: &label,
    		Reps:  reps,
    	}
    	// 请注意,数据在响应中变为二进制数据
    	// 将输出被 protoexample.Test protobuf 序列化了的数据
    	c.ProtoBuf(http.StatusOK, data)
    })
    

    三、获取参数

    3.1 获取querystring参数

    querystring指的是URL中?后面携带的参数,例如:/web?name=小王子&age=23。 获取请求的querystring参数的方法如下:

    func main() {
    	r := gin.Default()
    	// 获取请求中携带的query string数据
    	// GET 请求 URL ?后面的是querystring参数
    	// key-value格式,多个key-value用&连接
    	// eq: /web?name=张亚飞&age=23
    	r.GET("/web", func(c *gin.Context) {
    		name := c.Query("name")	// 通过query获取请求中的query string参数
    		age := c.Query("age")	// 通过query获取请求中的query string参数
    		//name := c.DefaultQuery("name", "somebody")	// 取不到就用指定的值
    		//name, ok := c.GetQuery("name")		// 取到返回(值, true) 取不到返回(值,false)
    		//if !ok {
    		//	取不到
    			//name = "somebody"
    		//}
    		c.JSON(http.StatusOK, gin.H{
    			"name": name,
    			"age": age,
    		})
    	})
    
    	_ = r.Run(":9000")
    }
    

    3.2 获取form参数

    请求的数据通过form表单来提交,例如向/login发送一个POST请求,获取请求数据的方式如下:

    func main() {
    	r := gin.Default()
    	r.LoadHTMLFiles("templates/login.html", "templates/home.html")
    	r.GET("/login", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "login.html", nil)
    	})
    	r.POST("/login", func(c *gin.Context) {
    		// 获取form表单提交的数据
    		// 方式一  PostForm
    		//username := c.PostForm("username")
    		//pwd := c.PostForm("password")
    		// 方式二 DefaultPostForm
    		//username := c.DefaultPostForm("username", "未知用户")
    		//pwd := c.DefaultPostForm("password", "***")
    		//pwd := c.DefaultPostForm("xxx", "***")
    		// 方式三
    		username, ok := c.GetPostForm("username")
    		if !ok {
    			username = "sb"
    		}
    		pwd, _ := c.GetPostForm("password")
    		c.HTML(http.StatusOK, "home.html", gin.H{
    			"name": username,
    			"password": pwd,
    		})
    
    	})
    	_ = r.Run(":9000")
    }
    

    3.3 获取path参数

    请求的参数通过URL路径传递,例如:/user/search/小王子/沙河。 获取请求URL路径中的参数的方式如下。

    func main() {
    	r := gin.Default()
    	r.GET("/user/:name/:age", func(c *gin.Context) {
    		name := c.Param("name")
    		age := c.Param("age")
    		c.JSON(http.StatusOK, gin.H{
    			"name": name,
    			"age": age,
    		})
    	})
    
    	r.GET("/blog/:year/:month", func(c *gin.Context) {
    		year := c.Param("year")
    		month := c.Param("month")
    		c.JSON(http.StatusOK, gin.H{
    			"year": year,
    			"month": month,
    		})
    	})
    	_ = r.Run(":9000")
    }

    3.4 参数绑定

    为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

    // Binding from JSON
    type Login struct {
    	User     string `form:"user" json:"user" binding:"required"`
    	Password string `form:"password" json:"password" binding:"required"`
    }
    
    func main() {
    	router := gin.Default()
    
    	// 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
    	router.POST("/loginJSON", func(c *gin.Context) {
    		var login Login
    
    		if err := c.ShouldBind(&login); err == nil {
    			fmt.Printf("login info:%#v
    ", login)
    			c.JSON(http.StatusOK, gin.H{
    				"user":     login.User,
    				"password": login.Password,
    			})
    		} else {
    			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		}
    	})
    
    	// 绑定form表单示例 (user=q1mi&password=123456)
    	router.POST("/loginForm", func(c *gin.Context) {
    		var login Login
    		// ShouldBind()会根据请求的Content-Type自行选择绑定器
    		if err := c.ShouldBind(&login); err == nil {
    			c.JSON(http.StatusOK, gin.H{
    				"user":     login.User,
    				"password": login.Password,
    			})
    		} else {
    			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		}
    	})
    
    	// 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
    	router.GET("/loginForm", func(c *gin.Context) {
    		var login Login
    		// ShouldBind()会根据请求的Content-Type自行选择绑定器
    		if err := c.ShouldBind(&login); err == nil {
    			c.JSON(http.StatusOK, gin.H{
    				"user":     login.User,
    				"password": login.Password,
    			})
    		} else {
    			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		}
    	})
    
    	// Listen and serve on 0.0.0.0:8080
    	router.Run(":8080")
    }
    

    ShouldBind会按照下面的顺序解析请求中的数据完成绑定:

     

    1. 如果是 GET 请求,只使用 Form 绑定引擎(query)。
    2. 如果是 POST 请求,首先检查 content-type 是否为 JSONXML,然后再使用 Formform-data)。

    四、文件上传

    4.1 单个文件上传

    文件上传前端页面代码:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <title>上传文件示例</title>
    </head>
    <body>
    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="f1">
        <input type="submit" value="上传">
    </form>
    </body>
    </html>
    

    后端gin框架部分代码:

    func main() {
    	r := gin.Default()
    	// 处理multipart forms提交文件时默认的内存限制是32 MiB
    	// 可以通过下面的方式修改
    	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
    	r.LoadHTMLFiles("templates/file.html")
    	r.GET("/upload", func(c *gin.Context) {
    		c.HTML(http.StatusOK, "file.html", nil)
    	})
    	r.POST("/upload", func(c *gin.Context) {
    		// 从请求中服务文件
    		f,err := c.FormFile("f1")	// 从请求中获取携带的参数是一样的
    		if err!=nil {
    			c.JSON(http.StatusBadRequest, gin.H{
    				"error": err.Error(),
    			})
    		}else {
    			// 将读取文件保存在本地
    			//data := fmt.Sprintf("./%s", f.Filename)
    			file_path := path.Join("./", f.Filename)
    			err = c.SaveUploadedFile(f, file_path)
    			if err!=nil {
    				log.Println("save file failed %v", err)
    			}else {
    				c.JSON(http.StatusOK, gin.H{
    					"status": "ok",
    				})
    			}
    		}
    	})
    	_ = r.Run(":9000")
    }

    4.2 多个文件上传

    func main() {
    	router := gin.Default()
    	// 处理multipart forms提交文件时默认的内存限制是32 MiB
    	// 可以通过下面的方式修改
    	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
    	router.POST("/upload", func(c *gin.Context) {
    		// Multipart form
    		form, _ := c.MultipartForm()
    		files := form.File["file"]
    
    		for index, file := range files {
    			log.Println(file.Filename)
    			dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
    			// 上传文件到指定的目录
    			c.SaveUploadedFile(file, dst)
    		}
    		c.JSON(http.StatusOK, gin.H{
    			"message": fmt.Sprintf("%d files uploaded!", len(files)),
    		})
    	})
    	router.Run()
    }
    

    五、重定向

    5.1 HTTP重定向

    HTTP 重定向很容易。 内部、外部重定向均支持。

    func main() {
    	r := gin.Default()
    	r.GET("/index", func(c *gin.Context) {
    		c.Redirect(http.StatusMovedPermanently, "https://cn.bing.com")
    	})
    	_ = r.Run(":9000")
    }
    

    5.2 路由重定向

    路由重定向,使用HandleContext

    func main() {
    	r := gin.Default()
    	r.GET("/a", func(c *gin.Context) {
    		// 跳转到/b对应的额路由处理函数
    		c.Request.URL.Path = "/b"	// 把请求的url修改
    		r.HandleContext(c)		// 继续后续的处理
    	})
    	r.GET("/b", func(c *gin.Context) {
    		c.JSON(http.StatusOK, gin.H{
    			"message": "b",
    		})
    	})
    	_ = r.Run(":9000")
    }
    

    六、Gin路由

    6.1 普通路由

    r.GET("/index", func(c *gin.Context) {...})
    r.GET("/login", func(c *gin.Context) {...})
    r.POST("/login", func(c *gin.Context) {...})

    此外,还有一个可以匹配所有请求方法的Any方法如下:

    r.Any("/test", func(c *gin.Context) {...})

    为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

    r.NoRoute(func(c *gin.Context) {
    		c.HTML(http.StatusNotFound, "views/404.html", nil)
    	})

    6.2 路由组

    我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

    func main() {
    	r := gin.Default()
    	userGroup := r.Group("/user")
    	{
    		userGroup.GET("/index", func(c *gin.Context) {...})
    		userGroup.GET("/login", func(c *gin.Context) {...})
    		userGroup.POST("/login", func(c *gin.Context) {...})
    
    	}
    	shopGroup := r.Group("/shop")
    	{
    		shopGroup.GET("/index", func(c *gin.Context) {...})
    		shopGroup.GET("/cart", func(c *gin.Context) {...})
    		shopGroup.POST("/checkout", func(c *gin.Context) {...})
    	}
    	r.Run()
    }

    路由组也是支持嵌套的,例如:

    shopGroup := r.Group("/shop")
    	{
    		shopGroup.GET("/index", func(c *gin.Context) {...})
    		shopGroup.GET("/cart", func(c *gin.Context) {...})
    		shopGroup.POST("/checkout", func(c *gin.Context) {...})
    		// 嵌套路由组
    		xx := shopGroup.Group("xx")
    		xx.GET("/oo", func(c *gin.Context) {...})
    	}

    通常我们将路由分组用在划分业务逻辑或划分API版本时。

    6.3 路由原理

    Gin框架中的路由使用的是httprouter这个库。

    其基本原理就是构造一个路由地址的前缀树。

    七、Gin中间件

    Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

    7.1 定义中间件

    Gin中的中间件必须是一个gin.HandlerFunc类型。例如我们像下面的代码一样定义一个统计请求耗时的中间件。

    // StatCost 是一个统计耗时请求耗时的中间件
    func StatCost() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		start := time.Now()
    		c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
    		// 调用该请求的剩余处理程序
    		c.Next()
    		// 不调用该请求的剩余处理程序
    		// c.Abort()
    		// 计算耗时
    		cost := time.Since(start)
    		log.Println(cost)
    	}
    }

    7.2 注册中间件

    在gin框架中,我们可以为每个路由添加任意数量的中间件。

    • 为全局路由注册

    func main() {
    	// 新建一个没有任何默认中间件的路由
    	r := gin.New()
    	// 注册一个全局中间件
    	r.Use(StatCost())
    	
    	r.GET("/test", func(c *gin.Context) {
    		name := c.MustGet("name").(string) // 从上下文取值
    		log.Println(name)
    		c.JSON(http.StatusOK, gin.H{
    			"message": "Hello world!",
    		})
    	})
    	r.Run()
    }
    • 为某个路由单独注册

    // 给/test2路由单独注册中间件(可注册多个)
    	r.GET("/test2", StatCost(), func(c *gin.Context) {
    		name := c.MustGet("name").(string) // 从上下文取值
    		log.Println(name)
    		c.JSON(http.StatusOK, gin.H{
    			"message": "Hello world!",
    		})
    	})
    • 为路由组注册中间件

    为路由组注册中间件有以下两种写法。

    写法1:

    shopGroup := r.Group("/shop", StatCost())
    {
        shopGroup.GET("/index", func(c *gin.Context) {...})
        ...
    }

    写法2:

    shopGroup := r.Group("/shop")
    shopGroup.Use(StatCost())
    {
        shopGroup.GET("/index", func(c *gin.Context) {...})
        ...
    }

    中间件示例

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"log"
    	"net/http"
    	"time"
    )
    
    func homeHandler(c *gin.Context)  {
    	log.Println("home")
    	name, ok := c.Get("name")
    	if  !ok {
    		name = "匿名用户"
    	}
    	c.JSON(http.StatusOK, gin.H{
    		"msg": name,
    	})
    }
    
    // 定义一个中间件m1:统计请求处理函数的耗时
    func m1(c *gin.Context)  {
    	log.Println("m1 in...")
    	// 计时
    	start := time.Now()
    	c.Next()	// 调用后续的处理函数
    	//c.Abort()	// 阻止后续的处理函数
    	cost := time.Since(start)
    	log.Printf("m1 out...")
    	log.Printf("cost:%v
    ", cost)
    }
    
    func m2(c *gin.Context)  {
    	log.Println("m2 in...")
    	c.Set("name", "yafei")
    	c.Next()	// 调用后续的处理函数
    	log.Printf("m2 out...")
    }
    
    func authMiddleware(doCheck bool) gin.HandlerFunc {
    	// 选择数据库
    	// 或这一些其他准备工作
    	return func(c *gin.Context) {
    		// 是否登录的判断
    		if doCheck {
    			c.Next()
    		} else {
    			c.Abort()
    		}
    	}
    }
    
    func main() {
    	r := gin.Default() // Default returns an Engine instance with the Logger and Recovery middleware already attached.
    	// 注册中间件
    	r.Use(m1, m2, authMiddleware(true))
    	//r.GET("/home", m1, homeHandler)
    	r.GET("/home", homeHandler)
    	r.GET("/shop", func(c *gin.Context) {
    		c.JSON(http.StatusOK, gin.H{
    			"msg": "shop",
    		})
    	})
    	r.GET("/user", func(c *gin.Context) {
    		c.JSON(http.StatusOK, gin.H{
    			"msg": "user",
    		})
    	})
    
    	_ = r.Run(":9000")
    }

    7.3 中间件注意事项

    gin默认中间件

    gin.Default()默认使用了LoggerRecovery中间件,其中:

    • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
    • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

    如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

    gin中间件中使用goroutine

    当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

    八、运行多个服务

    我们可以在多个端口启动服务,例如:

    package main
    
    import (
    	"log"
    	"net/http"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"golang.org/x/sync/errgroup"
    )
    
    var (
    	g errgroup.Group
    )
    
    func router01() http.Handler {
    	e := gin.New()
    	e.Use(gin.Recovery())
    	e.GET("/", func(c *gin.Context) {
    		c.JSON(
    			http.StatusOK,
    			gin.H{
    				"code":  http.StatusOK,
    				"error": "Welcome server 01",
    			},
    		)
    	})
    
    	return e
    }
    
    func router02() http.Handler {
    	e := gin.New()
    	e.Use(gin.Recovery())
    	e.GET("/", func(c *gin.Context) {
    		c.JSON(
    			http.StatusOK,
    			gin.H{
    				"code":  http.StatusOK,
    				"error": "Welcome server 02",
    			},
    		)
    	})
    
    	return e
    }
    
    func main() {
    	server01 := &http.Server{
    		Addr:         ":8080",
    		Handler:      router01(),
    		ReadTimeout:  5 * time.Second,
    		WriteTimeout: 10 * time.Second,
    	}
    
    	server02 := &http.Server{
    		Addr:         ":8081",
    		Handler:      router02(),
    		ReadTimeout:  5 * time.Second,
    		WriteTimeout: 10 * time.Second,
    	}
       // 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
    	g.Go(func() error {
    		return server01.ListenAndServe()
    	})
    
    	g.Go(func() error {
    		return server02.ListenAndServe()
    	})
    
    	if err := g.Wait(); err != nil {
    		log.Fatal(err)
    	}
    }

     

  • 相关阅读:
    解决PKIX:unable to find valid certification path to requested target 的问题
    Linux 上的常用文件传输方式介绍与比较
    用VNC远程图形化连接Linux桌面的配置方法
    红帽中出现”This system is not registered with RHN”的解决方案
    linux安装时出现your cpu does not support long mode的解决方法
    CentOS SSH配置
    es6扩展运算符及rest运算符总结
    es6解构赋值总结
    tortoisegit安装、clon、推送
    es6环境搭建
  • 原文地址:https://www.cnblogs.com/zhangyafei/p/12485430.html
Copyright © 2020-2023  润新知