Iris - 向后端传参

Iris 2020-01-12 6096 字 1676 浏览 点赞

起步

在前后端交互时,会有数据往来,我把数据的“往”称作:向后端传参。我觉得一次 url 的请求与调用 api 在意义上没有多大区别,仅形式上不那么直观罢了。为了直观所以出现了 grpc 则是后话。

现在我们来看看 Iris 框架是如何解析前端传来的参数,同时也会简单说一说前端的传参方式。

URL Params

最简单的方式就是给 url 赋予语义,让 url 的组成部分成为传递过来的参数。比如将 url 设计为:http://user/{name},这样一来就可以动态的传入 name,根据 name 的值对不同的用户打招呼。示例代码可以如下:

// app.Post(...)  也允许以下操作
app.Get("/user/{name:string}", func(ctx iris.Context) {
    name := ctx.Params().GetDefault("name", "stranger")
    // 返回 interface{} 类型,因此类型断言
    namestr, _ := name.(string)
    ctx.HTML("Hello, " + namestr)
})

为了更精细控制 url params 的格式、类型,你可以阅读我的这篇文章 Iris - 路由(2)

url params 的缺点是,当需要传递多个参数时,参数之间应该尽量保持层级关系。比如获取一本书的详情,其 url 可以是:http://{category}/{bookname}/{id};层级呈现:分类->书名->书号。当多个参数之间不存在层级关系,并非不可以 url params 传参,只是不易于人的理解。譬如:http://user/{name}/{age}/{gender},年龄与性别属于平级关系,凭啥 age 在前面 gender 在后面呢?

Query Params

为了弥补 url params 的不足,于是有了 query params,其请求格式:http://$your_host:port/path?query

注意了,当 path 结束之后,后面接上 ?,然后才是 query 部分。query 的格式也有所不同,需要写明键名与键值,用 = 符号连接,多个键值对之间用 & 连接。

示例:

http://$your_host:port/path?key1=value1

http://$your_host:port/path?key1=value1&key2=value2

因而较为合理的 url 设计方式是:http://user/{name}?age=15&gender=girl。对应 Iris 解析参数代码如下:

// app.Post(...)  也允许以下操作
app.Get("/user/{name:string}", func(ctx iris.Context) {
    // url params
    name := ctx.Params().GetDefault("name", "stranger")
    // query params
    age := ctx.URLParamInt32Default("age", 0)
    gender := ctx.URLParamDefault("gender", "unknown")
    // make url
    html := fmt.Sprintf("Name: %v \nAge: %v \nGender: %v\n", name, age, gender)
    ctx.HTML(html)
})
  • ctx.URLParamInt32Default 表示解析出来的值为 int32 型,Default 的意思如果没有这个键名就用默认值,也就是传入的第二个参数。
  • ctx.URLParamDefault 表示返回 string 型,其余同上。

iris.Context 有一系列 URLParam* 接口,都是用来提取 query params 中的数据,用法也都大同小异。详见 URL query parameters

Iris 为让开发者更灵活开发,可通过 ctx.Request() 获取 http 请求对象。有了请求对象也就有了数据来源,也就有了获取 query params 的权利。如官方说明:

ctx.URLParam("lastname") == ctx.Request().URL.Query().Get("lastname")

查看 URLParamDefault 源码也能发现 URLParam* 系列接口基本是直接或间接使用了更底层的 ctx.Request()...

// URLParamDefault 源码

// URLParamDefault returns the get parameter from a request, if not found then "def" is returned.
func (ctx *context) URLParamDefault(name string, def string) string {
    if v := ctx.request.URL.Query().Get(name); v != "" {
        return v
    }

    return def
}

Form Data

form 表单最常见的场景是创建用户。必要的 html 已经写好如下:

<form action="http://127.0.0.1:8080/user" method="post">
  <label for="name">Name:</label>
  <input type="text" name="name">
  <label for="passwd">Passwd:</label>
  <input type="password" name="passwd">
  <button type="submit">Create</button>
