上传单个文件

前面介绍了基本的发送数据,其中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,一次使BindJSONBindWith方法。

☁  ~  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,
        })

    })

results matching ""

    No results matching ""