deerchao的blog

Be and aware who you are.

HttpClient, 使用C#操作Web

我们知道, .Net类库里提供了HttpWebRequest等类,方便我们编程与Web服务器进行交互. 但是实际使用中我们经常会遇到以下需求,基础类里没有直接提供相应的功能(WebClient类包含这些功能,只是用起来稍微麻烦一点--谢谢网友东吴居士的提醒):
  • 对HttpWebResponse获取的HTML进行文字编码转换,使之不会出现乱码;
  • 自动在Session间保持Cookie,Referer等相关信息;
  • 模拟HTML表单提交;
  • 向服务器上传文件;
  • 对二进制的资源,直接获取返回的字节数组(byte[]),或者保存为文件

为了解决这些问题,我开发了HttpClient类.下面是使用的方法:

  • 获取编码转换后的字符串

    HttpClient client=new HttpClient(url);
    string html=client.GetString();

    GetString()函数内部会查找Http Headers, 以及HTML的Meta标签,试图找出获取的内容的编码信息.如果都找不到,它会使用client.DefaultEncoding, 这个属性默认为utf-8, 也可以手动设置.
  • 自动保持Cookie, Referer

    HttpClient client=new HttpClient(url1, null, true);
    string html1=client.GetString();
    client.Url=url2;
    string html2=client.GetString();

    这里HttpClient的第三个参数,keepContext设置为真时,HttpClient会自动记录每次交互时服务器对Cookies进行的操作,同时会以前一次请求的Url为Referer.在这个例子里,获取html2时,会把url1作为Referer, 同时会向服务器传递在获取html1时服务器设置的Cookies. 当然,你也可以在构造HttpClient时直接提供第一次请求要发出的Cookies与Referer:

    HttpClient client=new HttpClient(url, new WebContext(cookies, referer), true);

    或者,在使用过程中随时修改这些信息:

    client.Context.Cookies=cookies;
    client.Context.referer=referer;
  • 模拟HTML表单提交

    HttpClient client=new HttpClient(url);
    client.PostingData.Add(fieldName1, filedValue1);
    client.PostingData.Add(fieldName2, fieldValue2);
    string html=client.GetString();

    上面的代码相当于提交了一个有两个input的表单. 在PostingData非空,或者附加了要上传的文件时(请看下面的上传和文件), HttpClient会自动把HttpVerb改成POST, 并将相应的信息附加到Request上.
  • 向服务器上传文件

    HttpClient client=new HttpClient(url);
    client.AttachFile(fileName, fieldName);
    client.AttachFile(byteArray, fileName, fieldName);
    string html=client.GetString();

    这里面的fieldName相当于<input type="file" name="fieldName" />里的fieldName. fileName当然就是你想要上传的文件路径了. 你也可以直接提供一个byte[] 作为文件内容, 但即使如此,你也必须提供一个文件名,以满足HTTP规范的要求.
  • 不同的返回形式

    字符串: string html = client.GetString();
    流: Stream stream = client.GetStream();
    字节数组: byte[] data = client.GetBytes();
    保存到文件:  client.SaveAsFile(fileName);
    或者,你也可以直接操作HttpWebResponse: HttpWebResponse res = client.GetResponse();

    每调用一次上述任何一个方法,都会导致发出一个HTTP Request, 也就是说,你不能同时得到某个Response的两种返回形式.
    另外,调用后它们任意一个之后,你可以通过client.ResponseHeaders来获取服务器返回的HTTP头.
  • 下载资源的指定部分(用于断点续传,多线程下载)

    HttpClient client=new HttpClient(url);
    //发出HEAD请求,获取资源长度
    int length=client.HeadContentLength();

    //只获取后一半内容
    client.StartPoint=length/2;
    byte[] data=client.GetBytes();

    HeadContentLength()只会发出HTTP HEAD请求.根据HTTP协议, HEAD与GET的作用等同, 但是,只返回HTTP头,而不返回资源主体内容. 也就是说,用这个方法,你没法获取一个需要通过POST才能得到的资源的长度,如果你确实有这样的需求,建议你可以通过GetResponse(),然后从ResponseHeader里获取Content-Length.

计划中还有另外一些功能要加进来,比如断点续传, 多线程下载, 下载进度更新的事件机制等, 正在思考如何与现在的代码融合到一起,期待你的反馈.