</form>

Iris 解析 form 表单方式如下:

app.Post("/user", func(ctx iris.Context) {
    // 提取 form 表单中的参数
    name := ctx.FormValue("name")
    passwd := ctx.FormValue("passwd")
    
    if name == "" || passwd == "" {
        ctx.HTML("input err, pls try again")
    } else {
        html := fmt.Sprintf("create user %v successfully", name)
        ctx.HTML(html)
    }
})
  • 与 URLParam 相同,为提取 form data 于是有了 FormValue 接口。
  • 同样可以提取 form data 的还有 PostValue* 接口。也就是说,以上代码可以修改如下:
app.Post("/user", func(ctx iris.Context) {
    // 提取 form 表单中的参数
    name := ctx.PostValue("name")
    passwd := ctx.PostValue("passwd")
    ...
})
  • 更多请详见 Forms

现在问题来了,FormValue 与 PostValue 都能解析 form data,有啥区别呢?区别就在 <form> 标签的method 属性中。

<form action="http://127.0.0.1:8080/user" method="post">

<form action="http://127.0.0.1:8080/user" method="get">

form 标签允许 get 或者 post 的请求方式,但请注意,form get 实际是以 query params 方式传参,与 post 存在本质区别。当使用 FormValue 时,不论 form get 还是 form post 都可以成功提取到数据;使用 PostValue 时,只有 form post 请求才能提取到数据。


Iris 亦提供了 UploadFormFiles 方法用于处理文件上传。

方法签名:

UploadFormFiles(destDirectory string, before ...func(Context, *multipart.FileHeader)) (n int64, err error)
  • destDirectory 表示存储目录。
  • before 表示在文件写入磁盘之前的操作(可以没有这个操作,第二个参数不传即可)。

前端 html 如下:

<form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">
  <label for="file"></label>
  <input type="file" name="file">
  <button type="submit">上传</button>
</form>

后端代码如下:

app.Post("/upload", func(ctx iris.Context) {
    _, err := ctx.UploadFormFiles("store",
        // 修改文件名
        func(ctx iris.Context, file *multipart.FileHeader) {
            clientHost := ctx.RemoteAddr()
            file.Filename = clientHost + "-" + file.Filename
        })

    if err != nil {
        ctx.HTML("upload err")
    } else {
        ctx.HTML("upload successfully")
    }
})

JSON

当前最流行的是 Restful 设计,也就是传递数据用 json 格式,请求头中有 Content-Type: application/json。你需要区分它与 x-www-form-urlencoded、multipart/form-data 之间的不同(详情可见 四种常见的 POST 提交数据方式)。

现在假设用户需要更新用户信息,put 请求方法,json 格式传输数据。为方便测试,这里需要用到 vscode 的插件 REST Client。如果你有更好的选择,请随意。

http 文件:

PUT http://127.0.0.1:8080/user/zty
Content-Type: application/json

{
    "age": 16,
    "gender": "boy"
}

后端代码:

func main() {
    app.Put("/user/{name}", func(ctx iris.Context) {
        var info UserInfo
        // 注意传入指针
        err := ctx.ReadJSON(&info)
        if err != nil {
            ctx.HTML("update user info failed")
        } else {
            name := ctx.Params().Get("name")
            str := fmt.Sprintf("%v age: %v gender: %v", name, info.Age, info.Gender)
            ctx.HTML("update user info successfully\n" + str)
        }
    })

    app.Run(iris.Addr(":8080"))
}

// UserInfo 用户信息
type UserInfo struct {
    Age    int16  `json:"age"`
    Gender string `json:"gender"`
}
  • 利用 ctx.ReadJSON() + 结构体的方式提取出 json 数据。
  • 相应还有 ReadForm、ReadQuery,其用法就不言而喻了。

感谢



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论