11.2Go gin

11.1 Go gin

框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用。

成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用。

Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确。

具有快速灵活,容错方便等特点。

其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。

自身的net/http足够简单,性能也非常不错

框架更像是一些常用函数或者工具的集合。

借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

1.1. 使用gin

安装gin

go get github.com/gin-gonic/gin

ginhello world

package main

import (
    "net/http"

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

func main() {
    //gin.Default方法创建路由handler
    router := gin.Default()
    //绑定路由规则和路由函数,gin支持restful方法
    //gin把request和response都封装到了gin.Context的上下文环境中
    router.GET("/", func(c *gin.Context) {
        //返回字符串
        c.String(http.StatusOK, "Hello World")
    })
    //监听端口
    router.Run(":8000")
}

运行代码,即可访问http://0.0.0.0:8000/寻找hello world页面

1.2. restful api

借助于postman测试restful api

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
  • DELETE(DELETE):从服务器删除资源。
  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

restful案例

  • GET /zoos:列出所有动物园
  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
  • DELETE /zoos/ID:删除某个动物园
  • GET /zoos/ID/animals:列出某个指定动物园的所有动物
  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

状态码

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

gin代码示例

用户信息接口设计 restful风格

package main

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

func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    //匿名函数可以用有名函数,也可以匿名函数
    r.GET("/user/info", func(c *gin.Context) {
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message": "get user info succ",
        })
    })
    r.POST("/user/info", func(c *gin.Context) {
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message": "create user info succ",
        })
    })
    r.PUT("/user/info", func(c *gin.Context) {
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message": "update user info succ",
        })
    })
    r.DELETE("/user/info", func(c *gin.Context) {
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message": "delete user info succ ",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

非restful风格的API

/user/info
/user/create
/user/delete
/user/update

1.3. Gin框架参数传递

实例代码,传入2个get参数

querystring传递参数

package main

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

func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.GET("/user/search", func(c *gin.Context) {
        //username := c.DefaultQuery("username", "少林")
        username := c.Query("username")
        address := c.Query("address")
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message":  "pong",
            "username": username,
            "address":  address,
        })
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}

通过postman发送请求,携带参数

127.0.0.1:8080/user/search?username=超哥&address=沙河

DefaultQuery返回URL参数

package main

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

func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.GET("/user/search", func(c *gin.Context) {
        //使用默认值参数
        username := c.DefaultQuery("username", "超老板")
        //username := c.Query("username")
        address := c.Query("address")
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message":  "pong",
            "username": username,
            "address":  address,
        })
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}

访问结果

127.0.0.1:8080/user/search?address=沙河

1.4. 获取路径中的参数

不建议使用,使用表单或者querystring方式获取参数

package main

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

func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.GET("/user/search/:username/:address", func(c *gin.Context) {
        username := c.Param("username")
        address := c.Param("address")
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message":  "pong",
            "username": username,
            "address":  address,
        })
    })

    r.Run(":8080") // listen and serve on 0.0.0.0:8080
}

访问方式

127.0.0.1:8080/user/search/chao/beijing/

1.5. 获取表单的参数

package main

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

func main() {
    //Default返回一个默认的路由引擎
    r := gin.Default()
    r.POST("/user/search", func(c *gin.Context) {
        //username := c.DefaultPostForm("username", "少林")
        username := c.PostForm("username")
        address := c.PostForm("address")
        //输出json结果给调用方
        c.JSON(200, gin.H{
            "message":  "pong",
            "username": username,
            "address":  address,
        })
    })

    r.Run(":8080") // listen and serve on 0.0.0.0:8080
}

运行方式,使用post方式提交form

1.6. Gin文件上传

上传文件的名字应该由服务端统一文件名规则,防止非法字符

package main

