分享

如何使用HttpWebRequest POST数据时设置Connection=“Keep

 estorm2008 2015-03-13

以下信息,来自:http://hi.baidu.com/iceser/blog/item/3006a559e02d3e272834f058.html

在使用HttpWebRequest来POST数据的时候,发现我们提交的请求里,并没有出现

Connection = "Keep-Alive" 的头

而由于某些原因,我们希望我们使用HttpWebRequest来提交的请求跟浏览器提交的请求一模一样。

于是,我们发现 HttpWebReqeust 有一个 Connection 属性,然后我们写了如下类似的代码:

string url = "http://www.";

HttpWebRequest req = HttpWebRequest.Create(url) as HttpWebRequest;
req.Connection = "Keep-Alive";

我们满以为问题从此解决了,好吧,我们来测试一下效果 ---

...编译....运行....

一个意料之外的情况发生了……

居然....出现了一个异常?

说什么 “Keep-Alive 和 Close 不能使用此属性设置。” ,晕死,这是什么鬼东西,你又不让我设置,你公开这个属性来干什么?

那我们想设置 Connection = "Keep-Alive" 怎么办……

不要紧,我们还年轻么,有的是时间和精力来折腾,于是我们开始测试

req.Headers[HttpRequestHeader.Connection] = "Keep-Alive";

再测试。。。。什么? 又是一个 “此标头必须使用适当的属性进行修改。”的异常!

到底想怎样样啊? 这不是踢足球么……

好吧,有事没事找百度,还是没发现什么有效的解决方案,有位仁兄建议,实在不行,咱就从TCP做起,自己解析HTTP协议,好吧,,,我也有点动心了,反正 HTTP 协议还是很熟悉的,记得很久很久以前,自己实现了一个SMTP。。。貌似里面大多数协议是相同的……

跑题了,不好意思,我又回来了~~

但,好像自己实现又没什么必要,虽然不复杂,但是还是有点麻烦,时间就是生命,时间就是金钱,我们要节约时间……

好吧,再想想有没有其它的办法吧~~

这时候我就想, HttpWebRequest.Headers 到底是一个什么东西,为什么要加限制呢?

那....如果我绕过这个限制,是不是可以设置Connection呢?

好吧,看看 Headers 到底是什么....哦,,原来是 WebHeaderCollection。

这个时候,一个很激动人心的词语,在脑海中闪现。。。。。

反射 !!!!

对。。。。就是反射。。。

好吧,在磁盘里翻箱倒柜的找啊找,终于把那个多年不用的 Reflector 给找出来的,加载System.dll...

展开 System.Net .... 靠,这个命名空间里的对象也太多了吧,看得我眼那个花啊!不好意思~发表感慨时一激动,说话就开始不文明起来了……

...鼠标滚轮....滚啊滚...滚啊滚....N个周圈之后。。终于看见   WebHeaderCollection 了,皇天不负有心人啊,可算给我找到了...

好吧,我们来看看定的定义

public class WebHeaderCollection : NameValueCollection, ISerializable

哦,原来是从 NameValueCollection 继承来的
,继续找我们感兴趣的东西……

我们去找它的索引属性...

public string this[HttpRequestHeader header]
{
    get
    {
        if (!this.AllowHttpRequestHeader)
        {
            throw new InvalidOperationException(SR.GetString("net_headers_req"));
        }
        return base[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int) header)];
    }
    set
    {
        if (!this.AllowHttpRequestHeader)
        {
            throw new InvalidOperationException(SR.GetString("net_headers_req"));
        }
        base[UnsafeNclNativeMethods.HttpApi.HTTP_REQUEST_HEADER_ID.ToString((int) header)] = value;
    }
}
OK,就是它了....

这里只是调用基类(NameValueCollection)的索引属性啊,没看到在哪里抛出的异常……

革命尚未成功,同志仍需努力~~

我们接着看 WebHeaderCollection 的其它成员...

看是不是重写了 NameValueCollection 的 Add 函数之类的

于是,,,我们发现了这个

public override void Add(string name, string value)
{
    name = CheckBadChars(name, false);
    this.ThrowOnRestrictedHeader(name);
    value = CheckBadChars(value, true);
    if (((this.m_Type == WebHeaderCollectionType.HttpListenerResponse) && (value != null)) && (value.Length > 0xffff))
    {
        throw new ArgumentOutOfRangeException(SR.GetString("net_headers_toolong", new object[] { (ushort) 0xffff }));
    }
    this.NormalizeCommonHeaders();
    base.InvalidateCachedArrays();
    this.InnerCollection.Add(name, value);
}

看到 this.ThrowOnRestrictedHeader(name); 这句了吧,直觉告诉我,异常可能就是它干的……

继续看 ThrowOnRestrictedHeader 函数..