你可以从这里下载目前版本的全部代码.

注意:使用时应该添加对System.Web.dll的引用,并在使用此类的代码前添加"using System.Web;",不然会无法通过编译(感谢Hyke的提醒).

[update:2007年8月11日]

  • 修复了一个与文件上传相关的bug;
  • 听从大家的意见,给公开方法和属性添加了XML注释;
  • 添加了断点续传的支持功能(还需要考虑一下怎么做能让使用更方便).
  • 修复了一个与Post相关的bug

posted on 2007-08-09 15:53 deerchao 阅读(4139) 评论(35)  编辑 收藏

评论

#1楼 [楼主] 2007-08-09 16:02 deerchao      

编辑器里的格式化代码功能出问题了,只以这么素面朝天了:(   回复  引用  查看    

#2楼  2007-08-09 17:13 Hyke      

很喜欢您的这个类。
很支持您开发更多功能。   回复  引用  查看    

#3楼  2007-08-09 17:22 无名 [未注册用户]

哎.若长一段代码.一句注释没有   回复  引用    

#4楼 [楼主] 2007-08-09 17:30 deerchao      

@无名
我认为方法,参数,属性名就是所有的注释.
除了//todo:以外的注释都是坏味道.   回复  引用  查看    

#5楼  2007-08-09 17:49 AA [未注册用户]

不错!   回复  引用    

#6楼  2007-08-09 18:09 Anders Liu      

@deerchao

@无名
我认为方法,参数,属性名就是所有的注释.
除了//todo:以外的注释都是坏味道.

-------------

这个我认为不妥。你在写一个类(将来可能是一个类库),所以你有责任让所有使用你的类型的人读懂它。

我的建议是,对于public的成员或类型,都应该加有详细的xml文档注释。   回复  引用  查看    

#7楼 [楼主] 2007-08-09 18:14 deerchao      

@Anders Liu
这种做法太重量级了,而且文档与代码的同步从来都是一个难以解决的问题.
我的观点是尽量从代码本身来提升其可读性,注释直接用变量/方法等的名称取代,文档则用测试取代.
  回复  引用  查看    

#8楼  2007-08-09 19:57 aspnetx      

不错
很适合做一个刷票的程序

下届超女的诞生就靠这个程序了:)   回复  引用  查看    

#9楼  2007-08-09 20:10 王琳 [未注册用户]

不错,学习一下   回复  引用    

#10楼  2007-08-09 21:43 Anders Liu      

@deerchao
可能你太“唯代码”了,要不就是我太刻板。

但是,如果一个vb开发者(或者其他古怪语言的开发者)想使用这个类(类库)呢?   回复  引用  查看    

#11楼 [楼主] 2007-08-09 22:19 deerchao      

@Anders Liu
我基本上看得懂VB.net的语法,所以我相信VB.net的程序员看C#程序也没有太大的困难;
我也很少在.Net平台下碰到其它古怪语言--当然我不是说它们不存在,像IronPython, IronRuby等我将来也可能会用到.我的观点是,明天的需求,明天再说,不然很容易落到学院派的作风,追求庞大,复杂,完美,而不是简单和实用.
而且,我还是觉得,现在,基本上所有合格的程序员(也许也除了专门玩Lisp,Scheme之类函数语言的),都能比较轻松地看懂质量优良的C#代码(简单易学性应该也算微软对C#的一个目标吧).
  回复  引用  查看    

#12楼  2007-08-09 22:44 在线代理 [未注册用户]

好东西,希望继续晚上,有注释总归是比没有注释好 。 拙见。   回复  引用    

#13楼  2007-08-09 23:51 csharphack      

编码的部分这样做还不够精确,可以参考firefox得源码   回复  引用  查看    

#14楼 [楼主] 2007-08-10 00:12 deerchao      

@csharphack
对,这样确实不够精确,甚至有可能错误(如果Header里没有指定编码,Meta标签也没有,而正文中出现了charset=xxx的话).
不过大部分情况下还是可以将就着用的.FireFox源代码没有接触过,有机会的学习一下,虽然很可能因为代码规模太大,找不到处理编码的部分...   回复  引用  查看    

#15楼  2007-08-10 00:14 qtybb      

