假设你的go环境已经配置好了
mkdir -p $GOPATH/src/github.com/zxhjames/Zonst
//在gopath的目录下新建该文件夹(在这里我以自己的github仓库地址命名为主)
cd $_
//进入该项目根目录
go get -v github.com/gin-gonic/gin
//获取gin框架
pwd //显示在当前的项目根目录下 GOPATH/src/github.com/zxhjames/Zonst
mkdir -p gindemo1/main && cd gindemo1/main
vi main.go
package main
import "github.com/gin-gonic/gin"
func main(){
//1.创建gin的实例
router:=gin.Default()
//2.发送get请求,并声明回调函数
router.GET("/ping",func(c *gin.Context){
//3.返回一个json格式的数据
c.JSON(200,gin.H{
"message":"pong",
})
})
//4.启动服务器,默认监听8080端口
router.Run()
{
"message": "pong"
}
请求路由分为
多种请求类型
(get/post/delete/put…等8种请求类型)绑定静态文件夹
(可以将gin作为静态服务器使用)参数作为url
(多见于restful服务中)泛绑定
(类似于nginx的路由匹配)package main
import "github.com/gin-gonic/gin"
func main(){
r:=gin.Default()
//get
r.GET("/get", func(context *gin.Context) {
context.String(200,"get")
})
//post
r.GET("/post", func(context *gin.Context) {
context.String(200,"post")
})
//delete
r.Handle("DELETE","/delete", func(context *gin.Context) {
context.String(200,"delete")
})
//设置多个路由方法,多种请求都可以同时打到any上
r.Any("any", func(context *gin.Context) {
context.String(200,"any")
})
r.Run()
}
这种方式可以直接将本地的静态资源映射到服务器的URL上
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
/**
请求路由,静态文件夹
*/
func main(){
r:=gin.Default()
//设置静态文件夹绑定
r.Static("/assets","./assets")
//或者另外一种方法
r.StaticFS("/static",http.Dir("static"))
//设置单个静态资源
r.StaticFile("/favicon.ico","./favicon.ico")
r.Run()
}
go build -o router_static && ./router_static
注意这里不能直接在goland里运行,会访问不到的,最后使用
curl "http://localhost:8080/assets/a.html"
curl "http://localhost:8080/static/b.html"
可以分别访问到静态资源
package main
import "github.com/gin-gonic/gin"
func main(){
r:=gin.Default()
r.GET("/:name/:id", func(context *gin.Context) {
context.JSON(200,gin.H{
"name":context.Param("name"),
"id":context.Param("id"),
})
})
r.Run()
}
http://localhost:8080/james/10
{
"id": "10",
"name": "james"
}
package main
import "github.com/gin-gonic/gin"
func main(){
//声明gin实例
r:=gin.Default()
r.GET("/test", func(context *gin.Context) {
//获取第一个参数
firstName := context.Query("first_name")
//获取第二个参数,有默认值
lastName := context.DefaultQuery("last_name",
"last_default_name")
context.String(200,"%s,%s",firstName,lastName)
})
r.Run()
}
可以看到,第二个参数传不传值是有区别的
curl -X GET 'http://localhost:8080/test?first_name=james&last_name=vincent' //output james,vincent
curl -X GET 'http://localhost:8080/test?first_name=james' //output james
package main
import "github.com/gin-gonic/gin"
func main(){
r:=gin.Default()
// 所有以/user/为前缀的的URL都要打到helloworld上
r.GET("/user/*action", func(context *gin.Context) {
context.String(200,"hello world")
})
r.Run()
}
curl -X GET 'http://localhost:8080/user/hhh
curl -X GET 'http://localhost:8080/user/xxx
由于泛绑定了以/user为前缀的地址,可以看到最终都会显示hello world
package main
import (
"bytes"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
)
/**
获取body内容
*/
func main(){
r:=gin.Default()
r.POST("/test", func(context *gin.Context) {
//拿到body的字节内容
bodyByts,err:=ioutil.ReadAll(context.Request.Body)
if err != nil {
//如果抛异常则打印错误
context.String(http.StatusBadRequest,err.Error())
context.Abort()
}else{
/**
正确则输出正确内容
*/
context.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyByts))
firstName:=context.PostForm("firstName")
lastName:=context.DefaultPostForm("lastName","default")
context.String(200,"%s,%s,%s",firstName,lastName,string(bodyByts))
}
})
r.Run()
}
package main
import (
"github.com/gin-gonic/gin"
"time"
)
/**
测试传递结构体
*/
type Person struct {
/**
结构体参数绑定表单参数
*/
Name string `form:"name"`
Address string `form:"address"`
Birthday time.Time `form:"birthday" time_format:"2006-01-04"` //设置日期格式
}
func main(){
r:=gin.Default()
r.GET("/testing",testing)
r.POST("/testing",testing)
r.Run()
}
func testing(context *gin.Context){
var person Person
/**
这里是根据请求的content-type来做不同的bind操作
*/
//绑定结构体
if err:=context.ShouldBind(&person);err== nil {
//如果请求正确
context.String(200,"%v",person)
}else{
//如果请求参数错误
context.String(200,"person bind error:%v",err)
}
}
package main
import (
"github.com/gin-gonic/gin"
)
/**
验证结构体参数的验证
*/
type Person struct {
/**
结构体参数绑定表单参数
*/
//bind中有连续条件都要满足时,要使用,间隔,如果要求任意一个能满足那么就会用| 隔开
Age int `form:"age" binding:"required,gt=10"` //required表示三个参数必传,其中的age还要大于10adna
Address string `form:"address" binding:"required"`
Name string `form:"name" binding:"required"`
}
func main(){
r:=gin.Default()
r.GET("/testing", func(context *gin.Context) {
var person Person
//声明验证参数
if err:=context.ShouldBind(&person);err!=nil {
context.String(500,"%v",err)
}
context.String(200,"%v",person)
})
r.Run()
}
如果我们传入的age值小于或等于10,那么就不能满足验证条件,会访问失败
package main
import (
"net/http"
"reflect"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8"
)
// Booking contains binded and validated data.
type Booking struct {
//绑定验证器 bookabledate ,同时设置日期格式
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
//要保证checkout时间要大于checkin时间
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
func bookableDate(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
/**
重写验证器,预定时间一定要保证大于今天的日期
*/
if date, ok := field.Interface().(time.Time); ok {
today := time.Now()
if today.Year() > date.Year() || today.YearDay() > date.YearDay() {
return false
}
}
return true
}
func main() {
route := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
route.GET("/bookable", getBookable)
route.Run(":8080")
}
func getBookable(c *gin.Context) {
var b Booking
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
下面进行测试,首先我们输入一个合法的表单,两个日期都大于今天的日期,显然是合法的
下面是不合法的例子,这里的check_in还是昨天的日期,显然是不合法的
package main
import (
"github.com/gin-gonic/gin"
"io"
"os"
)
//gin的中间件
func main(){
//将日志结果输出到文件
f,_:=os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f) // 将普通日志写入文件
gin.DefaultErrorWriter = io.MultiWriter(f) // 将错误日志写入文件
r:=gin.New() // New()也是中间件
r.Use(gin.Logger(),gin.Recovery()) //使用recovery捕获错误,防止进程挂掉
r.GET("/test", func(context *gin.Context) {
name:=context.DefaultQuery("name","defaultName")
context.String(200,"%s",name)
})
r.Run()
}
接下来进行测试,每次测试完,我们都可以在项目的根目录下找到该日志文件
这次我们定义一个过滤IP地址的中间件,判断请求的IP数组中是否有与主机相同放入IP
package main
import "github.com/gin-gonic/gin"
/**
2.17 自定义白名单中间件
*/
func IPAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
/**
设置白名单服务器列表
*/
ipList:=[]string{
"127.0.0.1",
//"127.0.0.2",
}
flag:=false
clientIP:=c.ClientIP()
for _,host:=range ipList{
if clientIP == host {
flag = true
break
}
}
if !flag {
c.String(401,"%s not in iplist",clientIP)
c.Abort()
}
}
}
func main(){
r:=gin.Default()
/**
使用自己定义的文件
*/
r.Use(IPAuthMiddleware())
r.GET("/test", func(context *gin.Context) {
context.String(200,"hello test")
})
r.Run()
}
本机的ip为127.0.0.1,所以启动后,会显示 “hello test”,然而如果将请求数组中的IP改为127.0.0.2,那么将显示不再iplist中
package main
import (
"context"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
/**
优雅关停
*/
func main(){
r:=gin.Default()
r.GET("/test", func(context *gin.Context) {
//休眠5五秒
time.Sleep(5*time.Second)
context.String(200,"hello world")
})
/**
创建一个server对象
*/
srv:=&http.Server{
Addr: ":8085",
Handler: r,
TLSConfig: nil,
ReadTimeout: 0,
ReadHeaderTimeout: 0,
WriteTimeout: 0,
IdleTimeout: 0,
MaxHeaderBytes: 0,
TLSNextProto: nil,
ConnState: nil,
ErrorLog: nil,
}
/**
将server放入协程
*/
go func() {
/**
如果报错,或者是服务器没有开启,就报错
*/
if err:=srv.ListenAndServe();err!=nil && err!=http.ErrServerClosed{
log.Fatalf("listen:%s\n",err)
}
}()
//定义一个信号
quit:=make(chan os.Signal)
//阻塞channal
signal.Notify(quit,syscall.SIGINT,syscall.SIGTERM)
<-quit
log.Println("shutdown Server...")
//设置超时显示
ctx,cancel:=context.WithTimeout(context.Background(),10*time.Second)
defer cancel()
//关服务器
if err:=srv.Shutdown(ctx);err!=nil{
log.Fatal("server shutdown:",err)
}
log.Println("server exiting")
}
运行程序,由于我们给请求的响应时间设定为5秒,所以当我们另外开一个终端
输入curl -X GET "http://127.0.0.1:8085/test"
,并且马上关闭服务器,这时可以发现,终端仍然可以显示出hello world,同时服务器终端也显示了server shutdown
这次学习一下模版渲染的技术,首先我们在项目的main文件夹下面新建一个template文件夹,在里面新建一个a.html文件,并且定义一个变量title
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
{{.title}}
body>
html>
接下来去创建main.go文件,如下
package main
import "github.com/gin-gonic/gin"
func main(){
r:=gin.Default()
r.LoadHTMLGlob("template/*")
r.GET("/index", func(context *gin.Context) {
context.HTML(200,"a.html",gin.H{
//绑定到html的属性上
"title":"a.html",
})
})
/**
注意,这一步要在手动启动服务器,不能直接在golang里面run
*/
r.Run()
}