Golang | Gin 框架的使用

安装辅助库

go get -u -v github.com/ramya-rao-a/go-outline

如果网络原因,可以选择先从 github 下载,之后手动安装。

git clone https://github.com/golang/tools.git $GOPATH/src/golang.org/x/tools

安装成功的提示:

安装 Gin 框架,使用命令:

go get -u -v github.com/gin-gonic/gin

Gin 程序

编写第一个 Gin 程序。

/**
 * @author Real
 * @since 2023/10/28 16:42
 */
package main

import "github.com/gin-gonic/gin"

func main() {
	engine := gin.Default()
	engine.GET("/hello", func(context *gin.Context) {
		context.JSON(200, gin.H{
			"message": "hello world",
		})
	})

	// 直接运行 engine 服务
	err := engine.Run()
	if err != nil {
		return
	}
}

之后直接运行,可以看到在控制栏,有对应的服务启动。同时 Gin 还提供了自己的监控 Console 控制台。

浏览器访问 [**http://localhost:8080/hello**](http://localhost:8080/hello) 即可访问上述的方法服务。

可以看到访问的结果是输出在 Gin Function 中返回的 H 结构 JSON 字符串。

  1. 首先,我们使用了 gin.Default() 生成了一个实例,这个实例即 WSGI 应用程序。
  2. 接下来,我们使用 engine.GET("/hello",...) 声明了一个路由,告诉 Gin 什么样的 URL 能触发传入的函数,这个函数返回我们想要显示在用户浏览器中的信息。
  3. 最后用 engine.Run() 函数来让应用运行在本地服务器上,默认监听端口是 8080,可以传入参数设置端口,例如 engine.Run(":9999") 即运行在 9999 端口。

测试:一个 engine 实例,能否同时监听两个端口。

编写这样的程序,之后启动程序,查看控制台,发现只显示监听了 8080 端口。

访问 9090 端口,发现系统提示无服务。

测试:一个 main 方法,能否同时启动多个 engine 实例。

查看控制台,发现仍然只 Listen 8080 端口。

经过两次测试,结论:

  • 发现 Gin 在监听 HTTP 请求时,一个 main 方法中,只能启动一个实例且只能监听一个端口。
  • 有同时监听多个端口的需求,需要启动多个服务。

路由 Route

Gin 在接受请求的时候,可以支持多种模式。Router 将请求根据不同的策略,路由到不同的 Context 上下文中。

无参数

package main

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

func main() {
	fmt.Println("------------- routerWithRouter --------------")
	routerWithoutParameter()
}

func routerWithoutParameter() {
	// 创建一个默认的路由引擎
	engine := gin.Default()

	// 注册一个 GET 请求,第一个参数是请求的路径,第二个参数是处理这个请求的函数
	// gin.Context 封装了 request 和 response
	// context.String() 第一个参数是状态码,第二个参数是返回的内容
	// 没有指定端口,默认监听 8080 端口;没有参数,默认监听 / 路径
	engine.GET("/", func(context *gin.Context) {
		context.String(http.StatusOK, "hello world!")
	})

	engine.Run()
}

运行程序,结果如下:

在浏览器访问 [**http://localhost:8080/**](http://localhost:8080/)** **之后,可以看到输出的 String 字符串。

路径参数

func routerWithPathParameter() {
	engine := gin.Default()
	// func 程序中的函数没有名称,称为匿名函数
	engine.GET("/hello/:name", func(context *gin.Context) {
		name := context.Param("name")
		context.String(200, "hello %s", name)
	})

	_ = engine.Run()
}

运行之后,可以看到 Gin 在 Console 控制台的输出结果:

浏览器访问 [**http://localhost:8080/hello/Alice**](http://localhost:8080/hello/Alice)** **可以看到浏览器的输出结果:

获取 Query 参数

针对 HTTP 请求中的 RequestParam 类型的参数,在 Gin 中的处理方式也十分简单。

// 匹配users?name=xxx&role=xxx,role可选
func routerWithQuery() {
	engine := gin.Default()
	engine.GET("/hello", func(context *gin.Context) {
		name := context.Query("name")
		role := context.Query("role")
		context.String(200, "%s is a %s", name, role)
	})

	_ = engine.Run()
}

启动之后,访问 [**http://localhost:8080/hello?name=Alice&role=Teacher**](http://localhost:8080/hello?name=Alice&role=Teacher)** **地址,可以看到输出的结果,符合我们的预期。

获取 Post 参数

上述方式,是对于 Get 请求的处理。Post 请求参数通常是以表单的形式提交的,所以对于这部分请求的处理,与 Query 不一样。

func routerWithPostForm() {
	engine := gin.Default()
	engine.POST("/login", func(context *gin.Context) {
		username := context.PostForm("username")
		// 优先以 postForm 的值为准,如果没有则使用默认值
		password := context.DefaultPostForm("password", "000000")
		context.JSON(200, gin.H{
			"username": username,
			"password": password,
		})
	})

	_ = engine.Run()
}

请求结果来看,对于表单数据的处理,也十分简单。

Query 与 Post 混合

对于 Query 和 Post 混合的场景中,应该使用 Post 请求。

func routerWithQueryAndPostForm() {
	engine := gin.Default()
	engine.POST("/pages", func(context *gin.Context) {
		pageNum := context.Query("pageNum")
		pageSize := context.DefaultQuery("pageSize", "10")
		username := context.PostForm("username")
		password := context.DefaultPostForm("password", "000000")

		context.JSON(http.StatusOK, gin.H{
			"pageNum":  pageNum,
			"pageSize": pageSize,
			"username": username,
			"password": password,
		})
	})

	_ = engine.Run(":9999")
}

请求结果:

Map 传参

除了常见的 Get 与 Post 传参方式,还有另外比较常见的 Map 传参方式。虽然这种方式不符合 REFTful 风格,但是 Gin 同样提供了比较好的支持。

func routerWithMap() {
	engine := gin.Default()
	engine.POST("/map", func(context *gin.Context) {
		ids := context.QueryMap("ids")
		names := context.PostFormMap("names")

		context.JSON(http.StatusOK, gin.H{
			"ids":   ids,
			"names": names,
		})
	})

	_ = engine.Run(":9999")
}

启动之后,查看控制台的输出,正常。

我们需要将参数放在 URL 中,并且需要符合 Go 的语法,这样才能被 Gin 正确解析。

curl --request POST \
  --url 'http://localhost:9999/map?ids[Jack]=001&ids[Tom]=002' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --header 'content-type: application/json' \
  --data 'names[a]=Sam&names[b]=David'

访问上述的 curl 之后,我们可以看到结果被正确输出了。

重定向 Redirect

重定向在 HTTP 协议中,对应的 Code 是 301。Gin 框架同样对 Redirect 提供了支持。

func routerWithRedirect() {
	engine := gin.Default()
	engine.GET("/redirectBaidu", func(context *gin.Context) {
		context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
	})

	_ = engine.Run(":9999")
}

之后访问 [http://localhost:9999/redirectBaidu](http://localhost:9999/redirectBaidu) 地址,会直接跳转百度的网站。

测试:如果重定向是同一个端口,设置的同样是 Gin 的端口服务,查看是否会正常跳转。

func routerWithRedirectSelf() {
	engine := gin.Default()
	engine.GET("/redirect", func(context *gin.Context) {
		context.Redirect(http.StatusMovedPermanently, "/index")
	})

	engine.GET("/index", func(context *gin.Context) {
		context.Request.URL.Path = "/"
		context.String(http.StatusOK, "index")
	})

	_ = engine.Run(":9999")
}

访问 [**http://localhost:9999/redirect**](http://localhost:9999/redirect)** 网站之后,会直接跳转到 [**http://localhost:9999/index**](http://localhost:9999/redirect) **服务,最终输出 index 数据。

结论:重定向自身服务,是可行的。

分组路由 Grouping Routes

如果有一组路由,前缀都是 /api/v1 开头,是否每个路由都需要加上 /api/v1 这个前缀呢?答案是不需要,Gin 提供的分组路由可以解决这个问题。

利用分组路由还可以更好地实现权限控制,例如将需要登录鉴权的路由放到同一分组中,简化权限控制。

func routerWithGroup() {
	defaultHandler := func(context *gin.Context) {
		context.JSON(http.StatusOK, gin.H{
			"path": context.FullPath(),
		})
	}

	engine := gin.Default()
	v1Group := engine.Group("/api/v1")
	{
		v1Group.GET("/hello", defaultHandler)
		v1Group.GET("/greet", defaultHandler)
	}

	v2Group := engine.Group("/api/v2")
	{
		v2Group.GET("/hello", defaultHandler)
		v2Group.GET("/greet", defaultHandler)
	}

	_ = engine.Run(":9999")
}

启动之后,可以看到 console 输出的信息。

访问 [**http://localhost:9999/api/v1/hello**](http://localhost:9999/api/v1/hello)** **之后,可以看到输出的结果。

上传文件

Gin 同样提供了对上传文件的支持。Gin 支持上传单个文件,也支持同时上传多个文件。

上传单个文件

func uploadSingleFile() {
	engine := gin.Default()
	engine.POST("/upload/single", func(context *gin.Context) {
		file, _ := context.FormFile("file")
		// context.SaveUploadedFile(file, file.Filename)
		context.JSON(http.StatusOK, gin.H{
			"result": fmt.Sprintf("'%s' uploaded!", file.Filename),
		})
	})

	_ = engine.Run(":9999")
}

访问 [**http://localhost:9999/upload/single**](http://localhost:9999/upload/single)** **之后,上传文件,可以得到:

控制台输出:

上传多个文件

上传多个文件,处理细节稍微有点不同。

func uploadMultipleFile() {
	engine := gin.Default()
	engine.POST("/upload/multiple", func(context *gin.Context) {
		form, _ := context.MultipartForm()
		files := form.File["file"]

		result := make(map[string]string)

		for _, file := range files {
			// context.SaveUploadedFile(file, file.Filename)
			result[file.Filename] = fmt.Sprintf("'%s' uploaded!", file.Filename)
		}
		
		context.JSON(http.StatusOK, result)
	})

	_ = engine.Run(":9999")
}

启动之后,同时上传多个 key 为 file 的文件,可以得到:

控制台的输出,如下:

HTML 模版 Template

Gin 默认使用模板 Go 语言标准库的模板 text/template 和 html/template,语法与标准库一致,支持各种复杂场景的渲染。参考官方文档 text/templatehtml/template

type student struct {
	Name string
	Age  int
}

func htmlTemplate() {
	engine := gin.Default()
	engine.LoadHTMLGlob("/Users/*/GolandProjects/go_study/gin/template/templates/*")

	stu1 := &student{Name: "Real", Age: 18}
	stu2 := &student{Name: "Alice", Age: 18}

	engine.GET("/arr", func(context *gin.Context) {
		context.HTML(http.StatusOK, "arr.tmpl", gin.H{
			"title":  "Gin",
			"stuArr": [2]*student{stu1, stu2},
		})
	})

	_ = engine.Run(":9999")
}

运行之后,访问 [**http://localhost:9999/arr**](http://localhost:9999/arr)** **可以得到运行结果:

其中,engine.LoadHTMLGlob() 方法中的参数可能访问不到具体的模版,所以需要填写绝对路径。attr.impl 模版文件,内容如下:

<html>
  <body>
    <p>hello, {{.title}}</p> {{range $index, $ele := .stuArr }}
    <p>{{ $index }}: {{ $ele.Name }} is {{ $ele.Age }} years old</p>
    {{ end }}
  </body>
</html>

中间件 MiddleWare

Gin 支持自定义一些中间件。

middleware 可以作用于全局、单个路由、分组路由,适应于不同的场景。

全局

func middlewareGlobal() {
	engine := gin.Default()
	// 作用于全局
	engine.Use(gin.Logger())
	engine.Use(gin.Recovery())

	// 作用于全局
	engine.Use(Logger())

	engine.GET("/hello", func(context *gin.Context) {
		keys := context.Keys
		fmt.Println(keys)
		context.JSON(200, gin.H{
			"message": "hello world",
			"Test":    context.GetString("Test"),
		})
	})

	_ = engine.Run(":9999")
}

func Logger() gin.HandlerFunc {
	return func(context *gin.Context) {
		now := time.Now()
		// 给Context实例设置一个值
		context.Set("Test", "123456")
		// 请求前
		context.Next()
		// 请求后
		latency := time.Since(now)
		log.Print(latency)
	}
}

运行之后的访问效果:

单个路由

func middlewareSingle() {
	engine := gin.Default()
	// 作用于单个路由
	engine.GET("/benchmark", Logger())

	_ = engine.Run(":9999")
}

处理结果类似。

分组路由

func middlewareGroup() {
	engine := gin.Default()

	// 作用于某个组
	authorized := engine.Group("/")
	authorized.Use(AuthRequired())
	{
		authorized.POST("/login", loginEndpoint())
		authorized.POST("/submit", submitEndpoint())
	}

	_ = engine.Run(":9999")
}

func AuthRequired() gin.HandlerFunc {
	return func(context *gin.Context) {
		if context.GetBool("authenticated") {
			context.Next()
		} else {
			context.String(401, "unauthorized")
		}
	}
}

func loginEndpoint() gin.HandlerFunc {
	return func(context *gin.Context) {
		context.String(200, "login")
	}
}

func submitEndpoint() gin.HandlerFunc {
	return func(context *gin.Context) {
		context.String(200, "submit")
	}
}

正常访问,返回的 code 为 401,符合预期。

运行结果:

热加载调试 Hot Reload

Python 的 Flask 框架,有 debug 模式,启动时传入 debug=True 就可以热加载(Hot Reload, Live Reload)了。即更改源码,保存后,自动触发更新,浏览器上刷新即可。免去了杀进程、重新启动之苦。

Gin 原生不支持,但有很多额外的库可以支持。例如:

  • github.com/codegangsta/gin
  • github.com/pilu/fresh

安装 pilu/fresh 依赖,使用:

go get -v -u github.com/pilu/fresh

安装好后,只需要将 go run main.go 命令换成 fresh 即可。每次更改源文件,代码将自动重新编译(Auto Compile)。

Reference

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值