import (
    "fmt"
    "log"
    "net/http"

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

func main() {
    router := gin.Default()
    // Set a lower memory limit for multipart forms (default is 32 MiB)
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // single file
        file, err := c.FormFile("file")
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "message": err.Error(),
            })
            return
        }
        //
        log.Println(file.Filename)
        dst := fmt.Sprintf("文件夹路径/%s", file.Filename)
        // Upload the file to specific dst.
        c.SaveUploadedFile(file, dst)
        c.JSON(http.StatusOK, gin.H{
            "message": fmt.Sprintf("'%s' uploaded!", file.Filename),
        })
    })
    router.Run(":8080")
}

上传单个文件

上传多个文件

package main

import (
    "fmt"
    "log"
    "net/http"

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

func main() {
    router := gin.Default()
    //文件比较大,默认文件存在内中占32M,大于32M就写入到磁盘
    // Set a lower memory limit for multipart forms (default is 32 MiB)
    //设置上传文件的内存大小
    // router.MaxMultipartMemory = 8 << 20  // 8 MiB
    router.POST("/upload", func(c *gin.Context) {
        // Multipart form
        form, _ := c.MultipartForm()
        files := form.File["file"]

        for index, file := range files {
            log.Println(file.Filename)
            dst := fmt.Sprintf("/Users/yuchao/go/src/gostudy/gobook/%s_%d", file.Filename, index)
            // Upload the file to specific dst.
            c.SaveUploadedFile(file, dst)
        }
        c.JSON(http.StatusOK, gin.H{
            "message": fmt.Sprintf("%d files uploaded!", len(files)),
        })
    })
    router.Run(":8080")
}

上传方式

1.7. 路由分组

把前缀一样的url,放入一个组

package main

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

func login(ctx *gin.Context) {
    ctx.JSON(200, gin.H{
        "message": "success",
    })
}

func read(ctx *gin.Context) {
    ctx.JSON(200, gin.H{
        "message": "success",
    })
}

func submit(ctx *gin.Context) {
    ctx.JSON(200, gin.H{
        "message": "success",
    })
}

func main() {
    //Default返回一个默认的路由引擎
    router := gin.Default()

    // Simple group: v1
    //   /v1/login
    //   /v1/submit
    //   /v1/read
    v1 := router.Group("/v1")
    {
        v1.POST("/login", login)
        v1.POST("/submit", submit)
        v1.POST("/read", read)
    }

    // Simple group: v2
    //   /v2/login
    //   /v2/submit
    //   /v2/read
    v2 := router.Group("/v2")
    {
        v2.POST("/login", login)
        v2.POST("/submit", submit)
        v2.POST("/read", read)
    }

    router.Run(":8080")
}

运行方式

1.8. Gin参数绑定

使用方便,提高开发效率

通过反射机制,自动提取querystring,from表单,json,xml等参数到struct中

通过http协议中的context type,识别json、xml或是表单

package main

import (
    "fmt"
    "net/http"

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

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

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

    // Example for binding JSON ({"user": "manu", "password": "123"})
    router.POST("/loginJSON", func(c *gin.Context) {
        var login Login

        if err := c.ShouldBindJSON(&login); err == nil {
            fmt.Printf("login info:%#v\n", login)
            c.JSON(http.StatusOK, gin.H{
                "user":     login.User,
                "password": login.Password,
            })
        } else {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        }
    })

    // Example for binding a HTML form (user=manu&password=123)
    router.POST("/loginForm", func(c *gin.Context) {
        var login Login
        // This will infer what binder to use depending on the content-type header.
        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()})
        }
    })

    // Example for binding a HTML querystring (user=manu&password=123)
    router.GET("/loginForm", func(c *gin.Context) {
        var login Login
        // This will infer what binder to use depending on the content-type header.
        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()})
        }
    })

    // Listen and serve on 0.0.0.0:8080
    router.Run(":8080")
}

json方式提交数据

form方式提交

1.9. gin框架渲染

json渲染

package main

