基于HttpWebRequest的通用请求和响应处理

简介:

最近基于C#写了个Spider框架,所以有了此文的心得,虽然这话题也不算什么新话题了,但我还是想写一篇这样的文章,因为我发现这玩意真的写起代码来还是有很多细节的,要做到通用还是有一定难度的。当然,本文会略过最基本的一些东西,如GET/POST方式,UserAgent等,这不是本文要讨论的重点。

我使用过程中遇到的最大挑战是以下几个问题:

a. 如何处理chunked的页面

b. 如何处理gzip过的页面

c. 如何自动判断文本编码

d. 如何使用CookieContainer

 

我们一个一个来,先从第一个开始。chunked是常用的web服务器设置,也经常与gzip一起使用,

a. 如何处理chunked的页面

image (fiddler的Response Header视图截图)

chunked的好处最主要的是页面优化,浏览器通常会在接收完第一个chunked块之后开始解析页面,而不是等到整个页面加载完成。关于chunked的传输细节有兴趣的朋友可以继续阅读以下两篇文章http 《chunked传输 
HTTP chunked+gzip及浏览器兼容测试

由于页面的chunked,我们必须通过stream方式来获得数据,且由于之后要对获得的Stream进行gzip解密,所以我们需要把Stream暂时保存到MemoryStream中,如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
MemoryStream memory =  null ;
int  length = 0;
 
if  (res.ContentLength > 0)
{
     length = ( int )res.ContentLength;
}
else
{
     length = 8000;
}
memory =  new  MemoryStream(length);
Byte[] buf =  new  byte [4096];
Stream resStream = res.GetResponseStream();
int  count = 0;
 
