上传单个文件
前面介绍了基本的发送数据,其中multipart/form-data转用于文件上传。gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。
func main(){
router := gin.Default()
router.POST(
"/upload"
, func(c *gin.Context) {
name := c.PostForm(
"name"
)
fmt.Println(name)
file, header, err := c.Request.FormFile(
"upload"
)
if
err !=
nil
{
c.String(http.StatusBadRequest,
"Bad request"
)
return
}
filename := header.Filename
fmt.Println(file, err, filename)
out
, err := os.Create(filename)
if
err !=
nil
{
log.Fatal(err)
}
defer
out
.Close()
_, err = io.Copy(
out
, file)
if
err !=
nil
{
log.Fatal(err)
}
c.String(http.StatusCreated,
"upload successful"
)
})
router.Run(
":8000"
)
}
使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。然后使用os的操作,把文件数据复制到硬盘上。
使用下面的命令可以测试上传,注意upload为c.Request.FormFile指定的参数,其值必须要是绝对路径:
curl -X POST
http:
/
/127.0.0.1:8000/upload
-F
"upload=@/Users/ghost/Desktop/pic.jpg"
-H
"Content-Type: multipart/form-data"
上传多个文件
单个文件上传很简单,别以为多个文件就会很麻烦。依葫芦画瓢,所谓多个文件,无非就是多一次遍历文件,然后一次copy数据存储即可。下面只写handler,省略main函数的初始化路由和开启服务器监听了:
router.POST(
"/multi/upload"
, func(c *gin.Context) {
err := c.Request.ParseMultipartForm(
200000
)
if
err !=
nil
{
log.Fatal(err)
}
formdata := c.Request.MultipartForm
files := formdata.File[
"upload"
]
for
i, _ := range files { /
file, err := files[i].Open()
defer file.Close()
if
err !=
nil
{
log.Fatal(err)
}
out
, err := os.Create(files[i].Filename)
defer
out
.Close()
if
err !=
nil
{
log.Fatal(err)
}
_, err = io.Copy(
out
, file)
if
err !=
nil
{
log.Fatal(err)
}
c.String(http.StatusCreated,
"upload successful"
)
}
})
与单个文件上传类似,只不过使用了c.Request.MultipartForm得到文件句柄,再获取文件数据,然后遍历读写。
使用curl上传
curl -X POST
http:
/
/127.0.0.1:8000/multi
/upload -F "upload=@/
Users/ghost/Desktop/pic.jpg
" -F "
upload=@/Users/ghost/Desktop/journey.png
" -H "
Content-
Type:
multipart/form-data
"
表单上传
上面我们使用的都是curl上传,实际上,用户上传图片更多是通过表单,或者ajax和一些requests的请求完成。下面展示一下web的form表单如何上传。
我们先要写一个表单页面,因此需要引入gin如何render模板。前面我们见识了c.String和c.JSON。下面就来看看c.HTML方法。
首先需要定义一个模板的文件夹。然后调用c.HTML渲染模板,可以通过gin.H给模板传值。至此,无论是String,JSON还是HTML,以及后面的XML和YAML,都可以看到Gin封装的接口简明易用。
创建一个文件夹templates,然后再里面创建html文件upload.html:
<
!DOCTYPE html
>
<
html
lang
=
"en"
>
<
head
>
<
meta
charset
=
"UTF-8"
>
<
title
>
upload
<
/
title
>
<
/
head
>
<
body
>
<
h3
>
Single Upload
<
/
h3
>
<
form
action
=
"/upload"
,
method
=
"post"
enctype
=
"multipart/form-data"
>
<
input
type
=
"text"
value
=
"hello gin"
/
>
<
input
type
=
"file"
name
=
"upload"
/
>
<
input
type
=
"submit"
value
=
"upload"
/
>
<
/
form
>
<
h3
>
Multi Upload
<
/
h3
>
<
form
action
=
"/multi/upload"
,
method
=
"post"
enctype
=
"multipart/form-data"
>
<
input
type
=
"text"
value
=
"hello gin"
/
>
<
input
type
=
"file"
name
=
"upload"
/
>
<
input
type
=
"file"
name
=
"upload"
/
>
<
input
type
=
"submit"
value
=
"upload"
/
>
<
/
form
>
<
/
body
>
<
/
html
>
upload 很简单,没有参数。一个用于单个文件上传,一个用于多个文件上传。
router.LoadHTMLGlob(
"templates/*"
)
router.GET(
"/upload"
, func(c *gin.Context) {
c.HTML(http.StatusOK,
"upload.html"
, gin.H{})
})
使用LoadHTMLGlob定义模板文件路径。
参数绑定
我们已经见识了x-www-form-urlencoded类型的参数处理,现在越来越多的应用习惯使用JSON来通信,也就是无论返回的response还是提交的request,其content-type类型都是application/json的格式。而对于一些旧的web表单页还是x-www-form-urlencoded的形式,这就需要我们的服务器能改hold住这多种content-type的参数了。
Python的世界里很好解决,毕竟动态语言不需要实现定义数据模型。因此可以写一个装饰器将两个格式的数据封装成一个数据模型。golang中要处理并非易事,好在有gin,他们的model bind功能非常强大。
type User
struct
{
Username
string
`form:
"username"
json:
"username"
binding:
"required"
`
Passwd
string
`form:
"passwd"
json:
"passwd"
bdinding:
"required"
`
Age
int
`form:
"age"
json:
"age"
`
}
func
main
()
{
router := gin.Default()
router.POST(
"/login"
, func(c *gin.Context) {
var user User
var err error
contentType := c.Request.Header.Get(
"Content-Type"
)
switch
contentType {
case
"application/json"
:
err = c.BindJSON(
&
user)
case
"application/x-www-form-urlencoded"
:
err = c.BindWith(
&
user, binding.Form)
}
if
err != nil {
fmt.Println(err)
log
.Fatal(err)
}
c.JSON(http.StatusOK, gin.H{
"user"
: user.Username,
"passwd"
: user.Passwd,
"age"
: user.Age,
})
})
}
先定义一个User模型结构体,然后针对客户端的content-type,一次使BindJSON和BindWith方法。
☁ ~ curl -X POST
http:
/
/127.0.0.1:8000/login
-H
"Content-Type:application/x-www-form-urlencoded"
-d
"username=rsj217
&
passwd=123
&
age=21"
| python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 79 100 46 100 33 41181 29543 --:--:-- --:--:-- --:--:-- 46000
{
"age": 21,
"passwd": "123",
"username": "rsj217"
}
☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217
&
passwd=123
&
new=21" |
python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100
78
100
45
100
33
37751
27684
--
:--
:--
--
:--
:--
--
:--
:--
45000
{
"age"
:
0
,
"passwd"
:
"123"
,
"username"
:
"rsj217"
}
☁ ~ curl -X POST
http:
/
/127.0.0.1:8000/login
-H
"Content-Type:application/x-www-form-urlencoded"
-d
"username=rsj217
&
new=21"
| python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server
No JSON object could be decoded
可以看到,结构体中,设置了binding标签的字段(username和passwd),如果没传会抛错误。非banding的字段(age),对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。
改成json的效果类似:
☁ ~ curl -X POST
http:
/
/127.0.0.1:8000/login
-H
"Content-Type:application/json"
-d
'{"username": "rsj217", "passwd": "123", "age": 21}'
| python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 96 100 46 100 50 32670 35511 --:--:-- --:--:-- --:--:-- 50000
{
"age": 21,
"passwd": "123",
"username": "rsj217"
}
☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "new": 21}' |
python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100
95
100
45
100
50
49559
55066
--
:--
:--
--
:--
:--
--
:--
:--
50000
{
"age"
:
0
,
"passwd"
:
"123"
,
"username"
:
"rsj217"
}
☁ ~ curl -X POST
http:
/
/127.0.0.1:8000/login
-H
"Content-Type:application/json"
-d
'{"username": "rsj217", "new": 21}'
| python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (52) Empty reply from server
No JSON object could be decoded
☁ ~ curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": 123, "new": 21}' |
python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0
0
0
0
0
0
0
0
--
:--
:--
--
:--
:--
--
:--
:--
0
curl:
(
52
) Empty reply from server
No JSON object could be decoded
使用json还需要注意一点,json是有数据类型的,因此对于
{"passwd": "123"}和{"passwd": 123}是不同的数据类型,解析需要符合对应的数据类型,否则会出错。
当然,gin还提供了更加高级方法,c.Bind,它会更加content-type自动推断是bind表单还是json的参数。
router.POST(
"/login"
, func(c *gin.Context) {
var user User
err := c.Bind(
&
user)
if
err != nil {
fmt.Println(err)
log
.Fatal(err)
}
c.JSON(http.StatusOK, gin.H{
"username"
: user.Username,
"passwd"
: user.Passwd,
"age"
: user.Age,
})
})