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

 

现象
现象: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;
    }
}
 

C#中,HttpWebRequest是用于发送HTTP请求的基础类。如果你想要设置超时重试次数,通常需要自定义一些逻辑来处理网络请求可能出现的超时错误。因为HttpWebRequest本身并没有直接提供自动重试的机制。 以下是简单的步骤: 1. 创建`HttpWebRequest`实例并设置基础超时时间: ```csharp HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://example.com"); request.Timeout = TimeSpan.FromSeconds(30); // 设置默认超时时间为30秒 ``` 2. 在发送请求之前,你可以封装这个操作,并添加一个循环结构,比如最多重试5次: ```csharp int retryCount = 0; while (retryCount < 5) { try { using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { // 请求成功,处理响应 } break; // 如果没有异常,则跳出循环 } catch (WebException ex) { if (ex.Status == WebExceptionStatus.ConnectTimeout || ex.Status == WebExceptionStatus.ReadTimeout) { // 网络超时,增加重试次数 retryCount++; if (retryCount < 5) // 如果还有重试机会 continue; else { // 超过最大重试次数,抛出异常或记录日志 throw new AggregateException("所有重试都失败了", ex); } } else { throw; // 其他类型的异常直接抛出 } } } ``` 在这个例子中,如果遇到连接或读取超时,会增加一次重试,直到达到最大重试次数5次。当然,实际应用中你可能还需要根据具体需求调整其他细节,如判断是否应该等待一段时间再重试等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值