C# - 在 HttpWebRequest 中未发送 Connection: keep-alive 标头

32

我正在尝试使用HttpWebRequest发送以下头信息:

Connection: keep-alive

然而,该头信息从未被发送。Fiddler2 显示每当我在 Google Chrome 中请求页面时,都会发送该头信息。但是,我的应用程序出于某种原因拒绝发送此头信息。

我已将 KeepAlive 属性设置为 true(默认情况下就是 true),但头信息仍未被发送。

我正在尝试使用多个 HttpWebRequests 发送此头信息,但它们都基本上是这样的:

HttpWebRequest logIn6 = (HttpWebRequest)WebRequest.Create(new Uri(responseFromLogIn5));
logIn6.CookieContainer = cookies;
logIn6.KeepAlive = true;
logIn6.Referer = "https://login.yahoo.com/config/login?.src=spt&.intl=us&.lang=en-US&.done=http://football.fantasysports.yahoo.com/";
logIn6.UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1";
logIn6.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
logIn6.Headers.Add("Accept-Encoding:gzip,deflate,sdch");
logIn6.Headers.Add("Accept-Language:en-US,en;q=0.8");
logIn6.Headers.Add("Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3");
logIn6.AllowAutoRedirect = false;

HttpWebResponse logIn6Response = (HttpWebResponse)logIn6.GetResponse();
string responseFromLogIn6 = logIn6Response.GetResponseHeader("Location");

cookies.Add(logIn6Response.Cookies);

logIn6Response.Close();

有人知道我需要做什么才能确保发送这个头部信息吗?

Fiddler2从Chrome捕获的原始数据:

GET xxx HTTP/1.1
Host: accounts.google.com
Connection: keep-alive
Referer: https://login.yahoo.com/config/login?.src=spt&.intl=us&.lang=en-US&.done=http://football.fantasysports.yahoo.com/
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: xxx

HTTP/1.1 302 Moved Temporarily
Set-Cookie: xxx
Set-Cookie: xxx
Location: xxx
Content-Type: text/html; charset=UTF-8
P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."
Date: Sat, 17 Sep 2011 22:27:09 GMT
Expires: Sat, 17 Sep 2011 22:27:09 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Length: 2176
Server: GSE

我的应用程序中的Fiddler2原始数据:

GET xxx HTTP/1.1
Referer: https://login.yahoo.com/config/login?.src=spt&.intl=us&.lang=en-US&.done=http://football.fantasysports.yahoo.com/
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Host: accounts.google.com

HTTP/1.1 302 Moved Temporarily
Location: xxx
Content-Type: text/html; charset=UTF-8
Date: Sun, 18 Sep 2011 00:05:40 GMT
Expires: Sun, 18 Sep 2011 00:05:40 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Length: 573
Server: GSE

我试图让第二个Fiddler2的原始信息看起来像第一个Fiddler2的原始信息。


Http 1.1自动使用Keep-alive,因此我认为保持连接头是多余的。但是,HttpWebRequest确实发送Connection: close命令以显式关闭连接。 - Rick Strahl
Keep-Alive 头部不是多余的,因为它控制持久连接上的超时和请求参数的数量。如果设置了 Keep-Alive,则 Connection 也应设置为 Keep-Alive,否则这些参数将被忽略。显然 HttpWebRequest 没有直接设置这些头部到正确值的方法 - 它们将 KeepAlive 设为布尔值(这不应该是这样的),而设置 Connection 只会抛出异常。微软在这个问题上搞砸了。 - Suncat2000
5个回答

40
我曾遇到同样的问题:除了第一个请求之外,不会发送“连接:保持活动”头,并且如果缺少该头,我访问的服务器将无法提供正确响应。因此,以下是我的解决方法:
首先是将HttpWebRequest实例的ProtocolVersion属性设置为HttpVersion.Version10。除了http命令变成GET xxx HTTP/1.0,它可以正常工作并且只使用公共API。
第二种方法使用反射修改HttpWebRequest实例的内部属性ServicePoint.HttpBehaviour,像这样:
var req = (HttpWebRequest)WebRequest.Create(someUrl);

var sp = req.ServicePoint;
var prop = sp.GetType().GetProperty("HttpBehaviour", 
                        BindingFlags.Instance | BindingFlags.NonPublic);
prop.SetValue(sp, (byte)0, null);

req.GetResponse().Close();

希望这可以帮到你。


