起步
在前后端交互时,会有数据往来,我把数据的“往”称作:向后端传参。我觉得一次 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,其用法就不言而喻了。
还不快抢沙发