6.9.2 使用抓包工具分析文件上传请求
在编写调用上传多媒体文件接口的程序之前,需要清楚POST是什么格式的数据。在6.9.1节中提到过,我们要通过FORM表单的方式上传多媒体文件。在网页中通过表单上传文件时,需要将<form>标签的enctype属性设置为multipart/form-data,示例代码如下。
- <form method="POST" enctype="multipart/form-data" action="./upload.action">
- <input type="file" name="media" />
- <input type="submit" value="上传" />
- </form>
enctype属性用于指定将表单数据发送到服务器之前的编码方式,enctye属性的取值有3种,如表6-14所示。
表6-14 表单属性enctype的3种取值说明
enctype的前两种取值application/x-www-form-urlencoded和text/plain只能用于上传文本数据,如果要上传文件,必须将enctype设置为multipart/form-data,也就是需要将表单数据编码成一条消息再发送到服务器。
HTTP抓包工具能够帮助我们分析HTTP请求的详细信息,包括请求头、Cookie、请求体等。因此,借助于抓包工具就能够分析出上传文件时发送到服务器的数据格式。常用的HTTP抓包工具有Wireshark、HttpWatch、HTTP Analyzer、WSockExpert、Fiddler等,它们的功能大同小异,随便选择一款都能够满足我们的要求。
笔者使用的是Chrome浏览器(版本为30.0.1599.101)自带的开发者工具,它也能够抓取HTTP数据包。打开Chrome浏览器,依次点击菜单按钮→“工具”→“开发者工具”,就会在Chrome浏览器底部显示开发者工具,点击开发者工具顶部的"Network"选项,就能够进入到HTTP请求分析界面,如图6-11所示。此时,在地址栏输入网址,就能够在"Network"界面看到所有捕获到的HTTP请求信息,包括请求的资源名称(Name)、请求方法(Method)、资源类型(Type)等。
图6-11 Chrome浏览器的抓包工具
接下来,我们就用Chrome浏览器的抓包工具分析微信服务器的多媒体文件上传请求。
2013年10月29日,公众平台推出了一款网页版的接口调试工具,该工具能够帮助开发者检测调用公众平台接口时发送的请求参数是否正确,提交请求后可获得微信服务器的验证结果。接口调试工具的网页地址为http://mp.weixin.qq.com/debug/,我们用Chrome浏览器访问它,进入页面后能够看到一个接口调试表单,在这里选择“基础支持”下的“多媒体文件上传接口/media/upload”,并填写参数access_token、type和media,如图6-12所示。
图6-12 多媒体文件上传接口的调试表单填写
参数access_token可以随便填写一个,是不是真实的接口访问凭证都没关系,只要保证非空就行;参数type也可以随便选择一个,笔者选择的是image;最后,笔者选择了图片liufeng.jpg。表单填写完成后,请确保Chrome浏览器的抓包工具是打开的,此时点击页面上的“检查问题”按钮,抓包工具就能捕获到多媒体文件上传请求,如图6-13所示。
从图6-13中可以看出,一共捕获到3条HTTP请求记录,第一条就是文件上传请求,而另外两条是访问CSS和PNG资源时产生的。点击第一条记录,将会看到该请求的详细信息,包括请求地址(Request URL)、请求方法(Request Method)、请求状态(Status Code)、请求头(Request Headers)、查询参数(Query String Parameters)、请求体(Request Payload)和响应头(Response Headers),如图6-14所示。因为我们的目的是分析出上传文件时的请求数据格式,所以只需要关注请求头和请求体。
图6-13 使用抓包工具捕获多媒体文件上传请求
图6-14 使用抓包工具分析多媒体文件上传请求
请求头中包含了一系参数,如Content-Length、Content-Type、Cookie等,但大多数参数并不是必需的。对于文件上传请求,最重要的是Content-Type,它用于指定HTTP请求的内容类型。图6-14中Content-Type的值如下:
- Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryLhuhxmsef74yiAUO
对于文件上传请求,Content-Type的值必须是"multipart/form-data"。boundary是数据分隔符,它是由浏览器随机生成的,如果同时上传多个文件,在请求体中通过boundary分隔每个文件的内容。
通过抓包工具获取到的请求体如下:
- ------WebKitFormBoundaryLhuhxmsef74yiAUO
- Content-Disposition: form-data; name="media"; filename="liufeng.jpg"
- Content-Type: image/jpeg
- ------WebKitFormBoundaryLhuhxmsef74yiAUO--
可以看到,请求体中用到了在请求头中定义的boundary,如果将boundary的值替换为${boundary},则请求体可以写成下面这样:
- 1 --${boundary}
- 2 Content-Disposition: form-data; name="media"; filename="liufeng.jpg"
- 3 Content-Type: image/jpeg
- 4
- 5
- 6 --${boundary}--
请求体的第1行是“—${boundary}”。第2行是Content-Disposition,对应于表单中<input>标签的name属性,而filename是上传文件的名称。第3行是Content-Type,表示当前文件的MIME类型。接下来是连续两个空行,最后以“—${boundary}—”结束。读者可能比较疑惑:上传的文件"liufeng.jpg"的内容在请求体的什么位置?文件的内容在请求体中的第5行,因为Chrome自带的抓包工具不支持显示上传的文件内容,所以看不到。
在请求体中,换行符是用"\r\n"表示的。如果将文件内容用变量${content}表示,上面的请求体可以表示成这样:
- --${boundary}\r\n
- Content-Disposition: form-data; name="media"; filename="liufeng.jpg"\r\n
- Content-Type: image/jpeg\r\n\r\n
- ${content}\r\n
- --${boundary}--\r\n
通过代码上传文件和使用网页表单上传文件的原理一样,也需要构造相同格式的请求头和请求体,再发送到服务器。