很久很久以前,在哪个FAT32格式还流行的年代,文件大小普遍还没超过4G的年代,.Net已经出来了。
而那时候.Net实现的HTTP断点续传协议,还没预料到如此普及(我猜的)。那时候的HttpWebRequest.AddRange 还仅限于 int(Int32)类型。。。
虽然在.Net 4.0 以后,HttpWebRequest的 AddRange 方法已经默认支持 long类型(Int64)。但是相对于比较旧的版本上,应该如何支持呢?特别是Unity3D 这些N久还在用Mono 2.6(相当于 .Net 3.5)的时候。
当然网上也有相应的解决方案:
1 MethodInfo method = typeof(WebHeaderCollection).GetMethod("AddWithoutValidate", BindingFlags.Instance | BindingFlags.NonPublic); 2 3 HttpWebRequest request = (HttpWebRequest)WebRequest.Create ("http://www.example.com/file.exe"); 4 5 long start = int32.MaxValue; 6 long end = int32.MaxValue + 100000; 7 8 string key = "Range"; 9 string val = string.Format ("bytes={0}-{1}", start, end); 10 11 method.Invoke (request.Headers, new object[] { key, val });
也有一个不需要反射(Reflection)的继承版本,当然这个做法不可行,因为HttpWebRequest.set Headers时候内部是重新构造了WebHeaderCollection 对象,而且并没有将 "Range" 头信息添加进去
1 //错误版本 2 public class InheritWebHeaders : WebHeaderCollection 3 { 4 /* 5 define your properties & methods here. -- by Mitchell Chu 6 */ 7 public void AddHeaderWithoutValidate( 8 string name 9 , string value) 10 { 11 base.AddWithoutValidate(name, value); 12 } 13 } 14 15 // usage: 16 HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://blog.useasp.net/"); 17 InheritWebHeaders headers = new InheritWebHeaders(); 18 headers.AddHeaderWithoutValidate("Referrer", "http://blog.useasp.net/tag/.net"); 19 // other HTTP Headers 20 request.Headers = headers; 21 /// do more here...
所以 还是只能反射来支持long类型了,使用ILSpy 查看一下 System.dll(2.0.0.0) 中HttpWebRequest.AddRange的实现:
1 public void AddRange(int from, int to) 2 { 3 this.AddRange("bytes", from, to); 4 } 5 public void AddRange(int range) 6 { 7 this.AddRange("bytes", range); 8 } 9 10 public void AddRange(string rangeSpecifier, int from, int to) 11 { 12 if (rangeSpecifier == null) 13 { 14 throw new ArgumentNullException("rangeSpecifier"); 15 } 16 if (from < 0 || to < 0) 17 { 18 throw new ArgumentOutOfRangeException(SR.GetString("net_rangetoosmall")); 19 } 20 if (from > to) 21 { 22 throw new ArgumentOutOfRangeException(SR.GetString("net_fromto")); 23 } 24 if (!WebHeaderCollection.IsValidToken(rangeSpecifier)) 25 { 26 throw new ArgumentException(SR.GetString("net_nottoken"), "rangeSpecifier"); 27 } 28 if (!this.AddRange(rangeSpecifier, from.ToString(NumberFormatInfo.InvariantInfo), to.ToString(NumberFormatInfo.InvariantInfo))) 29 { 30 throw new InvalidOperationException(SR.GetString("net_rangetype")); 31 } 32 } 33 34 public void AddRange(string rangeSpecifier, int range) 35 { 36 if (rangeSpecifier == null) 37 { 38 throw new ArgumentNullException("rangeSpecifier"); 39 } 40 if (!WebHeaderCollection.IsValidToken(rangeSpecifier)) 41 { 42 throw new ArgumentException(SR.GetString("net_nottoken"), "rangeSpecifier"); 43 } 44 if (!this.AddRange(rangeSpecifier, range.ToString(NumberFormatInfo.InvariantInfo), (range >= 0) ? "" : null)) 45 { 46 throw new InvalidOperationException(SR.GetString("net_rangetype")); 47 } 48 } 49 50 private bool AddRange(string rangeSpecifier, string from, string to) 51 { 52 string text = this._HttpRequestHeaders["Range"]; 53 if (text == null || text.Length == 0) 54 { 55 text = rangeSpecifier + "="; 56 } 57 else 58 { 59 if (string.Compare(text.Substring(0, text.IndexOf('=')), rangeSpecifier, StringComparison.OrdinalIgnoreCase) != 0) 60 { 61 return false; 62 } 63 text = string.Empty; 64 } 65 text += from.ToString(); 66 if (to != null) 67 { 68 text = text + "-" + to; 69 } 70 this._HttpRequestHeaders.SetAddVerified("Range", text); 71 return true; 72 }
由此看出 AddRange 方法最终实现是 AddRange(string rangeSpecifier, string from, string to)
这样做的好处是有扩展性,哪怕以后文件大到Int128,Int256 也是可以支持的。但是至于为何不公开,应该是为了防止某些程序员乱写FromTo的参数吧,万一写成了非整型类型呢~
所以,相应地我们可以用反射写一个工具类(网上找的):HttpWebRequest.AddRange support for long values
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Net; 6 using System.Reflection; 7 8 namespace HttpHelper 9 { 10 public static class HttpHelper 11 { 12 private static MethodInfo _addRangeMethodInfo = null; 13 private static void GetMethodInfo() 14 { 15 if (_addRangeMethodInfo == null) 16 { 17 Type t = typeof(HttpWebRequest); 18 MethodInfo[] methodInfoArray = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic); 19 foreach (MethodInfo mi in methodInfoArray) 20 if (mi.Name == "AddRange") 21 { 22 _addRangeMethodInfo = mi; 23 return; 24 } 25 throw new Exception("HttpWebRequest type is missing the private AddRange method"); 26 } 27 } 28 29 public static void AddRange(this HttpWebRequest req, long from, long to) 30 { 31 GetMethodInfo(); 32 33 string rangeSpecifier = "bytes"; 34 string fromString = from.ToString(); 35 string toString = to.ToString(); 36 37 object[] args = new object[3]; 38 39 args[0] = rangeSpecifier; 40 args[1] = fromString; 41 args[2] = toString; 42 43 _addRangeMethodInfo.Invoke(req, args); 44 } 45 46 public static void AddRange(this HttpWebRequest req, long from) 47 { 48 GetMethodInfo(); 49 50 string rangeSpecifier = "bytes"; 51 string fromString = from.ToString(); 52 string toString = ""; 53 54 object[] args = new object[3]; 55 56 args[0] = rangeSpecifier; 57 args[1] = fromString; 58 args[2] = toString; 59 60 _addRangeMethodInfo.Invoke(req, args); 61 } 62 } 63 }
当然事情还没结束,因为我们要支持的是还在使用老旧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 == 0) { 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 < 0 || to < 0 || from > to) { throw new ArgumentOutOfRangeException(); } string text = this.webHeaders["Range"]; if (text == null || text.Length == 0) { 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 = 0; i < count; i++) { webHeaderCollection.Add(value.GetKey(i), value.Get(i)); } this.webHeaders = webHeaderCollection; } }
我们可以针对U3D也做一个版本,但是这样反而觉得比较麻烦。 所以可以先给Helper添加默认的AddRange方法,然后通过外部可以覆盖原来的AddRange方法。
以下是代码:
1 using System; 2 using System.Net; 3 using System.Reflection; 4 5 namespace MyDownloader.Extension.Common 6 { 7 public static class HttpWebRequestHelper 8 { 9 10 public delegate void AddRangeFromToHandler(HttpWebRequest request, long from, long to); 11 public delegate void AddRangeFromHandler(HttpWebRequest request, long from); 12 13 static MethodInfo _addRangeMethodInfo = null; 14 static AddRangeFromHandler addRangeFrom = null; 15 static AddRangeFromToHandler addRangeFromTo = null; 16 17 static HttpWebRequestHelper() 18 { 19 SetAddRange(AddRangeInternal, AddRangeInternal); 20 } 21 22 static void GetMethodInfo() 23 { 24 if (_addRangeMethodInfo != null) 25 { 26 return; 27 } 28 Type t = typeof(HttpWebRequest); 29 MethodInfo[] methodInfoArray = t.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic); 30 foreach (MethodInfo mi in methodInfoArray) 31 { 32 if (mi.Name == "AddRange") 33 { 34 _addRangeMethodInfo = mi; 35 return; 36 } 37 } 38 throw new NotSupportedException("HttpWebRequest type is missing the private AddRange method"); 39 } 40 41 42 //默认调用 43 internal static void AddRangeInternal(HttpWebRequest req, long from, long to) 44 { 45 GetMethodInfo(); 46 47 string rangeSpecifier = "bytes"; 48 string fromString = from.ToString(); 49 string toString = to.ToString(); 50 51 object[] args = new object[3]; 52 53 args[0] = rangeSpecifier; 54 args[1] = fromString; 55 args[2] = toString; 56 57 _addRangeMethodInfo.Invoke(req, args); 58 } 59 60 internal static void AddRangeInternal(HttpWebRequest req, long from) 61 { 62 GetMethodInfo(); 63 64 string rangeSpecifier = "bytes"; 65 string fromString = from.ToString(); 66 string toString = ""; 67 68 object[] args = new object[3]; 69 70 args[0] = rangeSpecifier; 71 args[1] = fromString; 72 args[2] = toString; 73 74 _addRangeMethodInfo.Invoke(req, args); 75 } 76 77 public static void SetAddRange(AddRangeFromHandler fromOnly, AddRangeFromToHandler fromTo) 78 { 79 if (fromOnly == null || fromTo == null) 80 throw new ArgumentNullException(); 81 addRangeFrom = fromOnly; 82 addRangeFromTo = fromTo; 83 } 84 85 public static void AddRange(HttpWebRequest request, long from) 86 { 87 addRangeFrom(request, from); 88 } 89 90 public static void AddRange(HttpWebRequest request, long from, long to) 91 { 92 addRangeFromTo(request, from, to); 93 } 94 } 95 }
在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 == 0) { 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 < 0 || to < 0 || from > to) { throw new ArgumentOutOfRangeException(); } string text = req.Headers["Range"]; if (text == null || text.Length == 0) { 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"); } }