很久很久以前,在哪个FAT32格式还流行的年代,文件大小普遍还没超过4G的年代,.Net已经出来了。
而那时候.Net实现的HTTP断点续传协议,还没预料到如此普及(我猜的)。那时候的HttpWebRequest.AddRange 还仅限于 int(Int32)类型。。。
虽然在.Net 4.0 以后,HttpWebRequest的 AddRange 方法已经默认支持 long类型(Int64)。但是相对于比较旧的版本上,应该如何支持呢?特别是Unity3D 这些N久还在用Mono 2.6(相当于 .Net 3.5)的时候。
当然网上也有相应的解决方案:
MethodInfo method = typeof(WebHeaderCollection).GetMethod("AddWithoutValidate", BindingFlags.Instance | BindingFlags.NonPublic); HttpWebRequest request = (HttpWebRequest)WebRequest.Create ("http://www.example.com/file.exe"); long start = int32.MaxValue;
long end = int32.MaxValue + ; string key = "Range";
string val = string.Format ("bytes={0}-{1}", start, end); method.Invoke (request.Headers, new object[] { key, val });
也有一个不需要反射(Reflection)的继承版本,当然这个做法不可行,因为HttpWebRequest.set Headers时候内部是重新构造了WebHeaderCollection 对象,而且并没有将 "Range" 头信息添加进去
//错误版本
public class InheritWebHeaders : WebHeaderCollection
{
/*
define your properties & methods here. -- by Mitchell Chu
*/
public void AddHeaderWithoutValidate(
string name
, string value)
{
base.AddWithoutValidate(name, value);
}
} // usage:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://blog.useasp.net/");
InheritWebHeaders headers = new InheritWebHeaders();
headers.AddHeaderWithoutValidate("Referrer", "http://blog.useasp.net/tag/.net");
// other HTTP Headers
request.Headers = headers;
/// do more here...
所以 还是只能反射来支持long类型了,使用ILSpy 查看一下 System.dll(2.0.0.0) 中HttpWebRequest.AddRange的实现:
public void AddRange(int from, int to)
{
this.AddRange("bytes", from, to);
}
public void AddRange(int range)
{
this.AddRange("bytes", range);
} public void AddRange(string rangeSpecifier, int from, int to)
{
if (rangeSpecifier == null)
{
throw new ArgumentNullException("rangeSpecifier");
}
if (from < || to < )
{
throw new ArgumentOutOfRangeException(SR.GetString("net_rangetoosmall"));
}
if (from > to)
{
throw new ArgumentOutOfRangeException(SR.GetString("net_fromto"));
}
if (!WebHeaderCollection.IsValidToken(rangeSpecifier))
{
throw new ArgumentException(SR.GetString("net_nottoken"), "rangeSpecifier");
}
if (!this.AddRange(rangeSpecifier, from.ToString(NumberFormatInfo.InvariantInfo), to.ToString(NumberFormatInfo.InvariantInfo)))
{
throw new InvalidOperationException(SR.GetString("net_rangetype"));
}
} public void AddRange(string rangeSpecifier, int range)
{
if (rangeSpecifier == null)
{
throw new ArgumentNullException("rangeSpecifier");
}
if (!WebHeaderCollection.IsValidToken(rangeSpecifier))
{
throw new ArgumentException(SR.GetString("net_nottoken"), "rangeSpecifier");
}
if (!this.AddRange(rangeSpecifier, range.ToString(NumberFormatInfo.InvariantInfo), (range >= ) ? "" : null))
{
throw new InvalidOperationException(SR.GetString("net_rangetype"));
}
} private bool AddRange(string rangeSpecifier, string from, string to)
{
string text = this._HttpRequestHeaders["Range"];
if (text == null || text.Length == )
{
text = rangeSpecifier + "=";
}
else
{
if (string.Compare(text.Substring(, text.IndexOf('=')), rangeSpecifier, StringComparison.OrdinalIgnoreCase) != )
{
return false;
}
text = string.Empty;
}
text += from.ToString();
if (to != null)
{
text = text + "-" + to;
}
this._HttpRequestHeaders.SetAddVerified("Range", text);
return true;
}
由此看出 AddRange 方法最终实现是 AddRange(string rangeSpecifier, string from, string to)
这样做的好处是有扩展性,哪怕以后文件大到Int128,Int256 也是可以支持的。但是至于为何不公开,应该是为了防止某些程序员乱写FromTo的参数吧,万一写成了非整型类型呢~
所以,相应地我们可以用反射写一个工具类(网上找的):HttpWebRequest.AddRange support for long values
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Reflection; namespace HttpHelper
{
public static class HttpHelper
{
private static MethodInfo _addRangeMethodInfo = null;
private static void GetMethodInfo()
{
if (_addRangeMethodInfo == null)
{
Type t = typeof(HttpWebRequest);
MethodInfo[] methodInfoArray = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
foreach (MethodInfo mi in methodInfoArray)
if (mi.Name == "AddRange")
{
_addRangeMethodInfo = mi;
return;
}
throw new Exception("HttpWebRequest type is missing the private AddRange method");
}
} public static void AddRange(this HttpWebRequest req, long from, long to)
{
GetMethodInfo(); string rangeSpecifier = "bytes";
string fromString = from.ToString();
string toString = to.ToString(); object[] args = new object[]; args[] = rangeSpecifier;
args[] = fromString;
args[] = toString; _addRangeMethodInfo.Invoke(req, args);
} public static void AddRange(this HttpWebRequest req, long from)
{
GetMethodInfo(); string rangeSpecifier = "bytes";
string fromString = from.ToString();
string toString = ""; object[] args = new object[]; args[] = rangeSpecifier;
args[] = fromString;
args[] = toString; _addRangeMethodInfo.Invoke(req, args);
}
}
}
当然事情还没结束,因为我们要支持的是还在使用老旧Mono版本的Unity3D,而Mono的HttpWebRequest内部实现 又和.Net的实现不同,所以上述的方法在U3D上根本不Work。
照版煮碗,我们先看一下U3D种 System.dll 的实现(如果找不到System.dll,可以先将工程Build一个出来,然后在工程文件内部(Xxx_Data/Managed/System.dll)即可找到。
我目前使用的是Unity4.6.4(项目需要,目前一直在使用此版本), 找到的System.dll版本为 2.0.5.0
public void AddRange(int range)
{
this.AddRange("bytes", range);
} public void AddRange(int from, int to)
{
this.AddRange("bytes", from, to);
} public void AddRange(string rangeSpecifier, int range)
{
if (rangeSpecifier == null)
{
throw new ArgumentNullException("rangeSpecifier");
}
string text = this.webHeaders["Range"];
if (text == null || text.Length == )
{
text = rangeSpecifier + "=";
}
else
{
if (!text.ToLower().StartsWith(rangeSpecifier.ToLower() + "="))
{
throw new InvalidOperationException("rangeSpecifier");
}
text += ",";
}
this.webHeaders.RemoveAndAdd("Range", text + range + "-");
} public void AddRange(string rangeSpecifier, int from, int to)
{
if (rangeSpecifier == null)
{
throw new ArgumentNullException("rangeSpecifier");
}
if (from < || to < || from > to)
{
throw new ArgumentOutOfRangeException();
}
string text = this.webHeaders["Range"];
if (text == null || text.Length == )
{
text = rangeSpecifier + "=";
}
else
{
if (!text.ToLower().StartsWith(rangeSpecifier.ToLower() + "="))
{
throw new InvalidOperationException("rangeSpecifier");
}
text += ",";
}
this.webHeaders.RemoveAndAdd("Range", string.Concat(new object[]
{
text,
from,
"-",
to
}));
} //顺道看一下 Headers的实现 public override WebHeaderCollection Headers
{
get
{
return this.webHeaders;
}
set
{
this.CheckRequestStarted();
WebHeaderCollection webHeaderCollection = new WebHeaderCollection(true);
int count = value.Count;
for (int i = ; i < count; i++)
{
webHeaderCollection.Add(value.GetKey(i), value.Get(i));
}
this.webHeaders = webHeaderCollection;
}
}
我们可以针对U3D也做一个版本,但是这样反而觉得比较麻烦。 所以可以先给Helper添加默认的AddRange方法,然后通过外部可以覆盖原来的AddRange方法。
以下是代码:
using System;
using System.Net;
using System.Reflection; namespace MyDownloader.Extension.Common
{
public static class HttpWebRequestHelper
{ public delegate void AddRangeFromToHandler(HttpWebRequest request, long from, long to);
public delegate void AddRangeFromHandler(HttpWebRequest request, long from); static MethodInfo _addRangeMethodInfo = null;
static AddRangeFromHandler addRangeFrom = null;
static AddRangeFromToHandler addRangeFromTo = null; static HttpWebRequestHelper()
{
SetAddRange(AddRangeInternal, AddRangeInternal);
} static void GetMethodInfo()
{
if (_addRangeMethodInfo != null)
{
return;
}
Type t = typeof(HttpWebRequest);
MethodInfo[] methodInfoArray = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
foreach (MethodInfo mi in methodInfoArray)
{
if (mi.Name == "AddRange")
{
_addRangeMethodInfo = mi;
return;
}
}
throw new NotSupportedException("HttpWebRequest type is missing the private AddRange method");
} //默认调用
internal static void AddRangeInternal(HttpWebRequest req, long from, long to)
{
GetMethodInfo(); string rangeSpecifier = "bytes";
string fromString = from.ToString();
string toString = to.ToString(); object[] args = new object[]; args[] = rangeSpecifier;
args[] = fromString;
args[] = toString; _addRangeMethodInfo.Invoke(req, args);
} internal static void AddRangeInternal(HttpWebRequest req, long from)
{
GetMethodInfo(); string rangeSpecifier = "bytes";
string fromString = from.ToString();
string toString = ""; object[] args = new object[]; args[] = rangeSpecifier;
args[] = fromString;
args[] = toString; _addRangeMethodInfo.Invoke(req, args);
} public static void SetAddRange(AddRangeFromHandler fromOnly, AddRangeFromToHandler fromTo)
{
if (fromOnly == null || fromTo == null)
throw new ArgumentNullException();
addRangeFrom = fromOnly;
addRangeFromTo = fromTo;
} public static void AddRange(HttpWebRequest request, long from)
{
addRangeFrom(request, from);
} public static void AddRange(HttpWebRequest request, long from, long to)
{
addRangeFromTo(request, from, to);
}
}
}
在U3D中使用(TEST VERSION):
public class HttpWebRequestHelperUnityTest{ public void TEST()
{
var http = new MyDownloader.Extension.Protocols.HttpFtpProtocolExtension(); HttpWebRequestHelper.SetAddRange(AddRange, AddRange);
DownloadManager.Instance.DownloadEnded += (s, e) => { print(e.Downloader.LocalFile); };
} public void AddRange(HttpWebRequest req, long range)
{
AddRange(req, "bytes", range);
} public void AddRange(HttpWebRequest req, long from, long to)
{
AddRange(req, "bytes", from, to);
} public void AddRange(HttpWebRequest req, string rangeSpecifier, long range)
{
if (rangeSpecifier == null)
{
throw new ArgumentNullException("rangeSpecifier");
}
string text = req.Headers["Range"];
if (text == null || text.Length == )
{
text = rangeSpecifier + "=";
}
else
{
if (!text.ToLower().StartsWith(rangeSpecifier.ToLower() + "="))
{
throw new InvalidOperationException("rangeSpecifier");
}
text += ",";
}
HeadersRemoveAndAdd(req.Headers, "Range", text + range + "-");
} public void AddRange(HttpWebRequest req, string rangeSpecifier, long from, long to)
{
if (rangeSpecifier == null)
{
throw new ArgumentNullException("rangeSpecifier");
}
if (from < || to < || from > to)
{
throw new ArgumentOutOfRangeException();
}
string text = req.Headers["Range"];
if (text == null || text.Length == )
{
text = rangeSpecifier + "=";
}
else
{
if (!text.ToLower().StartsWith(rangeSpecifier.ToLower() + "="))
{
throw new InvalidOperationException("rangeSpecifier");
}
text += ",";
}
HeadersRemoveAndAdd(req.Headers, "Range", string.Concat(new object[]
{
text,
from,
"-",
to
}));
} static void HeadersRemoveAndAdd(WebHeaderCollection headers, string name, string value)
{
GetMethodInfo();
removeAndAddRangeMethodInfo.Invoke(headers, new object[] { name, value });
} static MethodInfo removeAndAddRangeMethodInfo = null;
static void GetMethodInfo()
{
if (removeAndAddRangeMethodInfo != null)
return; Type t = typeof(WebHeaderCollection);
MethodInfo[] methodInfoArray = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic);
foreach (MethodInfo mi in methodInfoArray)
{
if (mi.Name == "RemoveAndAdd")
{
removeAndAddRangeMethodInfo = mi;
return;
}
}
throw new NotSupportedException("WebHeaderCollection type is missing the private RemoveAndAdd method");
}
}