@aspnetx
呵呵,搞笑啊。不过说的在理   回复  引用  查看    

#16楼  2007-08-10 09:57 雨恨云愁 [未注册用户]

基础类里没有直接提供相应的功能:

对HttpWebResponse获取的HTML进行文字编码转换,使之不会出现乱码;
自动在Session间保持Cookie,Referer等相关信息;
模拟HTML表单提交;
向服务器上传文件;
对二进制的资源,直接获取返回的字节数组(byte[]),或者保存为文件

除了第一点 其它的全有了   回复  引用    

#17楼 [楼主] 2007-08-10 11:42 deerchao      

@雨恨云愁
我还真的不知道,应该怎么用?   回复  引用  查看    

#18楼  2007-08-10 15:34 Hyke      

强烈再次支持啊!现在我还不方便测试,等有今晚有空一定要好好测试一下。
请尽快发布更多功能的啊!   回复  引用  查看    

#19楼 [楼主] 2007-08-10 15:42 deerchao      

@Hyke
基本上想好了怎么实现断点续传,今天或明天会更新这个功能.   回复  引用  查看    

#20楼  2007-08-11 06:47 DDotNet [未注册用户]

@deerchao

我支持@Anders Liu,既然你是开发的类,至少应该再每个方法上面有XML注释   回复  引用    

#21楼  2007-08-11 23:38 没有耳多 [未注册用户]

我先谢谢再说.   回复  引用    

#22楼  2007-08-12 15:59 Hyke      

@deerchao
应该注明:
用户在使用这个类的时候,需要导入引用“命名空间:System.Web
程序集:System.Web(在 system.web.dll )”。否则会提示找不到一些东西。   回复  引用  查看    

#23楼  2007-08-12 16:48 东吴居士      

我可以绝对的告诉你,.net提供的WebClient,webRequest足够满足你下面的所有功能了。。可能第一个需要人为判断下。当然包装下更好

* 对HttpWebResponse获取的HTML进行文字编码转换,使之不会出现乱码;
* 自动在Session间保持Cookie,Referer等相关信息;
* 模拟HTML表单提交;
* 向服务器上传文件;
* 对二进制的资源,直接获取返回的字节数组(byte[]),或者保存为文件   回复  引用  查看    

#24楼 [楼主] 2007-08-12 16:58 deerchao      

@东吴居士
以前还真没注意到System.Net.WebClient这个类,谢谢你的提醒.
看了一下,它比我的这个类多了异步操作的方法,少了一些对常用操作的包装.
  回复  引用  查看    

#25楼 [楼主] 2007-08-12 17:05 deerchao      

@Hyke
谢谢你的提醒:)   回复  引用  查看    

#26楼  2007-08-12 19:55 东吴居士      

@deerchao:
不客气。我以前做的东西也做了和你一样的工作量,包装了webrequest和cookieContainer,自己实现页面Load和post,甚至post到包括有附件的mutli-part的form.以及cookie的获取保持传递,文件图片保存等。webclient的文件下载和上传包装控制的比较好,像你说的有不少异步操作。不过对于这样的基础类库,正规项目应用我还是很提倡按照自己的意思封装一下,这有成就感的。简单的应用用用webclient就可以了。。。其实我还是用webrequest的多,webclient也是后来才发现的。   回复  引用  查看    

#27楼  2007-08-13 00:58 volnet(可以叫我大V)      

@deerchao
坏味道本身不在乎于注释,而注释本身不是坏味道。。。。。Reflecting看的不够悟道~呵呵
注释必然是需要的,因为我们还是要谦虚于每个看代码的人,或者说别人文化程度没你高,或者说自己的表达能力不够到位,不能保证自己的每个方法名就是那么地恰到好处,也不能强求别人的理解和你如出一辙对吧?
花点时间给自己的代码“绿化”一下(注释在VS默认是绿色的),我想对北京奥运会应该算是一个支持吧。   回复  引用  查看    

#28楼  2007-08-14 11:51 银饰 [未注册用户]

我想如果把判断状态(404,500,200)加上比较好   回复  引用    

#29楼  2007-11-22 21:24 gx111 [未注册用户]

在winform程序中,有用过引用web服务的地方,好象跟这个也有差不多的地方,引用一个web服务,站点用url赋值,可以实现灵活获得站点方法   回复  引用    

