背景

一月份做项目时 Android 客户端需要上传图片,小小的研究了下

如果要在客户端向服务器上传文件,我们就必须模拟一个 POST multipart/form-data 类型的请求,Content-Type 必须是 multipart/form-data

通过编写一个网页的文件上传程序,抓包看看协议规则

1
2
3
4
5
6
<form action="picture/upload.do" method="POST" enctype="multipart/form-data">
<input type="text" name="users_id" />
<input type="text" name="users_name" />
<input type="file" name="image_path" />
<input type="submit" />
</form>

使用上面这个表单,抓包的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
POST /picture/upload.do HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */*
Referer: http://192.168.1.98:8080/uploadpic.jsp
Accept-Language: zh-CN
User-Agent: Mozilla/4.0
Content-Type: multipart/form-data; boundary=---------------------------7dd36df20300
Accept-Encoding: gzip, deflate
Host: 192.168.1.98:8080
Content-Length: 529762
Connection: Keep-Alive
Cache-Control: no-cache

-----------------------------7dd36df20300
Content-Disposition: form-data; name="users_id"
<!--空行-->
1
-----------------------------7dd36df20300
Content-Disposition: form-data; name="users_name"
<!--空行-->
admin
-----------------------------7dd36df20300
Content-Disposition: form-data; name="image_path"; filename="S21223-170317.jpg"
Content-Type: image/pjpeg
<!--空行-->
<!--文件 byte 数据-->
-----------------------------7dd36df20300--
<!--空行-->

详细解析

  • Content-Type: multipart/form-data; boundary=—————————7dd36df20300
    • 根据 rfc1867, multipart/form-data 是必须的
    • 7dd36df20300 是分隔符,分隔多个文件、表单项
    • 7d 内核标志
    • d36df20300 是即时生成的一个字符,用以确保整个分隔符不会在文件或表单项的内容中出现
  • 从请求体里可以看出每一条数据后面都有换行 ,并且 Parametervalue 之间还有个空行

最后把格式提取

1
2
3
4
5
6
7
8
9
10
11
--分隔符 \r\n
Content-Disposition: form-data; name="[Parameter]" \r\n
\r\n
[value] \r\n
--分隔符 \r\n
Content-Disposition: form-data; name="[Parameter]"; filename="[]" \r\n
Content-Type: image/pjpeg \r\n
\r\n
<!--文件 byte 数据--> \r\n
--分隔符-- \r\n
\r\n

程序实现传输协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
String path = "http://192.168.1.102:8080/picture/upload.do";
// 换行符
String enter = "\r\n";
// 分割线
String boundary = "---------------------------7dd36df20300";
StringBuffer sb = new StringBuffer();
// 获取文件流
File file = new File("D:\\123.jpg");
FileInputStream is = new FileInputStream(file);
// 参数信息
String[] keys = { "users_id", "users_name" };
String[] values = { "1", "admin" };
// 拼凑参数
for (int i = 0; i < keys.length; i++) {
sb.append("--" + boundary + enter);
sb.append("Content-Disposition: form-data; name=\"" + keys[i] + "\"");
sb.append(enter + enter);
sb.append(values[i]);
sb.append(enter);
}
// 拼凑文件头部信息
sb.append("--" + boundary);
sb.append(enter);
sb.append("Content-Disposition: form-data; name=\"image_path\"; filename=\"123.jpg\"");
sb.append(enter);
sb.append("Content-Type: image/*");
sb.append(enter + enter);
byte[] data = sb.toString().getBytes();
byte[] end_data = (enter + "--" + boundary + "--" + enter + enter).getBytes();
// 初始化 HttpURLConnection
URL url = new URL(path);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setConnectTimeout(3000);
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
con.setRequestMethod("POST");
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Charset", "UTF-8");
con.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
con.setRequestProperty("Content-Length", file.length() + data.length + end_data.length + "");
// 开始上传
OutputStream outputStream = con.getOutputStream();
outputStream.write(data);
byte[] fileBuffer = new byte[1024];
int length = -1;
while ((length = is.read(fileBuffer)) != -1) {
outputStream.write(fileBuffer, 0, length);
}
is.close();
outputStream.write(end_data);
outputStream.flush();
outputStream.close();
con.getResponseCode();