9
好的,这个方法可以解决问题,尽管我仍然觉得这只是为了应对某些愚蠢的 .net bug。非常感谢!我浪费了一整天来解决 HttpWebRequest 的问题! - Arsen Zahray

8
我为这个问题苦苦挣扎了半天!而我亲爱的老Fiddler(我的守护天使)无意中成为了问题的一部分:
每当我开启Fiddler监控来测试我的HTTP POST请求时,问题就不会出现。每当我关闭Fiddler监控时,问题就会出现。
我的POSTS是使用1.1协议发送的,并且在初始连接后忽略/冗余/为什么保持活动状态。也就是说,我可以在第一个POST的头中看到它(通过Fiddler!),但是尽管使用相同的代码,在后续的POST中看不到它。嘿呵...
但是,只有当Keep-Alive被发送时,远程服务器才会响应。现在我无法证明这一点,但我怀疑Fiddler监控连接导致远程服务器认为或者相信连接仍处于活动状态(尽管在我的第一个POST之后没有发送Keep-Alives),并做出了正确的响应。正如我所说,一旦我关闭了Fiddler,缺少Keep-Alives就会导致远程服务器超时。
我实施了上面描述的1.0解决方案,现在我的POSTS正常工作,无论Fiddler开启还是关闭。希望这可以帮助其他卡在某个地方的人...

这篇博客文章解释了Fiddler如何修复行为:https://www.telerik.com/blogs/help!-running-fiddler-fixes-my-app- - Bobson

7
你做得很对。代码应该会产生以下标题添加:

Connection: Keep-Alive

如果您没有看到这个头部信息,请提供您发送请求的代码和Fiddler的原始输出。如果您不想处理这个问题,也可以忽略它,因为HTTP 1.1连接默认情况下是保持活动状态

更新:看起来.NET只会明确地为第一个请求设置Keep-Alive。对于相同的主机/URL的后续请求将不会有此标头,可能是因为底层TCP连接已经被重用。


查看最后更新。检查第一个请求是否具有 Keep-Alive,而随后的请求则没有。 - Dmitry
啊,这很有道理。我的第一个连接确实发送了头部信息。对不起,我应该提到这一点。然而,我没有向同一主机/URL 发送进一步的请求。此外,在第一个“Keep-Alive”头部信息被发送后,我收到了这个响应头部信息:“Connection: close”。这难道不意味着 TCP 连接被关闭了吗? - Krzysztof Czelusniak

3

下载了 HttpWebRequest 的源代码后,发现每个属性都检查一些已知的头信息来获取 HeaderCollection。为了摆脱这种情况,需要在该集合上进行一些反射操作以使其正常工作。

var webRequest = (HttpWebRequest) WebRequest.Create(url);
webRequest.Headers.GetType().InvokeMember("ChangeInternal",
    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
    Type.DefaultBinder, webRequest.Headers, new object[] {name, value}
);

3

我知道这个问题的答案,因为我曾经遇到过同样的问题,并通过继承WebClient并重写其GetWebRequest方法来解决它。

请看下面的代码:

    public class CookieAwareWebClient : WebClient
{
    public CookieContainer CookieContainer { get; set; }

    public CookieAwareWebClient()
        : this(new CookieContainer())
    { }

    public CookieAwareWebClient(CookieContainer c)
    {
        this.CookieContainer = c;
    }

    protected override WebRequest GetWebRequest(Uri address)
    {
        WebRequest request = base.GetWebRequest(address);
        var castRequest = request as HttpWebRequest;

        if (castRequest != null)
        {
            castRequest.KeepAlive = true; //<-- this what you want! The rest you don't need. 
            castRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
            castRequest.UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.104 Safari/537.36";
            castRequest.Referer = "https://www.jobserve.com/gb/en/Candidate/Login.aspx?url=48BB4C724EA6A1F2CADF4243A0D73C13225717A29AE8DAD6913D";
            castRequest.Headers.Add("Accept-Encoding", "gzip,deflate,sdch");
            castRequest.Headers.Add("Accept-Language", "en-GB,en-US;q=0.8,en;q=0.6");
            castRequest.CookieContainer = this.CookieContainer;
        }

        return request;
    }
}

如您所见,我不仅启用了保持连接,还使用了Cookie和其他标头!

希望这可以帮到您!

Kiran


这个不起作用。在代码中设置一个断点,你会发现“.KeepAlive”属性已经为true,但“Connection: Keep-Alive”并没有随请求一起发送。 - Tom Regan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接