import (
    "net/http"

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

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

    // gin.H is a shortcut for map[string]interface{}
    r.GET("/someJSON", func(c *gin.Context) {
        //第一种方式,自己拼json
        c.JSON(http.StatusOK, gin.H{"message": "你大爷", "status": http.StatusOK})
    })

    r.GET("/moreJSON", func(c *gin.Context) {
        // You also can use a struct
        var msg struct {
            Name    string `json:"user"`
            Message string
            Number  int
        }
        msg.Name = "大王"
        msg.Message = "大王,唐僧给你捉来了"
        msg.Number = 123
        // Note that msg.Name becomes "user" in the JSON
        c.JSON(http.StatusOK, msg)
    })
    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

xml渲染

package main

import (
    "net/http"

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

func main() {
    r := gin.Default()
    r.GET("/moreXML", func(c *gin.Context) {
        // You also can use a struct
        type MessageRecord struct {
            Name    string
            Message string
            Number  int
        }

        var msg MessageRecord
        msg.Name = "二王"
        msg.Message = "你大爷"
        msg.Number = 123
        c.XML(http.StatusOK, msg)
    })
    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

HTML 模板渲染

package main

import (
    "net/http"

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

func main() {
    router := gin.Default()
    //模板路径,一层就是templates/*
    //两层就是templates/**/*
    router.LoadHTMLGlob("templates/**/*")
    router.GET("/posts/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
            "title": "我是标题",
        })
    })
    router.GET("/users/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
            "title": "我是标题",
        })
    })
    router.Run(":8080")
}

访问路径

注意先go build main.go 再运行

http://127.0.0.1:8080/users/index
http://127.0.0.1:8080/posts/index

1.10. Gin 静态文件服务器

package main

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

func main() {
    r := gin.Default()
    //在main.go同级准备static文件夹,内含图片
    r.Static("/static", "./static")
    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

注意先go build main.go 再运行

go build main.go 
./main.go

http://127.0.0.1:8080/static/1.png

1.11. Go中间件

golang的net/http设计的一大特点就是特别容易构建中间件。

gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。

对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。

中间件分为全局中间件,单个路由中间件和群组中间件。

Gin框架允许请求处理过程中,加入用户自己的钩子函数。这个函数就是中间件
利用中间件可以处理例如耗时统计,日志打印,登录校验等

计算耗时的中间件

package main

import (
    "log"
    "time"

    "net/http"

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

func StatCost() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()

        //可以设置一些公共参数,
        c.Set("example", "12345")
        //等其他中间件先执行
        c.Next()
        //获取耗时
        latency := time.Since(t)
        //打印花费的时间 319微秒
        log.Printf("total cost time:%d us", latency/1000)
    }
}

func main() {
    //新建一个路由 gin.New(),没有中间件
    // gin.Default()最常用,包含了中间件
    //r := gin.New()
    r := gin.Default()
    //Use是全局中间件,传入一个
    r.Use(StatCost())

    r.GET("/test", func(c *gin.Context) {
        example := c.MustGet("example").(string)

        // it would print: "12345"
        log.Println(example) //
        c.JSON(http.StatusOK, gin.H{
            "message": "success",
        })
    })

    // Listen and serve on 0.0.0.0:8080
    r.Run(":8080")
}

1.12. Gin框架路由原理

gin路由部分用的是

https://github.com/julienschmidt/httprouter

package main

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

func index(ctx *gin.Context) {
    ctx.JSON(200, gin.H{
        "message": "index",
    })
}

func main() {
    //Defatult返回一个默认的路由引擎
    router := gin.Default()
    router.POST("/", index)
    router.POST("/search", index)
    router.POST("/support", index)
    router.POST("/blog/:post", index)
    router.POST("/about", index)
    router.POST("/contact", index)
    router.POST(":8080", index)
}

共用url前缀

posted @ 2019-07-27 21:58  笑得好美  阅读(742)  评论(0编辑  收藏  举报