Web is Easy,Let's try Gin:
一. 快速启动
下载gin的包:go get github.com/gin-gonic/gin
编写gin的hello world如下:
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/hello", func(context *gin.Context) {
context.Writer.WriteString("你好!")
})
router.Run(":80")
}
访问一下:
go的原生http启动方法是通过net/http 包来启动的,通过http.ListenAndServer(addr, handler) 来启动服务,在这个包种有一个结构体叫Server ,http服务要通过它的各种方法来做,http.ListenAndServe 也是调用了server.ListenAndServe ,上面的router 其实是一个引擎,也可以说是一个处理器,Run 方法实际也调用了http.ListenAndServe() 这个方法,不过第二个参数传的是router本身,用原生的方法的话不传就是一个默认的defaultServeMux ,http.handleFunc 其实就是在这个mux 中注册一些东西。
二. 不同种类的Http请求方法
gin支持Get Post Put Delete 一系列的Restful风格的请求方式,使用的方式如下:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(context *gin.Context) {
context.Writer.WriteString("GET方法")
})
router.POST("/", func(context *gin.Context) {
context.Writer.WriteString("POST方法")
})
router.PUT("/", func(context *gin.Context) {
context.Writer.WriteString("PUT方法")
})
router.DELETE("/", func(context *gin.Context) {
context.Writer.WriteString("DELETE方法")
})
router.Run(":80")
}
三. 获取不同位置的参数
3.1 获取query参数
query参数即为url 中问号后面的键值对:
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/", func(context *gin.Context) {
name := context.Query("name")
context.Writer.WriteString("hello " + name)
})
router.Run(":80")
}
3.2 获取Post的表单参数
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.POST("/", func(context *gin.Context) {
name := context.PostForm("name")
context.Writer.WriteString("hello " + name)
})
router.Run(":80")
}
3.3 获取path路径参数
是url 路径上的一部分,比如路径可以写成这样:http://localhost/jerry jerry就是请求的参数,获取方法如下:
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.GET("/:name", func(context *gin.Context) {
name := context.Param("name")
context.Writer.WriteString("hello " + name)
})
router.Run(":80")
}
3.4 获取json数据
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
router.POST("/:name", func(context *gin.Context) {
nameBytes, _ := context.GetRawData()
context.Writer.WriteString("hello " + string(nameBytes))
})
router.Run(":80")
}
3.5 参数绑定
上面的调用方法在实际使用过程中其实是比较麻烦的,尤其是json的绑定方法,可以使用context.ShouldBind(&obj) 方法自动绑定到一个对象上去使用,也有对应的context.ShouldBindQuery() 和context.ShouldBindJSON() 方法可以使用
四. 路由
4.1 普通的路由
就像上面的写法即可
如果想要让所有的请求方法(get、post、delete等)在该路径下都处理的话可以用any方法:
func main() {
router := gin.Default()
router.Any("/hello", func(context *gin.Context) {
context.Writer.WriteString("hello")
})
router.Run(":80")
}
404处理的方法可以用NoRoute 解决,参数就是一个func(context *gin.Context) 即可
4.2 路由分组
可以将一个拥有共同前缀的路由划分成一个路由组,如下:
package main
import "github.com/gin-gonic/gin"
func main() {
router := gin.Default()
group := router.Group("/groups")
{
group.GET("/hello", func(context *gin.Context) {
context.Writer.WriteString("hi")
})
}
router.Run(":80")
}
访问http://localhost/groups/hello 即可,group.GET 默认已经填上了groups 前缀,group 也同样拥有Get Post 等请求方法,并且可以嵌套使用。这里router 是没有默认前缀的
五. 中间件middleware
需要实现一个HandlerFunc 类型的函数,就是上面处理函数的样子,然后使用router.Use(func) 即可实现
func timeDurationMiddleware(context *gin.Context) {
now := time.Now().Unix()
context.Next()
timeCost := time.Now().Unix() - now
context.Writer.WriteString(strconv.Itoa(int(timeCost)))
}
func main() {
router := gin.Default()
router.Use(timeDurationMiddleware)
group := router.Group("/groups")
{
group.GET("/hello", func(context *gin.Context) {
time.Sleep(time.Second + 1)
context.Writer.WriteString("hi ")
})
}
router.Run(":80")
}
这样就实现了一个记录调用时间的中间件,这里面的Next() 函数表示先执行后面的handlerFunc ,按照定义的顺序在访问http://localhost/groups/hello 时会先走全局注册的timeDurationMiddleware 中间件,然后走hello 请求的那个处理函数,那么调用了context.Next 就会在那个位置tag注释的那个位置停下,去执行下面处理函数,走完之后再回去执行tag下面的代码,类似于一个栈的过程。还有一个函数是context.Abort() ,表示当前这个函数执行完就不走下面的中间件和处理函数了。
gin的Default默认了两个中间件,可以使用gin.New()来获取一个没有中间件的引擎handler
六. 自定义Log
较为麻烦,还没整理清原理,后续补充~
七. 参数校验
gin自带了validation的验证,使用的时候需要加tag并且用ShouldBind 的方式:
type People struct {
Name string `json:"name" binding:"required"`
}
func main() {
router := gin.Default()
group := router.Group("/groups")
{
group.POST("/hello", func(context *gin.Context) {
p := new(People)
if err := context.ShouldBindJSON(p); err != nil {
fmt.Println("出错了", err.Error())
} else {
fmt.Println(p)
}
})
}
router.Run(":80")
}
这是一个例子,要求name字段必填,如果不填就会出现第一行的错误。 还有一些常见的验证如下(多种验证用逗号分隔,不要乱加空格):
eqfield=
oneof=a b c
email
gt=3
gte=3
eq=3
len=5
八. 权限中间件
可以使用第三方库jwt-go 来实现jwt的认证,jwt的三部分分别是头部,负载,签名,代码如下:
package jwt
import (
"github.com/dgrijalva/jwt-go"
"time"
)
const (
TokenExpireDuration = time.Hour * 2
)
var jwtSecret = []byte("密钥")
type MyClaims struct {
UserID int64 `json:"user_id"`
Username string `json:"username"`
jwt.StandardClaims
}
func GenToken(userId int64, username string) (string, error) {
c := MyClaims{
UserID: userId,
Username: username,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
Issuer: "bluebell",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
return token.SignedString(jwtSecret)
}
func ParseToken(tokenString string) (*MyClaims, error) {
var claims = new(MyClaims)
_, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil {
return nil, err
}
return claims, nil
}
然后实现一个认证的中间件:
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
authHeader := c.Request.Header.Get("Authorization")
mc, err := jwt.ParseToken(parts[1])
if err != nil {
response.ResponseError(c, response.CodeInvalidToken)
c.Abort()
return
}
c.Next()
}
}
九. Https支持
首先需要有证书,然后将ListenAndServe 修改为ListenAndServeTLS() 即可(或者调用gin引擎的RunTLS):
func main() {
gin.SetMode(gin.ReleaseMode)
router := gin.Default()
group := router.Group("/groups")
{
group.POST("/hello", func(context *gin.Context) {
p := new(People)
if err := context.ShouldBindJSON(p); err != nil {
fmt.Println("出错了", err.Error())
} else {
fmt.Println(p)
}
})
}
router.RunTLS(":443", "server.pem", "server.key")
}
十. 优雅关闭
可以通过第三方的endless 实现,但是可以手写一个,不用引入第三方包:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
}
十一. 一个进程启动多个服务
启动两个协程分别去启动server即可,大概的写法如下:
func main() {
r1 := gin.New()
r2 := gin.New()
var g errgroup.Group
g.Go(func() error {
return r1.Run(":8080")
})
g.Go(func() error {
return r2.Run(":8081")
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
|