do
{
     count = resStream.Read(buf, 0, buf.Length);
     if  (count != 0)
     {
         memory.Write(buf, 0, count);
     }
while  (count > 0);

这里利用ContentLength获取实际大小,但在有些情况下,ContentLength取不到,就会有个默认大小8000。当然这只是MemoryStream初始,如果Write的时候超过,它会自动增加大小(我记得是默认大小的两倍)。

b. 如何处理gzip

gzip的好处想必不用我说了吧,自然是传输大小更小,动态压缩,目前大部分门户都会打开这个选项,其平均压缩效果可以达到不开之前的40%左右,以下是处理gzip的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stream responseStream =  null ;
//handle gzip or deflate stream
if  (res.ContentEncoding.ToLower().Contains( "gzip" ))
{
     responseStream =  new  GZipStream(memory, CompressionMode.Decompress);
}
else  if  (res.ContentEncoding.ToLower().Contains( "deflate" ))
{
     responseStream =  new  DeflateStream(memory, CompressionMode.Decompress);
}
else
{
     responseStream = memory;
}

压缩方式可以通过ContentEncoding属性获得,这里同时也处理了deflate的压缩方式,不过目前我没遇到过这种网站,这代码是拷贝过来的,呵呵。

c. 如何自动判断文本编码

编码问题往往是让人头疼的问题,因为某些情况你拿到的数据会变成乱码,这就表示编码没有处理好。页面的实际编码是保存在ContentType中的,如下所示

image(fiddler的Response Header视图截图)

这里的charset就是我们需要的编码信息,编码的自动选择全靠它了,编码的真正作用发生在我们把刚才的responseStream转换为string,代码如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int  charsetPos = res.ContentType.IndexOf( "charset=" );
string  encoding =  "UTF-8" ;
 
if  (charsetPos >= 0)
{
     int  semicolonPos = res.ContentType.IndexOf( ";" , charsetPos);
     encoding = res.ContentType.Substring(charsetPos + 8, semicolonPos > 0 ? semicolonPos - charsetPos - 8 : res.ContentType.Length - charsetPos - 8);
}
var  oEncoding = Encoding.GetEncoding(encoding);
 
using  (StreamReader sr =  new  StreamReader(responseStream, oEncoding))
{
     content = sr.ReadToEnd();
}

我这边用了比较原始的方法,没有用正则表达式,如果你觉得这个方法很挫,你可以换成正则表达式。看到没,StreamReader通过oEncoding得知该用哪种编码来把byte[]转换为String。

(这里牵扯到几年前我做过的一道面试题:String和byte[]如何进行转换?说说你的经验。这道题虽然很多公司不面,但我倒觉得这道题是可以深入的,因为encoding其实是个很不错的面试话题,也很考基础知识。)

d. 如何使用CookieContainer

通常呢,稍微有点防御的网站都会验cookie,或者放个身份认证的cookie,如何在请求中放cookie呢,自然是靠CookieContainer(其实也可以通过AddHttpHeader,但本文只讨论CookieContainer,好处等会儿说,先卖个关子。)CookieContainer的接口有点搞,因为既有Add,也有SetCookie,我一开始就上了老当,用了Add,结果没有效果。SetCookies才是正确的方法,这个方法即可以一次性设置完所有的cookie,也可以一个一个设置,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
CookieContainer cookieContainer =  new  CookieContainer();
cookieContainer.SetCookies( new  Uri(hostname),  new  Cookie( "aaa" "98d4c408-7923-443a-ba3f-99df3bea7792" "/" "a.com" ).ToString());
cookieContainer.SetCookies( new  Uri(hostname),  new  Cookie( "bbb" "0" "/" "a.com" ).ToString());
cookieContainer.SetCookies( new  Uri(hostname),  new  Cookie( "ccc" "0" "/" "a.com" ).ToString());
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Headers.Add(HttpRequestHeader.AcceptEncoding,  "gzip,deflate" );
req.AllowAutoRedirect =  true ;
req.Accept =   "text/html, application/xhtml+xml, */*" ;
req.Timeout = 50000;
req.Method =  "GET" ;
req.CookieContainer = cookieContainer;
var  response = (HttpWebResponse)req.GetResponse();

这里使用Cookie而非直接用字符串是出于安全考虑,因为Cookie对象的ToString自动帮我们把encoding做掉了,所以这么写会比直接用字符串来的安全,SetCookies是需要指定Url的,不过通常对于同一个网站host应该是一样的,比如www.a.com,不过目前还没遇到过跨域问题,等遇到了我单独写一篇讨论,跨域可又是另外一个头疼的问题了。

这里要说一下CookieContainer和AddHttpHeader的区别,首先这两个不能同时使用,只能用一个,其次CookieContainer毕竟是容器,所以理论上可以把另外一个请求的cookie一起带过来,然后实现cookie传递逻辑,而AddHttpHeader做不到这一点,这是最大的区别。

先写到这,大家有什么关于HttpWebRequest的其他心得可以发在回复中,我会整理后更新这个帖子。










本文转自 瞿杰 51CTO博客,原文链接:http://blog.51cto.com/tonyqus/1133366,如需转载请自行联系原作者
目录
相关文章
|
6月前
|
前端开发
Request(获取请求数据的)请求转发,response响应数据,将数据反馈给前端
Request(获取请求数据的)请求转发,response响应数据,将数据反馈给前端
|
JSON 前端开发 网络架构
DRF--请求和响应
DRF--请求和响应
|
JSON PHP 数据格式
响应 方式
响应 方式
HTTP协议:响应消息和 Response对象和ServletContext对象
HTTP协议:响应消息和 Response对象和ServletContext对象
|
前端开发 API
axios定制化设置请求响应拦截器,统一处理请求响应
设置拦截器的目的在于:可以定制化,设置请求头,公共api,超时时间。统一处理响应,对于前端获取的数据更加清晰。
420 0
|
JSON 前端开发 数据格式
|
存储
无请求不响应?
无请求不响应?
112 0
无请求不响应?
|
JSON 数据库 数据安全/隐私保护
fastapi 响应模型 / 响应状态码 / 表单参数
fastapi 响应模型 / 响应状态码 / 表单参数
282 0
fastapi 响应模型 / 响应状态码 / 表单参数
|
Web App开发 XML 网络协议
一次完整的HTTP请求与响应涉及哪些知识?
TCP/IP协议模型(Transmission Control Protocol/Internet Protocol),包含了一系列构成互联网基础的网络协议,是Internet的核心协议,通过20多年的发展已日渐成熟,并被广泛应用于局域网和广域网中,目前已成为事实上的国际标准。TCP/IP协议簇是一组不同层次上的多个协议的组合,通常被认为是一个四层协议系统,与OSI的七层模型相对应。
165 0
一次完整的HTTP请求与响应涉及哪些知识?