#30楼  2007-11-28 20:48 私家侦探 [未注册用户]

粗粗的看了一下,晚上开始动工,把这个改造成可以自动下载指定网页的程序,包括网页中的css,js,flash,以及css中的图片,我主要想自动下载网页中的flash,flash的xml配置文件中的图片以及css中的图片,目前没有看过有什么程序同时包含这两个功能,
当你看到一个很好的flash或者菜单总是要花个至少十分钟去剥,这下火大了,亏还是学程序的   回复  引用    

#31楼  2007-12-02 20:43 私家侦探 [未注册用户]

当请求的资源不存在时,GetResponse()会出错噢,不过我已经帮你修正了嘿嘿   回复  引用    

#32楼  2008-04-09 16:33 阿良.net [未注册用户]

楼主帮忙看看怎样登录阿里妈妈.

HttpLook捕获到的
POST /membersvc/member/login.htm HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, application/x-silverlight, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, application/xaml+xml, */*
Referer: http://www.alimama.com/membersvc/member/login.htm
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; GOSURF; Embedded Web Browser from: http://bsalsa.com/; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; MAXTHON 2.0)
Host: www.alimama.com
Content-Length: 177
Connection: Keep-Alive
Cache-Control: no-cache
Cookie: ystat_b_cookie=0.略

action=MembersAction&event_submit_do_login=true&forward=&query_string=&_fmm.l._0.l=阿里妈妈帐号Email&originalLogpasswd=密码&_fmm.l._0.lo=哈稀后的密码

我用如下代码为什么不行呢.
private void NewMethod()
{
//action=MembersAction&event_submit_do_login=true&forward=&query_string=&_fmm.l._0.l=阿里妈妈帐号Email&originalLogpasswd=密码A&_fmm.l._0.lo=我用HttpLook哈稀后查到的密码

string url = "http://www.alimama.com/membersvc/member/login.htm";
bool keepCookie = true;
WebContext context = new WebContext();
context.Referer = "http://www.alimama.com/membersvc/member/login.htm";

HttpClient client = new HttpClient(url, context, keepCookie);
client.PostingData.Add("action", "MembersAction");
client.PostingData.Add("event_submit_do_login", "true");
client.PostingData.Add("forward", "");
client.PostingData.Add("query_string", "");
client.PostingData.Add("_fmm.l._0.l", "阿里妈妈帐号Email");
client.PostingData.Add("originalLogpasswd", "密码A");
client.PostingData.Add("_fmm.l._0.lo", "我用HttpLook哈稀后查到的密码");

string text = client.GetString();
CookieCollection cookies = client.Context.Cookies;

StringBuilder sb = new StringBuilder();
foreach (Cookie cookie in cookies)
{
sb.AppendFormat("{0}={1}&", cookie.Name, cookie.Value);
}

textBox1.Text = "cookie= " + sb.ToString() + "\r\n\r\n" + text;


url = "http://www.alimama.com/membersvc/report/seller_earning.htm";
//HttpClient client2 = new HttpClient(url, context, true);
client.Url = url;

text = client .GetString();
textBox2.Text = text; //这里为空,不能返回seller_earning.htm的html,不知为什么
}   回复  引用    

#33楼  2008-06-05 03:04 hzexe [未注册用户]

啊,晕,看了这么久才知道是vs2008里的,
webclent改进了这么多啊。   回复  引用    

#34楼  2008-06-16 18:09 bie [未注册用户]

不支持Proxy, 不支持set credential。
修改下吧。很容易的。   回复  引用    


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-08-20 11:52 编辑过


相关链接:
 


<2007年8月>
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

导航

统计

公告

给网络添加价值,就是让自己增加价值.

本博客所有内容,均为原创或对互联网已有资源的再加工,希望对你有用.在声明原作者的前提下,你可以任意使用,但本人对其正确性,使用的后果等不做任何担保,也不负任何责任.

正则表达式30分钟入门教程 v2.21 2007-8-3

I Want Spec#!

与我联系

搜索

 

常用链接

留言簿(64)

我管理的小组

我的标签

随笔档案(125)

文章分类(9)

文章档案(9)

新闻档案(9)

Links

积分与排名

最新评论

评论排行榜

60天内阅读排行