背景
用了那么多年的HttpWebRequest,没想到今天就被这么狠狠的坑了一波。
直接放代码,来看看今天的罪魁祸首HttpWebRequest.Abort()方法。
本文基于.NET4.5框架
在实际项目中,Http请求部分用的异步接口IAsyncResult实现的,但是为了缩短篇幅,这里改为Task来举例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
HttpWebRequest request = null;
Task.Run(() =>
{
try
{
request = (HttpWebRequest)WebRequest.Create("http://www.jiuling.com");
using (WebResponse response = request.GetResponse())
{
//模拟一个耗时请求
Thread.Sleep(10 * 000);
using (Stream stream = response.GetResponseStream())
{
using (StreamReader streamReader = new StreamReader(stream, Encoding.UTF8))
{
string html = streamReader.ReadToEnd();
//Do succeed callback
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("请求失败咯,准备回调");
//Do failing callback
}
});
//模拟一个等待超时的情况
Thread.Sleep(5 * 1000);
request.Abort();
Console.WriteLine("请求超时咯,准备回调");
//Do failing callback
|
上面的代码主要做了这么几件事:
- 请求成功后,调用一个成功的回调
- 请求出现异常时,调用一个失败的回调
- 请求出现超时时,调用一个失败的回调
今天出问题的订单就是收到了两次失败回调。
问题分析
由于两次的失败回调间隔很短(大概20ms的样子),所以我当时直接排除了代码进行了两次Http请求。结合我们的业务代码,当时猜想应该是有个地方捕获了异常并进行了处理(失败回调),接着又将异常抛出,导致外层代码也处理了一次失败回调
经过层层排查,后面确定了我的猜想失败了。导致问题的原因是由于request.Abort()引发了一次WebException异常,从而使得代码进行了两次回调(上面代码中注释的部分)
虽然我以前经常用HttpWebRequest,但是对Abort方法的认知却是少之又少,甚至可以说是没怎么用过。今天的问题刚好是这个方法引起的,又是自己的知识盲区,那么刚好研究下这个坑货。😎😎
先来看看MSDN上的说明:
哦,上帝啊,这该死的机翻,这可是MS的官网啊,他们怎么能这样呢🐶🐶

翻译成人话就是:Abort方法用于取消对资源的请求,调用Abort方法后,如果再调用GetResponse、BeginGetResponse、EndGetResponse、GetRequestStream、BeginGetRequestStream、EndGetRequestStream方法,将会引发一个WebException异常,并且该异常的Status值为RequestCanceled。
原来MSDN写的清清楚楚明明白白。
我们并不是被Abort方法坑了,写上面那段代码的人对这个方法不熟悉,埋了一个坑;今天排查问题时,我对这个方法也不熟悉,导致没有第一时间发现问题。该反思、该反思!
问题复现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
HttpWebRequest request = null;
Task.Run(() =>
{
try
{
Console.WriteLine("准备发起请求");
request = (HttpWebRequest)WebRequest.Create("http://www.jiuling.com");
using (WebResponse response = request.GetResponse())
{
Console.WriteLine("GetResponse");
using (Stream stream = response.GetResponseStream())
{
Console.WriteLine("GetResponseStream");
using (StreamReader streamReader = new StreamReader(stream, Encoding.UTF8))
{
//假装这次网络请求超级慢~~
Thread.Sleep(5000);
Console.WriteLine("ReadData");
string html = streamReader.ReadToEnd();
//如果代码执行到这里的时候,调用Abort方法则不会引发异常
//Thread.Sleep(5000);
Console.WriteLine($"服务器返回:{html}");
}
}
}
}
catch (WebException ex)
{
Console.WriteLine($"出错了,状态:{ex.Status},错误信息:{ex.Message}");
}
});
Thread.Sleep(2 * 1000);
Console.WriteLine("准备Abort");
request.Abort();
Console.WriteLine("Abort完成");
// 代码输出:
// 准备发起请求
// GetResponse
// GetResponseStream
// 准备Abort
// Abort完成
// ReadData
// 出错了,状态:RequestCanceled,错误信息:请求被中止: 请求已被取消。
|
这个例子中,调用Abort的时候虽然服务器已经响应,但是实际上执行streamReader.ReadToEnd()方法的时候才会去读取数据流,因此依然会引发一个WebException异常。
总结
总结一下就是:
- 当网络请求未完成时,如果调用
Abort方法,则会引发一个WebException异常
- 遇到不懂的技术点、不懂的代码,不要用在实际项目中(类似已经出过好多事了!!)
- 好好学习天天向上
- 编不下去了