internal void ThrowOnRestrictedHeader(string headerName)
{
    if (this.m_Type == WebHeaderCollectionType.HttpWebRequest)
    {
        if (HInfo[headerName].IsRequestRestricted)
        {
            throw new ArgumentException(!object.Equals(headerName, "Host") ? SR.GetString("net_headerrestrict") : SR.GetString("net_headerrestrict_resp", new object[] { "Host" }), "name");
        }
    }
    else if ((this.m_Type == WebHeaderCollectionType.HttpListenerResponse) && HInfo[headerName].IsResponseRestricted)
    {
        throw new ArgumentException(SR.GetString("net_headerrestrict_resp", new object[] { headerName }), "name");
    }
}

可不就是它吗!!!看来直觉还是可以相信的...

看来 HInfo 这个成员,保存了HTTP头名称的一个集合,而刚好

Connection 这个的 IsRequestRestricted 肯定是为 true .... 于是,这里就过不去了..

虽然问题已经找到了。。。但我还是想看清楚一些

于是找到了 HInfo 成员的初始化

private static readonly HeaderInfoTable HInfo = new HeaderInfoTable();

没有参数的构造函数?那只能去看看 HeaderInfoTable 对象的定义了~~

于是,找到这个下面这个函数

    static HeaderInfoTable()
    {
        HeaderInfo[] infoArray = new HeaderInfo[] {
            new HeaderInfo("Age", false, false, false, SingleParser), new HeaderInfo("Allow", false, false, true, MultiParser), new HeaderInfo("Accept", true, false, true, MultiParser), new HeaderInfo("Authorization", false, false, true, MultiParser), new HeaderInfo("Accept-Ranges", false, false, true, MultiParser), new HeaderInfo("Accept-Charset", false, false, true, MultiParser), new HeaderInfo("Accept-Encoding", false, false, true, MultiParser), new HeaderInfo("Accept-Language", false, false, true, MultiParser), new HeaderInfo("Cookie", false, false, true, MultiParser), new HeaderInfo("Connection", true, false, true, MultiParser), new HeaderInfo("Content-MD5", false, false, false, SingleParser), new HeaderInfo("Content-Type", true, false, false, SingleParser), new HeaderInfo("Cache-Control", false, false, true, MultiParser), new HeaderInfo("Content-Range", false, false, false, SingleParser), new HeaderInfo("Content-Length", true, true, false, SingleParser), new HeaderInfo("Content-Encoding", false, false, true, MultiParser),
            new HeaderInfo("Content-Language", false, false, true, MultiParser), new HeaderInfo("Content-Location", false, false, false, SingleParser), new HeaderInfo("Date", true, false, false, SingleParser), new HeaderInfo("ETag", false, false, false, SingleParser), new HeaderInfo("Expect", true, false, true, MultiParser), new HeaderInfo("Expires", false, false, false, SingleParser), new HeaderInfo("From", false, false, false, SingleParser), new HeaderInfo("Host", true, false, false, SingleParser), new HeaderInfo("If-Match", false, false, true, MultiParser), new HeaderInfo("If-Range", false, false, false, SingleParser), new HeaderInfo("If-None-Match", false, false, true, MultiParser), new HeaderInfo("If-Modified-Since", true, false, false, SingleParser), new HeaderInfo("If-Unmodified-Since", false, false, false, SingleParser), new HeaderInfo("Keep-Alive", false, true, false, SingleParser), new HeaderInfo("Location", false, false, false, SingleParser), new HeaderInfo("Last-Modified", false, false, false, SingleParser),
            new HeaderInfo("Max-Forwards", false, false, false, SingleParser), new HeaderInfo("Pragma", false, false, true, MultiParser), new HeaderInfo("Proxy-Authenticate", false, false, true, MultiParser), new HeaderInfo("Proxy-Authorization", false, false, true, MultiParser), new HeaderInfo("Proxy-Connection", true, false, true, MultiParser), new HeaderInfo("Range", true, false, true, MultiParser), new HeaderInfo("Referer", true, false, false, SingleParser), new HeaderInfo("Retry-After", false, false, false, SingleParser), new HeaderInfo("Server", false, false, false, SingleParser), new HeaderInfo("Set-Cookie", false, false, true, MultiParser), new HeaderInfo("Set-Cookie2", false, false, true, MultiParser), new HeaderInfo("TE", false, false, true, MultiParser), new HeaderInfo("Trailer", false, false, true, MultiParser), new HeaderInfo("Transfer-Encoding", true, true, true, MultiParser), new HeaderInfo("Upgrade", false, false, true, MultiParser), new HeaderInfo("User-Agent", true, false, false, SingleParser),
            new HeaderInfo("Via", false, false, true, MultiParser), new HeaderInfo("Vary", false, false, true, MultiParser), new HeaderInfo("Warning", false, false, true, MultiParser), new HeaderInfo("WWW-Authenticate", false, true, true, SingleParser)
         };
        HeaderHashTable = new Hashtable(infoArray.Length * 2, CaseInsensitiveAscii.StaticInstance);
        for (int i = 0; i < infoArray.Length; i++)
        {
            HeaderHashTable[infoArray[i].HeaderName] = infoArray[i];
        }
    }

