莫名其妙的 C# HttpWebRequest.GetResponse() 超时错误

作者:Liigo
日期:20170303
原创链接:http://blog.csdn.net/liigo/article/details/60144144
著作权归作者Liigo所有。商业转载请联系作者获得授权,非商业转载请注明出处。

现象

现象:GET请求 “特定URL” 时超时,无法获取Response响应。代码阻塞在HttpWebRequest.GetResponse()里面。

注意是在访问 “特定URL” 时才超时,访问其他URL是正常的。例如,访问 http://hello.com/?page=2(以下称为page2)超时,而访问 http://hello.com/?page=1 (以下称为page1)和 http://hello.com/?page=3 (以下称为page3)却是正常的。访问page1、page3每次都成功,访问page2每次都超时。

然而page2是可以被浏览器正常访问的;我用易语言两种方法GET请求page2都正常。排除了目标网站不工作或限制访问的情况。

分析

我的C#程序启动后第一次请求就是访问page2,排除了Connection、Request、Response等资源耗尽的情况。

网上有说.Net 3.5 / 4 默认使用代理,加上 HttpWebRequest.Proxy = null; 后也不行。

不是超时设定过短的问题,默认的100秒绝对不短。

我怀疑.Net库有BUG,把 .Net 2.0 换成了.Net 4.5.2、4.6.1,还是不行。

因超时而触发的异常对象只是说超时了,没有更多有价值的参考信息。

网络上找到的下面这篇经典文章,提供了不少解决GetResponse超时问题的办法,但不适用于我现在遇到的状况。

【已解决】HttpWebRequest的GetResponse或GetRequestStream偶尔超时 + 总结各种超时死掉的可能和相应的解决办法

一定是哪里出了问题!问题是出在哪里?

解决

问题解决了。

事实证明还是目标网站的page2有些特殊。经过Fidder观测,发现第一次访问page2时被302重定向到认证页面,进而又被302重定向到page2,我发现必须在访问认证页面时接收Cookie并将其传入到第二次访问page2的请求中。访问page1、page3等其他页面并不需要这一步骤。

上文提到的易语言的两个办法,“HTTP读文件()” 和 “网页_访问_对象()” 都自动处理了Cookie,不需要我关心,自己就工作的很好。可是C#代码的WEB请求并没有帮我接收和传递Cookie,导致最后302重定向很多遍之后以超时告终。

最后的解决办法是,我自行编码跟踪302重定向,接收Cookie并传递给下一个请求。以下是相关C#代码。

// 跟踪302临时跳转,接收Cookies并传递给下一次请求。
// by Liigo 20170303
public static string GetHtmlExX(string strUrl, string strReferer, string strCookies)
{
    try
    {
        HttpWebRequest req;
        HttpWebResponse res = GetHttpWebResponseNoRedirect(strUrl, strReferer, strCookies, out req);
        while (res.StatusCode == HttpStatusCode.Found)
        {
            strUrl = res.Headers[HttpResponseHeader.Location];
            strCookies = res.Headers[HttpResponseHeader.SetCookie];
            res.Close();
            req.Abort();
            res = GetHttpWebResponseNoRedirect(strUrl, strReferer, strCookies, out req);
        }
        Stream responseStream = res.GetResponseStream();
        StreamReader streamReader = new StreamReader(responseStream);
        string html = streamReader.ReadToEnd();
        streamReader.Close();
        responseStream.Close();
        res.Close();
        req.Abort();
        return html;
    } catch (Exception e)
    {
        string msg = e.Message;
        return "";
    }
}

// 执行HTTP GET请求,返回Response对象和Request对象。调用者负责关闭他们:Response.Close(),Request.Abort()。
public static HttpWebResponse GetHttpWebResponseNoRedirect(string strUrl, string strReferer, string strCookies, out HttpWebRequest request)
{
    HttpWebRequest req = null;
    try
    {
        req = HttpWebRequest.Create(strUrl) as HttpWebRequest;
        if (!string.IsNullOrEmpty(strCookies))
            req.Headers[HttpRequestHeader.Cookie] = strCookies;
        req.ContentType = contentType;
        req.ServicePoint.ConnectionLimit = maxTry;
        if (!string.IsNullOrEmpty(strReferer))
            req.Referer = strReferer;
        req.Accept = accept;
        req.UserAgent = userAgent;
        req.Method = "GET";
        req.Timeout = 15000;
        req.AllowAutoRedirect = false;
        HttpWebResponse res = req.GetResponse() as HttpWebResponse;
        request = req; // Liigo: 如果此处调用req.Abort()关闭请求,则返回的Response对象的数据流是不可读的("流不可读")。
        return res;
    }
    catch
    {
        if (req != null) req.Abort();
        request = null;
        return null;
    }
}

你可能感兴趣的:(Web,C#)