GodZza

导航

HttpWebRequest.AddRange 支持long类型

 

很久很久以前,在哪个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");
    }
}

 

posted on 2017-05-16 12:07  GodZza  阅读(615)  评论(0编辑  收藏  举报