函数有点长。。。。但还是把我们关心的东西找出来了

new HeaderInfo("Connection", true, false, true, MultiParser)

而 HeaderInfo 的构造函数,是如下定义

internal HeaderInfo(string name, bool requestRestricted, bool responseRestricted, bool multi, HeaderParser p)
{
    this.HeaderName = name;
    this.IsRequestRestricted = requestRestricted;
    this.IsResponseRestricted = responseRestricted;
    this.Parser = p;
    this.AllowMultiValues = multi;
}

看吧,果然是这样... 第2个参数传入了 true,,那就 IsRequestRestricted 为 true,,我们再回到先前判断的代码:

        if (HInfo[headerName].IsRequestRestricted)
        {
            throw new ArgumentException(!object.Equals(headerName, "Host") ? SR.GetString("net_headerrestrict") : SR.GetString("net_headerrestrict_resp", new object[] { "Host" }), "name");
        }

果然没错吧!

OK,这下根源算是彻底给搞清楚了。。

我们继续看 Add 函数...

public override void Add(string name, string value)
{
    name = CheckBadChars(name, false);
    this.ThrowOnRestrictedHeader(name);
    value = CheckBadChars(value, true);
    if (((this.m_Type == WebHeaderCollectionType.HttpListenerResponse) && (value != null)) && (value.Length > 0xffff))
    {
        throw new ArgumentOutOfRangeException(SR.GetString("net_headers_toolong", new object[] { (ushort) 0xffff }));
    }
    this.NormalizeCommonHeaders();
    base.InvalidateCachedArrays();
    this.InnerCollection.Add(name, value);
}

其实,大家应该早就看见了。。

就是最后一行……

this.InnerCollection.Add(name, value);

原来,最终是调用了这个...

于是我们看 InnerCollection 到底是什么

private NameValueCollection InnerCollection
{
    get
    {
        if (this.m_InnerCollection == null)
        {
            this.m_InnerCollection = new NameValueCollection(0x10, CaseInsensitiveAscii.StaticInstance);
        }
        return this.m_InnerCollection;
    }
}

哦....原来就是一个普通的NameValueCollection,没什么特别的。

到现在为止,我想大家应该跟我一样,有解决办法了吧?

那就是。。。。。反射…………

示例代码如下:

        public static void SetHeaderValue(WebHeaderCollection header, string name, string value)
        {
            var property = typeof(WebHeaderCollection).GetProperty("InnerCollection",
                System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            if (property != null)
            {
                var collection = property.GetValue(header, null) as NameValueCollection;
                collection[name] = value;
            }
        }

然后我们再试试

HttpWebRequest req = HttpWebRequest.Create(url) as HttpWebRequest;

SetHeaderValue(req.Headers, "Connection", "Keep-Alive");

真的激动啊~~~会不会是我们预期的效果呢?

就是不告诉你!~~~~想知道结果。。。自己去试吧~~

PS:利用这个方法,我又发现了一个很神奇的应用。。。

就是,可以利用这个方法,来让我们模拟提交的ttp头的出现顺序,也跟浏览器的http头的出现顺序一模一样!

比如:我的代码是这么写的

            HttpWebRequest req = HttpWebRequest.Create(url) as HttpWebRequest;
            req.Accept = "*/*";
            req.Headers[HttpRequestHeader.AcceptLanguage] = "zh-CN";
            if (!string.IsNullOrEmpty(referer))
            {
                req.Referer = referer;
            }
            req.Headers["x-flash-version"] = "10,1,53,64";
            req.ContentType = "application/x-amf";
            SetHeaderValue(req.Headers, "Content-Length", "0");
            req.Headers[HttpRequestHeader.AcceptEncoding] = "gzip, deflate";
            req.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; EmbeddedWB 14.52 from: http://www./ EmbeddedWB 14.52; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0; .NET4.0C; .NET4.0E; 360SE)";
            Uri uri = new Uri(url);
            SetHeaderValue(req.Headers, "Host", uri.Host);
            SetHeaderValue(req.Headers, "Connection", "Keep-Alive");
            req.Headers[HttpRequestHeader.CacheControl] = "no-cache";

看到

SetHeaderValue(req.Headers, "Content-Length", "0");

SetHeaderValue(req.Headers, "Host", uri.Host);

了吗?

其实这两个,在 HttpWebRequest 提交请求时,都会自动设置的,但因为它们被设置的时间稍微晚了一些,所以,会出现在Http头的后面的位置。

我上面的代码,只是它给他们占了个位置而已~~~

当HttpWebRequest设置他们的值的时候,因为之前已经Add过了,所以,只是修改它的值~~这样,就起到占位置的作用了~~~

现在想起来,还真有点变态 ~~

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多