有一点经验的AS开发者都知道,要发送数据用URLRequest,要POST数据用URLVarialbes。一切都非常好,直到碰到了发送图片+数据的时候···
因为图片BitmapData一般都Encode成了ByteArray,以流的形式传至后台,而变量是以值对的形式POST过去,这就造成了后台无法区分二进制和值对。
以前的做法是先把图片单独发过去,然后等服务器返回URL,再将URL与其他数据以值对的形式发送至后台。(比较Stupid,但是Work!!)
其实抓包分析之后,发现Flash其实也是以标准的HTTP协议发送数据的,我们完全可以构造URLRequest的内容来构造一个标准的HTTP协议。
首先分析一个简单的HTTP协议例子(截取自FileReference的例子):
POST /handler.cfm HTTP/1.1
Accept: text/*
Content-Type: multipart/form-data;
boundary=----------Ij5ae0ae0KM7GI3KM7
User-Agent: Shockwave Flash
Host: www.example.com
Content-Length: 421
Connection: Keep-Alive
Cache-Control: no-cache
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filename"
MyFile.jpg
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filedata"; filename="MyFile.jpg"
Content-Type: application/octet-stream
FileDataHere
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Upload"
Submit Query
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--
其中:
POST /handler.cfm HTTP/1.1
Accept: text/*
Content-Type: multipart/form-data;
boundary=----------Ij5ae0ae0KM7GI3KM7
User-Agent: Shockwave Flash
Host: www.example.com
Content-Length: 421
Connection: Keep-Alive
Cache-Control: no-cache
这一串是HTTP头,一般我们不用管太多,URLRequest会自动构造,但是有几个参数要留意一下:
Content-Type: multipart/form-data;
boundary=----------Ij5ae0ae0KM7GI3KM7
第一个是类似页面表单的数据类型。而boundary则定义了分界符的字符(这个可以自己定义)。
所以我们在AS3代码中应该写如下语句:
this.request.contentType = "multipart/form-data; boundary=---------------------------Ij5ae0ae0KM7GI3KM7" ;
就是要构造在HTTP协议中添加了这两段内容。
接下来是内容部分:
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filename"
MyFile.jpg
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
上下两段是分割符(细心的同学可能已经发现其实就是我们刚刚定义的boundary的值)。一般都是这样括住我们需要的数据(结尾除外)
Content-Disposition: form-data;
说明的是数据的类型,这个可以上网去查看,一般比较常用的有例如image/jpeg,image/x-png,当然还有非常多的其他值。
name="Filename"
这个就是POST的变量的变量名。
MyFile.jpg
这个就是变量的值。
所以上面的全句的意思可以理解为Filename = MyFile.jpg;
在AS3里我们可以这样写:
_byteArray.writeUTFBytes("\r\n-----------------------------" + separator + "\r\n");
_byteArray.writeUTFBytes("Content-Disposition: form-data; name=\"" + $name +"\"\r\n\r\n" + $value);
_byteArray.writeUTFBytes("\r\n-----------------------------" + separator + "\r\n");
(这里为了方便看写了三句,其实可以一句写完)
同理,对于图片数据,HTTP协议有一些不同:
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
Content-Disposition: form-data; name="Filedata"; filename="MyFile.jpg"
Content-Type: application/octet-stream
FileDataHere
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
(实践证明其实不用这么写,只要按我下面的写法就OK了,但还是把比较标准的格式给大家看看)
_byteArray.writeUTFBytes("\r\n-----------------------------" + separator + "\r\n");
_byteArray.writeUTFBytes("Content-Disposition: form-data; name=\"" + $name + "\"; filename=\"" + $filename + "\"\r\nContent-Type: " + $type + "\r\n\r\n");
_byteArray.writeBytes($content);
_byteArray.writeUTFBytes("\r\n-----------------------------" + separator + "\r\n");
最后一个变量的结尾记得换成结尾的分割符(跟我们一直写的分割符有那么一点点不同)
------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--
即:
_byteArray.writeUTFBytes("\r\n-----------------------------" + separator + "--");
然后把我们一直在编辑的赋值给URLRequest,即:
_request.data = _byteArray;
我们就大功告成了,同时后台同学也可以很轻松的按标准的格式去获取我们发送过来的数据。
下面附上我自己对应整合的一个小类,没测试过,只是做个范例吧 :)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package com.dannycheung
{
import flash.net.URLRequest;
import flash.net.URLRequestMethod;
import flash.net.URLVariables;
import flash.utils.ByteArray;
/**
* @author DannyCheung
*/
public class HttpRequestBuilder
{
//构造的URLRequest
private var _request:URLRequest;
//构造的二进制ByteArray
private var _byteArray:ByteArray;
//Http协议分割符
private var _separator:String;
//标志位
private var PROTOCAL_END_SET:Boolean = false; //是否填写HTTP文件尾
public function HttpRequestBuilder ($url:String, $separator:String = "7d86d710144a") {
//初始化
this.separator = $separator;
this.request = new URLRequest($url);
this.request.method = URLRequestMethod.POST;
this.request.contentType = "multipart/form-data; boundary=---------------------------" + separator;
this._byteArray = new ByteArray();
}
/*
* 写入变量
* @param $name 写入的变量名
* @param $value 写入的变量值
*/
public function writeVariable($name:String, $value:String):void {
writeSeparator();
_byteArray.writeUTFBytes("Content-Disposition: form-data; name=\"" + $name +"\"\r\n\r\n" + $value);
}
/*
* 写入图片
* @param $name 变量名
* @param $filename 图片文件名
* @param $type 图片类型,在HttpRequestBuilderConsts下定义
* @param $content 图片二进制数据
*/
public function writeImage($name:String, $filename:String, $type:String, $content:ByteArray):void {
writeSeparator();
_byteArray.writeUTFBytes("Content-Disposition: form-data; name=\"" + $name + "\"; filename=\"" + $filename + "\"\r\nContent-Type: " + $type + "\r\n\r\n");
_byteArray.writeBytes($content);
}
/*
* 构造HTTP分割线
*/
public function writeSeparator():void {
_byteArray.writeUTFBytes("\r\n-----------------------------" + separator + "\r\n");
}
/*
* 构造HTTP结尾,只能调用一次,二次调用会抛出错误
*/
public function writeHttpEnd():void {
if (!PROTOCAL_END_SET) {
_byteArray.writeUTFBytes("\r\n-----------------------------" + separator + "--");
} else {
throw(new Error("Write the Http End More Than Once"));
}
}
/*
* 获取构造好的URLRequest
*/
public function getURLrequest():URLRequest {
return this.request;
}
//get set
public function get separator():String { return _separator; }
/*public function set separator(value:String):void
{
//TODO 替换之前写入的内容
_separator = value;
}*/
/*
* 获取前会检查是否写入HTTP结尾,未调用的话会报错
*/
public function get request():URLRequest {
if (!PROTOCAL_END_SET) throw(new Error("Havn't Write the Http End Yet")); //??还是应该自动构造
_request.data = _byteArray;
return _request;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package com.dannycheung
{
/**
* ...
* @author DannyCheung
*/
public class HttpRequestBuilderConsts
{
public static const JPG:String = "image/jpeg";
public static const PNG:String = "image/x-png";
public function HttpRequestBuilderConsts() {
}
}
}