cURL和multipart/form-data

cURL的基本用法

通过cURL可以实现很多的网络请求,包括POST发送文件。输入curl -X POST来开始curl请求,表单的参数可以通过-F参数来添加,如:

curl -X POST -F 'username=foo' -F 'password=bar' http://somesite/login
1

如果服务端用的是PHP开发,可以通过print_r打印查看$_POST全局变量来确认服务端接收到的是不是期望的数据:

Array(
    'username' => 'foo',
    'password' => 'bar'
)
1
2
3
4

很多网站会通过header或者body中的数据来验证用户身份,cURL也可以添加特定的数据类型或者请求头:

# -d 发送原始数据
curl -X POST -H 'Content-Type: application/json' -d '{"username":"foo","password":"bar"}' http://somesite/login
1
2

multipart/form-data

什么是multipart/form-data呢?又如何用cURL发送multipart/form-data请求呢?

先说说表单的编码类型。表单的类型是通过属性enctype来决定的,它有三种取值:

  • application/x-www-form-urlencoded 表示URL编码了的表单,是enctype的默认值,enctype取值无效时也取该值。
  • multipart/form-data 表示的是Multipart表单,当用户想上传文件时需要使用。
  • text/plain 是HTML5中新出现的一个表单类型,只是简单的发送表单数据,而不经过任何的编码。

URL编码表单

我们来写一个表单来测试看。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>URL Encode</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <form action="/urlencoded?firstname=sid sloth&lastname=sloth" method="POST" enctype="application/x-www-form-urlencoded">
        <input type="text" name="username" value="sidthe sloth" />
        <input type="text" name="password" value="sloth secret" />
        <input type="submit" value="Submit" />
    </form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面的例子是一个发送POST请求表单,通过Chrome的开发这工具我们可以看到请求的是如何提交的。

url encode request

URL编码其实就是一个由namevalue构成的字符串,每个namevalue之间通过=连接,namevalue对和对之间通过& 连接,如:

key1=value1&key2=value2
1

我们还可以发现请求URL中的参数和请求体中的数据是相似的。不同的是sid sloth中的%20+

Multipart Forms

Multipart Forms通常在我们上传文件时用到,要想了解它是如何工作的,我们只需要把enctype的值修改为multipart/form-data

<form action="/multipart?firstname=sid slayer&lastname=sloth" method="POST" enctype="multipart/form-data">
    <input type="text" name="username" value="sid the sloth"/>
    <input type="text" name="password" value="slothsecret"/>
    <input type="submit" value="Submit" />
</form>
1
2
3
4
5

点击提交后,我们再看看浏览器为我们做了什么。

multipart form-data request

下面我们分别来看看 Content-Type头和请求体。

Content-Type头

Content-Type请求头的值显然是multipart/form-data,但同时我们可以看到其后方还有一个值boundary。这个boundary是浏览器生成的,用户也可以自动,稍后我们再研究它的用途。

请求体

请求体包含了表单的数据,每个namevalue对都转换成 MIME 格式:

--<<boundary_value>>
Content-Disposition: form-data; name="<<field_name>>"

<<field_value>>
1
2
3
4

<<boundary_value>><<field_name>><<field_value>>分别表示boundary值,namevalue的值。boundary_value 加上前缀和后缀--作为整个请求体的结束标志。所以整个请求体的结构如下:

--<<boundary_value>>
Content-Disposition: form-data; name="<<field_name>>"

<<field_value>>
--<<boundary_value>>
Content-Disposition: form-data; name="<<field_name>>"

<<field_value>>
--<<boundary_value>>--
1
2
3
4
5
6
7
8
9

由上所知,Content-Type请求头中的boundary值为了帮助浏览器和服务端判断请求体中的fields的起始位置。

Text/plain表单

该表单很少使用,是HTML5中新引出的。

cURL和multipart/form-data

了解了multipart/form-data表单的请求体和头的结构,我们就可以通过cURL来代替浏览器发送此类请求了。

curl 'http://somesite/upload' \
-H 'Content-Length: 1137' \
-H 'Content-Type: multipart/form-data; boundary=87ab6f4407aed433b18f4e2a70f8d486c59bb168' \
-X POST \
-d '--87ab6f4407aed433b18f4e2a70f8d486c59bb168\r\nContent-Disposition: form-data; name="filename"; filename="56953ad695e1062e8a51e50532f72586.png"\r\nContent-Length: 100\r\n\r\ndata:image/png;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBMRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAA\r\n--87ab6f4407aed433b18f4e2a70f8d486c59bb168\r\nContent-Disposition: form-data; name="filename"\r\nContent-Length: 96\r\n\r\nwx9074de28009e1111.o6zAJs2uLi0dmtrlVnoocbPWgXgQ.aZsFpiT61UlZ85a7de8f3d6fac589449e088e4beaf0d.png\r\n--87ab6f4407aed433b18f4e2a70f8d486c59bb168\r\nContent-Disposition: form-data; name="param1"\r\nContent-Length: 1\r\n\r\n1\r\n--87ab6f4407aed433b18f4e2a70f8d486c59bb168\r\nContent-Disposition: form-data; name="param2"\r\nContent-Length: 10\r\n\r\n172.17.0.1\r\n--87ab6f4407aed433b18f4e2a70f8d486c59bb168\r\nContent-Disposition: form-data; name="param3"\r\nContent-Length: 41\r\n\r\nfrom:1885396040;wm:90163_90001;lang:zh_CN\r\n--87ab6f4407aed433b18f4e2a70f8d486c59bb168\r\nContent-Disposition: form-data; name="param4"\r\nContent-Length: 1\r\n\r\n6\r\n--87ab6f4407aed433b18f4e2a70f8d486c59bb168\r\nContent-Disposition: form-data; name="skip_check"\r\nContent-Length: 1\r\n\r\n0\r\n--87ab6f4407aed433b18f4e2a70f8d486c59bb168--\r\n'

1
2
3
4
5
6

-d参数后,我们用请求头中的boundary为分隔,写好了请求体,通过这种方式上传文件。

要想了解cURL和服务器之间都发生了什么,我们可以加上 --trace-ascii dump.log 来获取完整的日志。

用cURL上传文件的简单方法

其实用cURL上传文件很简单,只需要在文件路径的前面添加一个@

curl -X POST -F 'image=@/path/to/pictures/picture.jpg' http://somesite/upload
1

本文参考

最近更新: 5/24/2019, 8:55:41 AM