Skip to content

接收请求

作为一个服务器,最基本的任务就是接收请求,然后是返回结果。

客户端发来的请求可以有多种,如 GET、POST、PUT、DELETE。 最基本的就是 GET、POST 两种,不过这里还是建议遵循 RESTful 规范。

基本处理:Handle()

gin 接收请求有一种最基本的方法,使用 Handle() 方法。

其方法签名如下:

func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes;
其接受的第一个参数为 HTTP 请求方式,第二个为路由路径,第三个为该请求对应的处理函数。

最基本的栗子,接收一个 GET 请求
func main() {
    app := gin.Default()

    app.Handle("GET", "/hello", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "msg": "Hello!",
        })
    })

    app.Run(":9090")
}

第4行调用了 Handle(),请求方式为 GET 方法,路由路径为 /hello,处理方法是返回一串JSON数据。

浏览器访问 http://localhost:9090/hello 可得到下面的结果

接收 POST、PUT 等方式的请求

通过 Handle() 第一个参数我们可以接收不同方式的请求,例如 POST

func main() {

    r := gin.Default()

    // http://127.0.0.1:9090/login
    r.Handle("POST", "/login", func(c *gin.Context) {    // 处理 POST 请求
        user := c.PostForm("user")    // 获取表单参数
        pass := c.PostForm("pass")    // 获取表单参数

        // 处理业务逻辑
        if user == "Boii" && pass == "123" {
            c.JSON(http.StatusOK, gin.H{
                "code": 2001,
                "msg":  "登录成功",
            })
            return
        }

        c.YAML(http.StatusOK, gin.H{
            "code": 4001,
            "msg":  "登录失败",
        })
    })

    r.Run(":9090")
}

通过 Postman 可以设定表单参数然后发起请求,我们用gin写的代码就可以接收到一个 POST 请求,并进行处理。

可以看到, Postman 中设置了两个参数 userpass,向 http://localhost:9090/login 发清请求。

关于获取参数后面会详解。

快捷方式:gin.GET()、gin.POST()...

像 GET、POST、PUT、DELETE 等这些 HTTP方式的处理函数会很常用,所以 gin 提供了一系列方法供我们更方便的使用。

源码
// POST is a shortcut for router.Handle("POST", path, handle).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPost, relativePath, handlers)
}

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodGet, relativePath, handlers)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodDelete, relativePath, handlers)
}

// PATCH is a shortcut for router.Handle("PATCH", path, handle).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPatch, relativePath, handlers)
}

// PUT is a shortcut for router.Handle("PUT", path, handle).
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodPut, relativePath, handlers)
}

// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodOptions, relativePath, handlers)
}

// HEAD is a shortcut for router.Handle("HEAD", path, handle).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle(http.MethodHead, relativePath, handlers)
}

通过源码可以看出,gin 很贴心的做了一层封装,提供了对应请求方式的 shortcut,使开发者更方便的调用。 参数上只需要传递 路由路径处理函数 即可,这些“快捷方式”会帮我们调用 handle()

eg:

1
2
3
4
5
6
7
8
9
func main() {
    r := gin.Default()

    // http://127.0.0.1:9090/hello
    r.GET("/hello", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{ "msg": "Hello"})
    })
    r.Run(":9090")
}
1
2
3
4
5
6
7
8
9
func main() {
    r := gin.Default()

    // http://127.0.0.1:9090/login
    r.POST("/login", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{ "msg": "Hello"})
    })
    r.Run(":9090")
}

404 页面

1
2
3
4
5
6
7
8
9
func main() {
    r := gin.Default()

    r.NoRoute(func(c *gin.Context) {
        c.HTML(http.StatusNotFound, "views/404.html", nil)
    })

    r.Run(":9090")
}
不需要指定路径,只要用户访问不存在的路径,就会执行此路由。

处理请求参数

获取 GET 请求的参数

GET 请求的参数会显式的携带在URL中。

可以通过 gin.Context.Query()gin.Context.DefaultQuery() 两种方法获取。

他们的区别是 DefaultQuery() 需要填入默认值。

func (c *Context) Query(key string) string;
func (c *Context) DefaultQuery(key, defaultValue string) string;
Example

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

    r.GET("/hello", func(c *gin.Context) {
        name := c.Query("name")
        age := c.DefaultQuery("age")
        c.JSON(200, gin.H{"msg": "Hello " + name + ", you're " + age})
    })

    r.Run(":9090")
}
age 给了值

age 没给值

获取 POST 表单参数

POST 请求的参数不会显式的携带在URL中,而是包裹在请求体 Body 中,是通过表单提交的。

可以通过 gin.Context.PostForm()gin.Context.DefaultPostForm() 两种方法获取。

同样他们的区别仅在于一个需要填入默认值。

func (c *Context) PostForm(key string) string;
func (c *Context) DefaultPostForm(key, defaultValue string) string;
Example
func main() {
    r := gin.Default()

    // http://127.0.0.1:9090/login
    r.POST("/login", func(c *gin.Context) {
        user := c.DefaultPostForm("user", "admin")
        pass := c.PostForm("pass")
        if user == "Boii" && pass == "123" {
            c.JSON(http.StatusOK, gin.H{
                "code": 2001,
                "msg":  "登录成功",
            })
            return
        }
        c.YAML(http.StatusOK, gin.H{
            "code": 4001,
            "msg":  "登录失败",
        })
    })
    r.Run(":9090")
}

处理其他格式的请求参数

