GoWeb-Gin框架快速入门(21.8.18-19)

1,162 阅读9分钟

GoWeb

创建一个简单Web程序

package  main
​
import (
    "fmt"
    "log"
    "net/http"
)
​
// 创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
    fmt.Fprintln(w, "Hello World", r.URL.Path)
}
​
​
func main() {
    
    
    // 映射请求地址 ,将请求地址和处理器相映射
    http.HandleFunc("/", handler)
    
    
    // 设置监听端口 和处理器
    // 第一个参数是端口号,如果不设置,默认是80,
    // 第二个参数是 处理器,若参数为nil,服务器将使用默认的多路复用器 DefaultServerMux
    err := http.ListenAndServe(":8000", nil)
    if err != nil{
        log.Fatalln("ListenAndServer", err)
    }
​
}

创建Go服务器

Go 提供了一系列用于创建 Web 服务器的标准库,而且通过 Go 创建一个服务器的 步骤非常简单,只要通过 net/http 包调用 ListenAndServe 函数并传入网络地址以及负 责处理请求的处理器( handler )作为参数就可以了。如果网络地址参数为空字符串,那 么服务器默认使用 80 端口进行网络连接;如果处理器参数为 nil,那么服务器将使用默 认的多路复用器 DefaultServeMux,当然,我们也可以通过调用 NewServeMux 函数创 建一个多路复用器。多路复用器接收到用户的请求之后根据请求的 URL 来判断使用哪 个处理器来处理请求,找到后就会重定向到对应的处理器来处理请求

Gin框架快速入门

gin简介

Gin是一个用Go语言编写的web框架。它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter, 速度提高了近40倍。

gin安装:

Terminal 输入go get github.com/gin-gonic/gin进行安装

每次新建项目时:go mod inti projectName

若下载失败参考链接:https://www.cnblogs.com/kevin-yang123/p/14799091.html

第一个gin程序

package main
​
import (
    "github.com/gin-gonic/gin"
)
​
//创建处理器函数
func sayHello(c *gin.Context){
    c.JSON(200,gin.H{   //gin.H 是json字符串
        "message": "hello",
    })
}
​
func main() {
    // 返回一个默认的路由引擎
    r := gin.Default()
    
    // GET: 请求方式; /hello: 请求路径
    // 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
    r.GET("/hello", sayHello )
​
    // 启动http服务,默认在0.0.0:8080启动服务
    r.Run(":9090")
}
​

RESTful API风格

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

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

推荐阅读www.ruanyifeng.com/blog/2011/0…

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

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

    // GET
    r.GET("/book", func(context *gin.Context){
        context.JSON(http.StatusOK, gin.H{
            "method": "GET",
        })
    })
​
    // POST
    r.POST("/book", func(context *gin.Context){
        context.JSON(http.StatusOK, gin.H{
            "method": "POST",
        })
    })
​
    
    //PUT
    r.PUT("/book", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "method": "PUT",
        })
    })
​
    //DElETE
    r.DELETE("/book", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "method": "DELETE",
        })
    })

Gin框架返回JSON

方法一:使用map ,gin.H 与其相同
package main
​
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
    // 创建默认路由
    r := gin.Default()
​
    r.GET("/json1", func(context *gin.Context) {
​
        // 方法一 :使用map  ,gin.H 与其相同
        /*data := map[string]interface{}{
            "name" : "LittlePrince",
            "message" : "helloworld",
            "age" : 16,
        }
        */
        data1 := gin.H{
            "name" : "LittlePrince",
            "message" : "helloworld",
            "age" : 16,
        }
        context.JSON(http.StatusOK, data1)
    })
    r.Run(":9091")
​
}
​

方法二:结构体 首字母大写,保证JSON可以取到

package main
​
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
    // 创建默认路由
    r := gin.Default()
​
​
    // 方法二: 结构体  首字母大写,保证JSON可以取到  ,可以使用tag对结构体字段进行自定义操作
    type msg struct{
        Name string  `json:"name"`
        Message string
        Age int
    }
    r.GET("/json2", func(context *gin.Context) {
        data2 := msg{
            Name: "LittlePringe",
            Message: "hello gin",
            Age: 14,
        }
        context.JSON(http.StatusOK, data2)
    })
​
    r.Run(":9091")
​
​
}
​

获取参数

获取querystring参数(URL中)

querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)
func main() {
	r := gin.Default()

	r.GET("/usr/search", func(context *gin.Context) {
		username := context.DefaultQuery("username", "LittlePrince")
		address := context.Query("address")

		// 输出json结果给调用方
		context.JSON(http.StatusOK, gin.H{
			"message" : "ok",
			"username" : username,
			"address" : address,
		})
	})
	r.Run(":9091")
}

获取form表单中的参数

当前端请求的数据通过form表单提交时,例如向/user/search发送一个POST请求,获取请求数据的方式如下:

package main
​
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
    r := gin.Default()
    r.POST("/usr/search", func(context *gin.Context) {
        // DefaultPostForm取不到值时会返回指定的默认值
        //username := context.DefaultPostForm("username", "小王子")
        username := context.PostForm("username")
        address := context.PostForm("address")
​
        context.JSON(http.StatusOK, gin.H{
            "message" : "ok",
            "username" : username,
            "address" : address,
        })
    })
​
    r.Run(":9091")
​
}

获取JSON的参数

当前端请求的数据通过JSON提交时,例如向/json发送一个POST请求,则获取请求参数的方式如下:

package main
​
import (
    "encoding/json"
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
    r := gin.Default()
​
    r.POST("/json3", func(context *gin.Context) {
        b, _ := context.GetRawData()  // 从context.Request.Body读取请求数据
        // 定义map或结构体
        var m map[string]interface{}
            // 反序列化
            _ = json.Unmarshal(b, &m)
​
            context.JSON(http.StatusOK, m)
​
    })
​
    r.Run(":9091")
​
}
​

获取Path参数

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

package main
​
import (
    "encoding/json"
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
    r := gin.Default()
​
    r.GET("usr/serarch/:username/:address", func(context *gin.Context) {
        username := context.Param("username")
        address := context.Param("address")
​
        //将结果输出到调用方
        context.JSON(http.StatusOK, gin.H{
            "message" : "ok",
            "username" : username,
            "address" : address,
        })
    })
​
    r.Run(":9091")
​
}
​

自动参数绑定

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

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

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

实例如下

package main

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()


	type Login struct {
		User string `form:"user" json:"user" binding:"required"`
		Password string  `form:"password" json:"password" binding:"required"`
	}

	// 绑定Json示例 ({"user": "q1mi", "password": "123456"})
	r.POST("/loginJson", func(context *gin.Context) {
		var login Login
		// 和login绑定
		if err := context.ShouldBind(&login); err == nil{
			fmt.Printf("login info: %#v \n", login)
			context.JSON(http.StatusOK, gin.H{
				"user":     login.User,
				"password": login.Password,
			})
		}else {
			context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}

	})

	// 绑定form表单示例 (user=q1mi&password=123456)
	r.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)
	r.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()})
		}
	})



	r.Run(":9091")

}

重定向

可用与访问资源出错定位到指定页面

http重定向

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

package main
​
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
​
    r := gin.Default()
​
    r.GET("/baidu", func(context *gin.Context) {
        // http 重定向
        context.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
    })
    r.Run(":8081")
​
}

路由重定向

路由重定向,使用HandleContext

package main
​
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
​
    r := gin.Default()
​
    r.GET("/sogo", func(context *gin.Context) {
        //路由重定向
        context.Request.URL.Path = "/baidu"
        r.HandleContext(context)
    })
​
    r.GET("/test", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{
            "user":     "login.User",
            "password": "login.Password",
        })
    })
​
    r.Run(":8081")
​
}

Gin路由

普通路由

上述已经使用过的普通路由

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)
    })

路由组

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

package main
​
import (
    "github.com/gin-gonic/gin"
    "net/http"
)
​
func main() {
    r := gin.Default()
    
    // 创建一个路由组
    userGroup := r.Group("/user")
    {
        // 路由组中 第一个路由
        userGroup.GET("/index", func(context *gin.Context) {
            context.JSON(http.StatusOK, gin.H{
                "time" : "teime",
            })
        })
        
        // 路由组中 第二个路由
        userGroup.POST("/login" , func(context *gin.Context) {
            context.JSON(http.StatusOK, gin.H{
                "test" : "test",
            })
        })
    }
    r.Run(":8081")
​
}

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

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版本时。

中间件

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

一个简单的中间件Demo

package main
​
​
import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
)
​
// 中间件ue是一个函数
func midddleware(c *gin.Context)  {
    fmt.Println("middleware....")
}
​
​
func task(c *gin.Context) {
    fmt.Println("task.....")
    c.JSON(http.StatusOK, gin.H{
        "task":"sfasfsa",
    })
}
​
func main() {
    r := gin.Default()
    // 中间件在task执行之前执行
    r.GET("/index", midddleware, task)
​
    r.Run(":8081")
​
​
}

使用中间件统计处理请求时间

package main
​
import (
    "fmt"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)
​
// 统计耗时中间件
func midddleware(c *gin.Context)  {
    fmt.Println("middleware....")
    start := time.Now()  //获取现在的时间
    c.Set("name", "LittlePrince")   //通过c.set在请求上下文中设置值,后续的处理函数能够得到值
    c.Next()  // 调用后续的处理程序
    //c.Abort() // 组织后续的处理程序
    // 计算耗时
    cost := time.Since(start)
    fmt.Printf("cost:%v \n",cost)
    fmt.Println("middleware out")
​
}
​
func task(c *gin.Context) {
    fmt.Println("task.....")
    c.JSON(http.StatusOK, gin.H{
        "task":"sfasfsa",
    })
}
​
func main() {
    r := gin.Default()
​
    r.GET("/index", midddleware, task)
​
    r.Run(":8081")
​
}
​

给一组路由添加中间件

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

为全局路由注册中间件
func main() {
    // 新建一个没有任何默认中间件的路由
    r := gin.Default
    // 注册一个全局中间件
    r.Use(midddleware)
    
    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()
}
为某个路由注册中间件

见上面的案例使用中间件统计处理请求时间

为路由组注册中间件

写法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) {...})
    ...
}

实际使用中使用闭包处理

// 实际使用中间件时,使用闭包包装一层
func autMiddleware(doCheck bool) gin.HandlerFunc  {
    return func(c *gin.Context) {
        if doCheck{
            // 存放具体逻辑
            // 是否登录判断
            // if 是用户
            // c.next()
            // else
            // c.abort()
        }else {
            c.Next()
        }
    }
}

中间件注意事项

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())。

参考博客:www.liwenzhou.com/posts/Go/Gi…