Gin 简介
Gin官网
https://gin-gonic.com/
gin简介
Gin是一个golang的微框架,基于httprouter,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点。
gin特征
速度快
基于基数树的路由,内存占用小。没有反射。可预测的 API 性能。
中间件支持
传入的 HTTP 请求可以由中间件链和最终操作处理。例如:Logger、Authorization、GZIP 最后在 DB 中发布一条消息。
Crash-free
Gin 可以捕获 HTTP 请求期间发生的panic并恢复它。这样,你的服务器将始终可用。
JSON 验证
Gin 可以解析和验证请求的 JSON - 例如,检查所需值的存在。
路由分组
更好地组织您的路线。需要授权与不需要授权,不同的 API 版本……此外,组可以无限嵌套,而不会降低性能。
错误管理
Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件、数据库并通过网络发送它们。
内置渲染
Gin 为 JSON、XML 和 HTML 渲染提供了一个易于使用的 API。
可扩展
创建一个新的中间件非常简单,只需查看示例代码即可。
第一个gin
安装gin
go get -u github.com/gin-gonic/gin
实现代码
package main
import "github.com/gin-gonic/gin"
func Hello(c *gin.Context) {
// c.String(200, "hello,%s", "zs")
c.JSON(200, gin.H{
"name": "tom",
"age": "20",
})
}
func main() {
e := gin.Default()
e.GET("/hello", Hello)
// 默认在8080端口
e.Run()
}
运行
浏览器输入:http://localhost:8080/hello
{
"age": "20",
"name": "tom"
}
Gin实现用户登录
实现步骤
创建一个文件tempates
在项目跟目录下面创建给文件夹tempates,用来保存静态文件
创建一个登录html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="Login">
</form>
</body>
</html>
创建一个欢迎html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome</title>
</head>
<body>
Welcome, {{.username}}
</body>
</html>
使用Gin处理逻辑
package main
import "github.com/gin-gonic/gin"
func MyHandler(c *gin.Context) {
c.JSON(200, gin.H{
"hello": "hello world",
})
}
func Login(c *gin.Context) {
// 跳转到login.html页面
c.HTML(200, "login.html", nil)
}
func DoLogin(c *gin.Context) {
// 获取表单的值
username := c.PostForm("username")
password := c.PostForm("password")
c.HTML(200, "welcome.html", gin.H{
"username": username,
"password": password,
})
}
func main() {
e := gin.Default()
// 加载templates目录下的文件
e.LoadHTMLGlob("templates/*")
e.GET("/login", Login)
e.POST("/login", DoLogin)
e.Run()
}
Gin请求参数
Get请求参数
使用
c.Query("key")、或者c.DefaultQuery("key")方法
package main
import "github.com/gin-gonic/gin"
func TestQueryString(c *gin.Context) {
username := c.Query("username")
// 第二个参数为默认值 如果没有就使用它
site := c.DefaultQuery("site", "默认.com")
c.String(200, "username:%s, site:%s", username, site)
}
func main() {
e := gin.Default()
// url : http://localhost:8080/testQueryString?username=zs&site=zs.com
e.GET("/testQueryString", TestQueryString)
e.Run()
}
运行结果
username:zs, site:zs.com
Post参数
使用c.PostForm("key")、或者 c.DefaultQuery("key")方法
func DoLogin(c *gin.Context) {
username := c.PostForm("username")
// 第二个参数为默认值 如果没有就使用它
password := c.DefaultPostForm("password", "123")
c.HTML(200, "welcome.html", gin.H{
"username": username,
"password": password,
})
}
使用Postman或者Post表单测试
路劲参数(restful风格)
使用c.Param("key")方法
package main
import "github.com/gin-gonic/gin"
func TestPathParam(c *gin.Context) {
s := c.Param("username")
c.String(200, "Username:%s", s)
// 输出:Username:ghz
}
func main() {
e := gin.Default()
// http://localhost:8080/hello/ghz
e.GET("/hello/:username", TestPathParam)
e.Run()
}
既有Get也有Post
package main
import "github.com/gin-gonic/gin"
func TestGetAndPost(c *gin.Context) {
page := c.DefaultQuery("page", "0")
key := c.PostForm("key")
c.String(200, "Page:%s, Key:%s", page, key)
}
func main() {
e := gin.Default()
// http://localhost:8080/query?page=1
e.POST("/query", TestGetAndPost)
e.Run()
}
使用Postman或者Post表单测试
Gin表单处理
创建一个HTML表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>多课网,听老郭讲golang</title>
</head>
<body>
<h1>用户注册页面</h1>
<form action="/register" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
爱好:
<input type="checkbox" name="hobby" value="swiming">游泳
<input type="checkbox" name="hobby" value="basketball">篮球
<br>
性别:<input type="radio" name="gender" id="1" value="m">男
<input type="radio" name="gender" id="2" value="f">女
<br>
城市: <select name="city">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
</select>
<br>
<input type="submit" value="注册">
</form>
</body>
</html>
Go code
package main
import "github.com/gin-gonic/gin"
func Regsiter(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
// 获取多选框的内容 数组类型
hobby := c.PostFormArray("hobby")
gender := c.PostForm("gender")
city := c.PostForm("city")
c.String(200, "Username:%s, Password:%s, hobby:%s, gender:%s, city:%s", username, password, hobby, gender, city)
}
func GoRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}
func main() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.POST("/register", Regsiter)
e.GET("/register", GoRegister)
e.Run()
}
运行结果
Username:ghz, Password:123, hobby:[swiming basketball], gender:m, city:beijing
Gin数据绑定
绑定Form表单
package main
import (
"github.com/gin-gonic/gin"
)
type User struct {
Username string `form:"username"`
Password string `form:"password"`
Hobby []string `form:"hobby"`
Gender string `form:"gender"`
City string `form:"city"`
}
func Regsiter(c *gin.Context) {
var user User
c.ShouldBind(&user)
c.String(200, "User:%s", user)
}
func GoRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}
func main() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.POST("/register", Regsiter)
e.GET("/register", GoRegister)
e.Run()
}
绑定查询参数
package main
import (
"log"
"github.com/gin-gonic/gin"
)
type User struct {
Username string `form:"username"`
Password string `form:"password"`
}
func TestGetBind(c *gin.Context) {
var user User
err := c.ShouldBind(&user)
if err != nil {
log.Fatal(err)
}
c.String(200, "User:%s", user)
}
func main() {
e := gin.Default()
// http://localhost:8080/testGetBind?username=ghz&password=123
e.GET("/testGetBind", TestGetBind)
e.Run()
}
路径请求参数绑定
package main
import (
"log"
"github.com/gin-gonic/gin"
)
type User struct {
Username string `uri:"username"`
Password string `uri:"password"`
}
func TestGetBind(c *gin.Context) {
var user User
err := c.ShouldBindUri(&user)
if err != nil {
log.Fatal(err)
}
c.String(200, "User:%s", user)
}
func main() {
e := gin.Default()
// http://localhost:8080/testGetBind/ghz/123
e.GET("/testGetBind/:username/:password", TestGetBind)
e.Run()
}
注意:结构体和绑定方法的变化
Gin访问静态文件集成BootStrap框架
下载BootStrap
下载地址:https://getbootstrap.com/
添加bootstrap css和js文件
创建一个assets文件夹,将css和js文件添加到该文件夹
创建html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
<title>Login</title>
</head>
<body>
<div class="container">
<form>
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>
Go Code
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func Login(c *gin.Context) {
c.HTML(200, "login.html", nil)
}
func main() {
e := gin.Default()
e.LoadHTMLGlob("templates/*")
e.Static("/assets", "./assets")
e.StaticFS("/croot", http.Dir("c:/"))
e.StaticFile("/favicon.ico", "./assets/favicon.ico")
e.GET("/login", Login)
e.POST("/login", DoLogin)
e.Run()
}
Gin使用中间件
中间件听起来非常高大上的名字,实际非常简单,就是在请求中间起到拦截作用的处理函数。
Gin默认中间件
如果你使用Gin.Default()实例化gin引擎,默认有两个中间件,Logger和Recovery,分别用来处理日志和处理错误。如果使用gin.New()需要重新添加。
// 新建一个没有任何默认中间件的路由
r := gin.New()
// 全局中间件
// Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。
r.Use(gin.Recovery())
自定义中间件
- 自定义中间件非常简单,定义一个符合下面格式的处理函数
type HandlerFunc func(*Context)
- 使用
Use方法调用
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func TestMW(c *gin.Context) {
c.String(200, "hello,%s", "ghz")
}
func MyMiddleware1(c *gin.Context) {
fmt.Println("我的第一个中间件")
}
func MyMiddleware2(c *gin.Context) {
fmt.Println("我的第二个中间件")
}
func main() {
/* func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
} */
// e := gin.Default()
// e := gin.New()
e := gin.Default()
e.Use(MyMiddleware1, MyMiddleware2)
e.GET("testmw", TestMW)
e.Run()
}
使用Gin BasicAuth中间件
Gin提供了BasicAuth中间件,用来对网站资源的访问保护。
示例
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
// 模拟一些私人数据
var secrets = gin.H{
"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
"austin": gin.H{"email": "austin@example.com", "phone": "666"},
"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
}
func main() {
r := gin.Default()
// 路由组使用 gin.BasicAuth() 中间件
// gin.Accounts 是 map[string]string 的一种快捷方式
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
// /admin/secrets 端点
// 触发 "localhost:8080/admin/secrets
authorized.GET("/secrets", func(c *gin.Context) {
// 获取用户,它是由 BasicAuth 中间件设置的
user := c.MustGet(gin.AuthUserKey).(string)
fmt.Println(user)
if secret, ok := secrets[user]; ok {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
} else {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
}
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
测试
在浏览器输入localhost:8080/admin/secrets时,会弹出一个对话框,要求输入正确的用户名和密码,才能访问资源。
Gin cookie的使用
cookie是服务器向客户端写的一些数据,可以实现像自动登录等功能。
Gin cookie的使用
package main
import "github.com/gin-gonic/gin"
func Handler(c *gin.Context) {
// 获得cookie
s, err := c.Cookie("username")
if err != nil {
s = "zs"
// 设置cookie
// security为true 只能https访问 false http也能访问
// 参数:cookie名称 cookie值 存活时间s 当前根目录 域 security 是否为http(js能否读到cookie)
c.SetCookie("username", s, 60*60, "/", "localhost", false, true)
}
c.String(200, "测试cookie")
}
func main() {
e := gin.Default()
e.GET("/test", Handler)
e.Run()
}
基于安全的考虑,需要给cookie加上
Secure和HttpOnly属性,HttpOnly比较好理解,设置HttpOnly=true的cookie不能被js获取到,无法用document.cookie打出cookie的内容。
Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie只能用https协议发送给服务器,用http协议是不发送的。
Gin 使用Session
因为http是无状态、短连接,如何保存客户端和服务器直接的会话状态呢?可以使用session。
使用gin session中间件
gin 本身没有对session的支持,可以使用第三方中间件。
go get github.com/gin-contrib/sessions
import "github.com/gin-contrib/sessions"
该中间件提供了很多后端支持:
- cookie-based
- Redis
- memcached
- MongoDB
- memstore
- PostgreSQL
实例
package main
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
store := cookie.NewStore([]byte("secret"))
// 注入中间件
r.Use(sessions.Sessions("mysession", store))
r.GET("/hello", func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("hello") != "world" {
session.Set("hello", "world")
session.Save()
}
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
r.Run(":8000")
}