除了URL中的参数、表单提交的参数,在客户端发起 HTTP 请求时还可以使用 JSON、XML、YAML 等格式。

获取这些格式的参数,可以通过 gin.Context.Bind() 来获取,

Bind() 有一系列的方法:

1
2
3
4
5
6
7
8
func (c *Context) Bind(obj interface{}) error;
func (c *Context) BindJSON(obj interface{}) error;
func (c *Context) BindXML(obj interface{}) error;
func (c *Context) BindQuery(obj interface{}) error;
func (c *Context) BindYAML(obj interface{}) error;
func (c *Context) BindHeader(obj interface{}) error;
func (c *Context) BindUri(obj interface{}) error;
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error;

显然,要解析请求中什么类型的数据就调用什么方法,参数 obj 需要我们传入一个结构体变量,用于装载解析后的数据。

结构体每个字段都需要有 tag,否则会解析失败。

Example
type UserInfo struct {
    User string
    Pass string
}

var u UserInfo

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

    r.POST("/login", func(c *gin.Context) {
        c.BindJSON(&u)
        c.JSON(200, gin.H{
            "user": u.User,
            "pass": u.Pass,
        })
    })

    r.Run(":9090")
}

这个例子可以解析 HTTP 请求中通过 JSON 携带的数据。

Bind()

如果想根据请求中的 content-type 属性来选择,可以使用 Bind() 方法,该方法会自动选择

源码
// gin 源码
// context.go

// Bind checks the Content-Type to select a binding engine automatically,
// Depending the "Content-Type" header different bindings are used:
//     "application/json" --> JSON binding
//     "application/xml"  --> XML binding
// otherwise --> returns an error.
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj interface{}) error {
    b := binding.Default(c.Request.Method, c.ContentType())
    return c.MustBindWith(obj, b)
}

// gin 源码
// binding.go

// Default returns the appropriate Binding instance based on the HTTP method
// and the content type.
func Default(method, contentType string) Binding {
    if method == http.MethodGet {
        return Form
    }

    switch contentType {
    case MIMEJSON:
        return JSON
    case MIMEXML, MIMEXML2:
        return XML
    case MIMEPROTOBUF:
        return ProtoBuf
    case MIMEMSGPACK, MIMEMSGPACK2:
        return MsgPack
    case MIMEYAML:
        return YAML
    case MIMEMultipartPOSTForm:
        return FormMultipart
    default: // case MIMEPOSTForm:
        return Form
    }
}

可以看到,context 中的 Bind() 方法调用了 binding.Default() 方法;

binding.Default() 方法中,如果请求方式是 GET 则返回 Form,否则的话,根据请求中的 content-type 属性返回对应的格式。

接着会调用 gin.Context.MustBindWith(obj, b) -> gin.Context.ShouldBindWith(obj, b) -> b.Bind()

b.Bind() 这里的时候,会根据 b 的类型调用各自的 Bind() 方法。

例如 JSON 的话会调用 jsonBinding.Bind() 方法,然后在里面调用 decodeJSON() 方法。

一次解析多个参数

上面登录的栗子中,我们简简单单的获取了两个参数,所以可以用两次 PostForm() 方法。

但是当参数多起来的时候,这种方式并不是很好。好在 gin 还提供另外的方法方便我们一次解析多个参数。

要一次解析多个参数,我们需要一个 结构体 来装载这些参数的值,然后将结构体传给 gin.Context.Bind()gin.Context.ShouldBind()gin.Context.ShouldBindQuery() 等方法。

func (c *Context) ShouldBind(obj interface{}) error;
func (c *Context) ShouldBindQuery(obj interface{}) error;

要注意的是,我们定义的这个结构体每个字段都需要有 tag,否则会解析失败。

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

    type UserInfo struct {
        User string `form:"user"`
        Pass string `form:"pass"`
    }
    // http://127.0.0.1:9090/login
    r.POST("/login", func(c *gin.Context) {
        var u UserInfo
        c.ShouldBind(&u)
        c.JSON(http.StatusOK, gin.H{
            "code": 2001,
            "msg":  "登录成功",
            "user": u.User,
            "pass": u.Pass,
        })
    })
    r.Run(":9090")
}

效果是一样的。

解析路由路径中的参数

gin.Context.Param()

路由路径有时候并不固定,而是根据实际情况变化的。

例如下面的栗子,不同的用户有不同的ID,要获取这个ID,需要用到 gin.Context.Param() 方法,填入冒号通配符后面的变量 id 即可。

在使用 PUT、DELETE 等请求方法的时候是修改、删除某条记录的目的,这需要在路径中指定一个关键值(如 id),这里就可以用像下面这样去解析。

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

    // http://127.0.0.1:9090/user/10086
    r.PUT("/user/:id", func(c *gin.Context) {
        userId := c.Param("id")
        c.JSON(http.StatusOK, gin.H{
            "code": 2001,
            "msg":  "ID is " + userId,
        })
    })
    r.Run(":9090")
}

gin.Context.Params

除了Param(),还可以使用 Params.Get()Params.ByName() 去获取想要的参数。

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

    // http://127.0.0.1:9090/Boii/18/1234567
    r.GET("/params/:name/:age/:tele", func(c *gin.Context) {
        params := c.Params

        name, _ := params.Get("name")
        age     := params.ByName("age")
        tele    := c.Param("tele")

        c.JSON(http.StatusOK, gin.H{
            "name": name,
            "age":  age,
            "tele": tele,
        })
    })

    r.Run(":9090")
}