最近一个winform项目想直接用http协议与java端进行双向通讯,发现HttpListener可以实现http通讯,如果提交form的enctype=application/x-www-form-urlencoded则可以通过HttpUtility.ParseQueryString来解析HttpListenerRequest.InputStream中的内容,但如果是multipart/form-data,没有找到可以直接解析的类。
在网上找了半天,发现了http://www.xuebuyuan.com/464241.html,可以实现解析上传文件,不过处理一个1M的上传表单,竟然要8秒,性能实在无法接受(后来发现瓶颈主要在流读取和数组读取的速度差异)。
想着dotnet内部肯定有实现类,在用reflection反复查找后,终于确定HttpMultipartContentTemplateParser是可以用来解析MFC1867协议的,但由于是内部类无法在外部实现,故将代码整理如下。
/*
RFC1867协议 举例
------WebKitFormBoundaryKcbJOyftlttL1JBB
Content-Disposition: form-data; name="name"
zq
------WebKitFormBoundaryKcbJOyftlttL1JBB
Content-Disposition: form-data; name="myfile"; filename="IMG_20130219_181308.jpg"
Content-Type: image/jpeg
[二进制数据]
------WebKitFormBoundaryKcbJOyftlttL1JBB--
*/
///
/// 解析RFC1867协议
///
public class HttpMultipartFormParser {
private string boundary;
private byte[] _boundary;
private byte[] _data;
private int _length;
private int _lineLength = -1;
private int _lineStart = -1;
private int _pos;
private bool _lastBoundaryFound;
private string _partContentType;
private int _partDataLength = -1;
private int _partDataStart = -1;
private string _partFilename;
private string _partName;
private Encoding _encoding;
public HttpMultipartFormParser(HttpListenerRequest request, Encoding encoding) {
this._encoding = encoding;
//Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Regex regex = new Regex("boundary=(.*)$");
Match match = regex.Match(request.ContentType);
if (match.Success) {
boundary = match.Groups[1].Value;
_boundary = _encoding.GetBytes("--" + boundary);
}
Stream input = request.InputStream;
//将上传文件保存到内存
BufferedStream br = new BufferedStream(input);
MemoryStream ms = new MemoryStream();
byte[] buffer = new byte[4096];
int len = 0;
while ((len = br.Read(buffer, 0, buffer.Length))>0) {
ms.Write(buffer,0,len);
}
_data = ms.ToArray();
_length = _data.Length;
ms.Close();
}
///
/// 获取每一行数据
///
///
private bool GetNextLine() {
int num = this._pos;
this._lineStart = -1;
while (num < this._length) {
if (this._data[num] == 10) { // '\n'
this._lineStart = this._pos;
this._lineLength = num - this._pos;
this._pos = num + 1;
// ignore \r
if ((this._lineLength > 0) && (this._data[num - 1] == 13)) {
this._lineLength--;
}
break;
}
if (++num == this._length) {
this._lineStart = this._pos;
this._lineLength = num - this._pos;
this._pos = this._length;
}
}
return (this._lineStart >= 0);
}
///
/// 当前行是否是分隔符行
///
///
private bool AtBoundaryLine() {
int length = this._boundary.Length;
if ((this._lineLength != length) && (this._lineLength != (length + 2))) {
return false;
}
for (int i = 0; i < length; i++) {
if (this._data[this._lineStart + i] != this._boundary[i]) {
return false;
}
}
if (this._lineLength != length) {
// last boundary line? (has to end with "--")
if ((this._data[this._lineStart + length] != 0x2d) || (this._data[(this._lineStart + length) + 1] != 0x2d)) {
return false;
}
this._lastBoundaryFound = true;
}
return true;
}
///
/// 是否解析完毕
///
///
private bool AtEndOfData() {
if (this._pos < this._length) {
return this._lastBoundaryFound;
}
return true;
}
///
/// 从Content-Disposition:行抽取""中的内容
///
/// 行内容
///
///
///
private string ExtractValueFromContentDispositionHeader(string l, int pos, string name) {
String pattern = name + "=\"";
//:所在行位置+1
int i1 = CultureInfo.InvariantCulture.CompareInfo.IndexOf(l, pattern, pos, CompareOptions.IgnoreCase);
if (i1 < 0)
return null;
i1 += pattern.Length;
int i2 = l.IndexOf('"', i1);
if (i2 < 0)
return null;
if (i2 == i1)
return String.Empty;
return l.Substring(i1, i2 - i1);
}
///
/// 读取头部信息
///
private void ParsePartHeaders() {
_partName = null;
_partFilename = null;
_partContentType = null;
while (GetNextLine()) {
if (_lineLength == 0)
break; // empty line signals end of headers ->\r\n
// get line as String
byte[] lineBytes = new byte[_lineLength];
Array.Copy(_data, _lineStart, lineBytes, 0, _lineLength);
String line = _encoding.GetString(lineBytes);
// parse into header and value
int ic = line.IndexOf(':');
if (ic < 0)
continue; // not a header
// remeber header
String header = line.Substring(0, ic);
if (header.Equals("Content-Disposition")) {
// parse name and filename
_partName = ExtractValueFromContentDispositionHeader(line, ic + 1, "name");
_partFilename = ExtractValueFromContentDispositionHeader(line, ic + 1, "filename");
}
else if (header.Equals("Content-Type")) {
_partContentType = line.Substring(ic + 1).Trim();
}
}
}
///
/// 处理数据部分
///
private void ParsePartData() {
_partDataStart = _pos;
_partDataLength = -1;
while (GetNextLine()) {
if (AtBoundaryLine()) {
// calc length: adjust to exclude [\r]\n before the separator
int iEnd = _lineStart - 1;
if (_data[iEnd] == 10) // \n
iEnd--;
if (_data[iEnd] == 13) // \r
iEnd--;
_partDataLength = iEnd - _partDataStart + 1;
break;
}
}
}
///
/// 解析数据为对象列表
///
///
public List ParseIntoElementList() {
List itemList = new List();
while (GetNextLine()) {
if (AtBoundaryLine())
break;
}
if (AtEndOfData())
return itemList;
do {
// Parse current part's headers
ParsePartHeaders();
if (AtEndOfData())
break; // cannot stop after headers
// Parse current part's data
ParsePartData();
if (_partDataLength == -1)
break; // ending boundary not found
// Remember the current part (if named)
if (_partName != null) {
MultipartFormItem item = new MultipartFormItem();
item.Name = _partName;
item.Data = new byte[_partDataLength];
Buffer.BlockCopy(_data, _partDataStart, item.Data, 0, _partDataLength);
item.ContentType = _partContentType;
if (item.ContentType != null) {
item.ItemType = FormItemType.File;
}
itemList.Add(item);
}
}
while (!AtEndOfData());
return itemList;
}
}