我是路标
FSLIB.NETWORK网络库系列文章
- 12306订票助手.NET V10.6.1 发布8年前 (2016-09-01)
- 开源 FSLIB.NETWORK 库 2.2.0.08年前 (2016-08-02)
- FSLIB.NETWORK手册(1) · 基本概念和流程8年前 (2016-05-05)
- 原创FSLib.Network库更新 2.0.0 版8年前 (2016-04-05)
- 原创FSLib.Network库更新 1.6.0版(目前专注于HTTP的高性能高易用性网络库)9年前 (2015-12-13)
- 玩具系列:批量QQ群签到工具v2 (暂时屏蔽自定义位置功能)9年前 (2015-08-29)
- 玩具系列:批量QQ群签到工具(支持自定义位置)9年前 (2015-08-28)
- 12306订票助手.NET 8.0.8 发布9年前 (2015-08-21)
- 12306订票客户端 FOR .NET 演示项目 【7】登录9年前 (2015-08-18)
- 12306订票客户端 FOR .NET 演示项目 【6】验证码输入9年前 (2015-08-12)
- 12306订票客户端 FOR .NET 演示项目 【5】获得余票数据9年前 (2015-06-10)
- 原创FSLib.Network库发布 1.5 版9年前 (2015-06-09)
- 12306订票客户端 FOR .NET 演示项目 【4】界面框架&基础数据初始化9年前 (2015-06-08)
- 12306订票客户端 FOR .NET 演示项目 【3】流程分析和项目规划9年前 (2015-05-28)
- 12306订票客户端 FOR .NET 演示项目 【2】准备工具9年前 (2015-05-22)
- 12306订票客户端 FOR .NET 演示项目 【1】项目概况9年前 (2015-05-19)
- 原创FSLib.Network库发布 1.4 版9年前 (2015-05-08)
- 放一个抓取网页的信息监控小工具源码9年前 (2015-04-27)
- FSLib.Network网络库使用教程[2] 实例教程·美女们快到硬盘里来!10年前 (2015-01-30)
- FSLib.Network网络库使用教程[1] 基本使用10年前 (2015-01-19)
- 原创FSLib.Network库(目前专注于HTTP的高性能高易用性网络库)10年前 (2015-01-18)
0.背景
开始在设计订票助手.NET的时候,我就在策划写一个比较易用的HTTP客户端库来完成底层的操作。由于.NET原生的HttpWebRequest比较复杂难用,而内置的WebClient先天不足。
.NET 4.5中新增了HttpClient,但可惜.NET4.0不支持XP,所以暂时只能放弃HttpClient了。在这种种背景之下,我花了很长的时间来打磨这个网络库。虽然起名叫Network,但是目前专注于HTTP库。
这个网络库在订票助手.NET中得到了广泛全面的使用,几乎所有对12306发出的请求都是由它发出的。在订票助手.NET中,最近四个月中,由它发出的请求过亿,其稳定性也是蛮有保证的嘛。
1.功能特点&运行需求
其实它是对HttpWebRequest/HttpWebResponse的包装,目的是为了用起来更简单明了。设计的时候就为了提供更高的可用性和扩展性。所以……它具备……如下的特性。
- 高综合处理能力:自动处理Cookies,自动跟踪引用页,自动GZIP压缩解压缩,自动编码识别……
- 自动的数据处理能力:理论上你想发的数据,不用转换丢给它,它都能给你发出去;理论上你想收的对象,类型丢给它, 它都能给你弄回来……
- 高健壮性:如果不是特殊情况,坚决不抛异常让你去catch。相反的是,它用状态来向你表示结果是否正确
- 高处理能力:完全多线程处理,支持同步、异步、任务模式,异步时甚至能自动处理同步线程上下文,想用.NET中的await?没问题
- 高扩展性:丰富的事件以及扩展性支持,你可以继承它来实现自己想做的事情。甚至都自带了抓包。。。。
由于编写时使用了大量的匿名类型和表达式,因此不能用于.NET3.5以下的平台。目前支持的平台为.NET3.5/4/4.5。其中,运行在3.5平台上时,个别特性无法使用。
2.先来看几个例子吧
口说无凭,先来看几个栗子吧。
提示
为了代码编写方便,以下全用.NET中的异步模式来编写(async/await),同步和异步的代码在后续的教程中提供。
此处仅为展示基本的用法,更详细的使用说明和实例将会在后续提供。
2.1 先来瞄一下我们的测试程序界面和测试网页
这里准备了一个简单的测试程序来展示库的基本用法。所以呢,先来个网页用来返回数据吧。
这里准备的服务器端程序非常简单,就是一个普通的aspx页面,接收一个GET或POST请求,将表单中所有数据、当前地址、是否是JSONP请求以及上传的文件信息返回。值得一提的是,如果上传了文件,返回的文件数据是经过base64编码的结果。
代码如下。
protected async void Page_Load(object sender, EventArgs e) { var callback = Request.QueryString["callback"]; //是否是JSONP请求 var isJsonP = !string.IsNullOrEmpty(callback); Response.ContentType = isJsonP ? "application/javascript" : "application/json"; //根据是否是JSONP设置正确的响应类型 if (isJsonP) { Response.Write(callback); Response.Write("("); } //form data var form = new Dictionary<string, string>(); foreach (var key in Request.Form.AllKeys) { form.Add(key, Request.Form[key]); } //filedata string filedata = null; string filename = null; var filelength = 0; if (Request.Files.Count > 0) { filename = Request.Files[0].FileName; filelength = Request.Files[0].ContentLength; var buffer = new byte[filelength]; filelength = await Request.Files[0].InputStream.ReadAsync(buffer, 0, buffer.Length); filedata = Convert.ToBase64String(buffer, 0, filelength); } //返回JSON结果 Response.Write(JsonConvert.SerializeObject(new { ret = true, //始终为true url = Request.RawUrl, //原始地址 jsonp = isJsonP, //是否是jsonp form, //表单数据 file = new //文件信息 { name = filename, length = filelength, data = filedata } })); if (isJsonP) { Response.Write(");"); } }
而下面这是测试程度界面。代码后面详述。
注意红框的地址,这里是本地的测试服务器地址。
2.2 最简单的GET请求:抓百度首页
最常见的GET请求一个网页。测试代码如下。
点击“抓百度首页”按钮后的响应代码。
private async void btnGrabBdPage_Click(object sender, EventArgs e) { InitWorkingUI(); AppendText("正在请求 http://www.baidu.com/ ..."); var client = new HttpClient(); var context = client.Create<string>(HttpMethod.Get, "http://www.baidu.com/"); await context.SendTask(); if (context.IsValid()) { AppendText(context.Result); } else { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } WorkingFinished(); }
上面的示例,真正核心的部分从 var client=
到 if(context.IsValid())
判断语句结束时结束。流程非常简单,新建个Client,Create一个请求上下文(context
),发送并等待,判断是否成功(IsValid()
),显示结果。最终结果如下。
是不是很简单呢?嗯。不过GET请求过于简单,用其它方法写起来也不见得会麻烦多少。所以继续吧。
注意上面用到了一些辅助函数,这些函数在下面都用到了,以下贴出来。
辅助函数
#region 辅助函数 void InitWorkingUI() { txtResult.Clear(); pgWaiting.Visible = true; } void AppendText(string txt) { txtResult.AppendText(txt); txtResult.AppendText(Environment.NewLine); txtResult.AppendText(Environment.NewLine); txtResult.ScrollToCaret(); } void WorkingFinished() { pgWaiting.Visible = false; } #endregion
2.3 最简单的数据处理请求:抓一个图片
OK,现在来抓个百度的图片玩吧。这种情况下代码是不是比上一个例子复杂呢?看代码吧。
Snippet
private async void btnGrabBdLogo_Click(object sender, EventArgs e) { InitWorkingUI(); AppendText("正在请求 http://www.baidu.com/img/bdlogo.png ..."); var client = new HttpClient(); var context = client.Create<Image>(HttpMethod.Get, "http://www.baidu.com/img/bdlogo.png"); await context.SendTask(); if (context.IsValid()) { AppendText("正在显示图片..."); var form = new Form() { Size = context.Result.Size }; var pic = new PictureBox(); form.Controls.Add(pic); pic.Dock = DockStyle.Fill; pic.Image = context.Result; form.Show(); form.Activate(); } else { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } WorkingFinished(); }
和上一个例子比较,差别很小。区别最大的是 Create
方法的类型参数从 String
变成了 Image
(注意哦,不是显示图片。。)。对。你只要传入预期的对象类型,库就会自动帮你转换成目标的对象类型。所以结果如下。
2.4 最简单的POST请求:POST一个字符串
嗯。POST数据这种事儿咱也不会少干。
先来个例子吧,咱做的最多的是把一个表单直接拼成字符串发出去。
private async void btnPostString_Click(object sender, EventArgs e) { InitWorkingUI(); var url = txtUrl.Text; AppendText(string.Format("正在请求 {0} ...", url)); var client = new HttpClient(); var context = client.Create<JsonContent>(HttpMethod.Post, url, data: "a=1&b=2&c=520"); await context.SendTask(); if (context.IsValid()) { AppendText("成功..."); DumpObject(context.Result); } else { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } WorkingFinished(); } void DumpObject(JsonContent obj) { AppendText("返回:" + obj.Ret); AppendText("JSONP:" + obj.JsonP); AppendText("请求地址:" + obj.Url); AppendText("表单数据"); AppendText("-----------------"); AppendText(string.Join(Environment.NewLine, obj.Form.Select(s => s.Key + "\t==>\t" + s.Value))); AppendText(""); AppendText("文件数据"); AppendText("-----------------"); if (obj.File == null || string.IsNullOrEmpty(obj.File.Name)) { AppendText("没有文件"); } else { AppendText("文件名\t==>\t" + obj.File.Name); AppendText("文件大小\t==>\t" + obj.File.Length.ToSizeDescription()); AppendText("文件内容\t==>\t" + obj.File.Data); } }
上面的主要代码没变,区别在于加了一个DumpObject方法来显示对象的内容。
注意加粗加下划线的那行代码。那是关键的一行区别,它定义了请求是POST,并且会发一个字符串做数据。这个例子的结果如下。
备注一下,那个JsonContent是个对象,声明如下。
public class JsonContent { public bool Ret { get; set; } public Dictionary<string, string> Form { get; set; } public JsonFileInfo File { get; set; } public bool JsonP { get; set; } public string Url { get; set; } } public class JsonFileInfo { public string Name { get; set; } public int Length { get; set; } public string Data { get; set; } }
2.5 来个高级点儿的POST请求:POST一个对象
直接POST字符串太奥特拉。我要直接POST一个对象,你信不信?
private async void btnPostObject_Click(object sender, EventArgs e) { InitWorkingUI(); var url = txtUrl.Text; AppendText(string.Format("正在请求 {0} ...", url)); var client = new HttpClient(); var context = client.Create<JsonContent>(HttpMethod.Post, url, data: new { a = 1, b = 2, c = 520, d = "you'r 2b." }); await context.SendTask(); if (context.IsValid()) { AppendText("成功..."); DumpObject(context.Result); } else { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } WorkingFinished(); }
依然注意Create那行,注意看到data原来可以直接传递一个匿名对象进去的。其实实体对象也可以的啦。结果有差别吗?
嗯。So Easy。
2.6 甚至……要发送数据但是是GET
有时候要GET请求,但是有参数。难道这种情况下我们就只能手动拼URL了么?非也……
private async void btnGetSendData_Click(object sender, EventArgs e) { InitWorkingUI(); var url = txtUrl.Text; AppendText(string.Format("正在请求 {0} ...", url)); var client = new HttpClient(); var context = client.Create<JsonContent>(HttpMethod.Get, url, data: new { a = 1, b = 2, c = 520, d = "you're 2b" }); await context.SendTask(); if (context.IsValid()) { AppendText("成功..."); DumpObject(context.Result); } else { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } WorkingFinished(); }
以上代码,除了将Post改成Get外,和之前的请求并没有差别。但是,结果完全正确……
注意看请求地址……
2.7 哪怕对方是个JSONP?不在话下
有时候我们要调用一个API,但是那个API是JSONP请求,也就是说返回的是一个javascript。这个搞不定吗?在大部分情况下,可以搞定……
private async void btnJsonP_Click(object sender, EventArgs e) { InitWorkingUI(); var url = txtUrl.Text + "?callback=_jsonCallback"; AppendText(string.Format("正在请求 {0} ...", url)); var client = new HttpClient(); var context = client.Create<JsonContent>(HttpMethod.Get, url, data: "a=1&b=2&c=520"); await context.SendTask(); if (context.IsValid()) { AppendText("成功..."); AppendText("JSONP参数\t==>\t" + (context.AcquireResponseToObject().JsonpCallbackName ?? "")); DumpObject(context.Result); } else { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } WorkingFinished(); }
以上代码的差别主要是URL地址里面加了JSONP回调地址。其它的并没有什么差别。但是……依然很OK的,啊哈哈哈。
注意看输出,不仅获得了结果,还获得了JsonP参数。
2.7 要上传文件?不是问题
最简单的上传任务,让用户任意选一个文件,然后post到服务器上去。
private async void btnUpFile_Click(object sender, EventArgs e) { MessageBox.Show(this, "为了避免太慢,请选择小文件。"); var ofd = new OpenFileDialog(); ofd.Filter = "所有文件(*.*)|*.*"; if (ofd.ShowDialog() != DialogResult.OK) return; InitWorkingUI(); var url = txtUrl.Text; AppendText(string.Format("正在请求 {0} ...", url)); var client = new HttpClient(); var context = client.Create<JsonContent>(HttpMethod.Post, url, data: new { a = 1, b = 2, c = 520, d = "you're 2b", file = new HttpPostFile("file", ofd.FileName) }); await context.SendTask(); if (context.IsValid()) { AppendText("成功..."); DumpObject(context.Result); } else { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } WorkingFinished(); }
有没有感觉上传文件都这么简单?嗯。。。
2.9 服务器出错了?
在传统的请求里面,一般出错后都是异常,要你try来catch去,十分痛苦不说,你还不知道到底是啥错。
所以这里专门针对错误做了处理,哪怕错误都让你知道为啥……
private async void btnGrab404_Click(object sender, EventArgs e) { InitWorkingUI(); AppendText("正在请求 http://www.fishlee.net/404.html ..."); var client = new HttpClient(); var context = client.Create<string>(HttpMethod.Get, "http://www.fishlee.net/404.html"); await context.SendTask(); if (!context.IsValid()) { AppendText("错误!状态码:" + context.Response.Status); AppendText("服务器响应:" + context.ResponseContent); } else { throw new Exception("不应该执行到这里!"); } WorkingFinished(); }
你看……错误原因一目了然,服务器错误的信息也告诉你了。
3. 引用&文档
嗯好啦,看到这里,你是不是也想试试了?嗯好吧。
目前发布的版本仅可以使用Nuget安装,如下所示。
4. 补充说明
- 此网络库依赖 FSLib.Extension 库(没错,我之前发的扩展库。。)以及 Newtonsoft.Json库(也就是大名鼎鼎的Json.NET)库
- 此网络库目前不开源,禁止用于商业目的
- 后续会有更详细的教程以及实例应用
- 如果您发现了问题或有任何建议,欢迎在论坛提出
老铁,这个库支持HTTP摘要认证吗?
支持。
老板 怎么设置全局user-agant
HttpSetting
对象有一个DefaultUserAgent
无效 还是以iFish_Network_Client作为User-Agant发请求
AppendLibAuthorVendor=false;
最新的2.0版本DLL 运行第一个小例子报错,这句话报错:await context.SendTask();
错误 1 “FSLib.Network.Http.HttpContext”不包含“SendTask”的定义,并且找不到可接受类型为“FSLib.Network.Http.HttpContext”的第一个参数的扩展方法“SendTask”(是否缺少 using 指令或程序集引用?) F:\My Demos\Network_Test\WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs 28 27 WindowsFormsApplication1
.NET版本是4.0+吗?
怎么改了user-agant没用啊
Setting 里面改。
改了没用怎么办 只有在context里改有用 外面改没用。 能不能这个Header和代理加到HttpClient的全局。会方便很多。
2.0 哪里发布的
啥意思?去这里提问吧 http://ask.fishlee.net/explore/category-21
请问服务返回的响应时间怎么精确到毫秒
。。请问您说的是什么响应时间?
比如我发送了一个http请求,服务会给我响应. 我是想知道服务器发出响应时的服务器时间(毫秒或微妙级)以及我收到这个响应花了多少时间(毫秒或微妙级).
HttpContext有个Performance属性,它是个性能对象,里面有相关的时间戳。参见 http://docs.fishlee.net/ifish/fslib.network/index.html#topic_000000000000018C_members–.html
这些都是本机时间 我其实想获取服务器的时间 毫秒级的 有没有办法. 我现在拿的是context.Response.Date是秒级的. 很不精确.
服务器时间?拿不到的,想其它辙吧。
我就是调整本地时间和服务器时间分毫不差.哈哈.我再想想看.谢谢老板.
请教下,按照2.7 上传 提示 MethodNotAllowed 是为什么
我知道了,是我服务器的问题 谢谢
记得初二的时候就关注了 你的博客 自己也想学C# 两年过去了 我还是没有一点进展…….现在准备 用你的这个库 学学POST。。。嘿嘿!!!!!!!!!
2.2 中 var context = client.Create(HttpMethod.Get, "http://www.baidu.com/"); 网址需替换为https,不替换的话会报错302 Found
嗯,百度的http加密是最近的事情,写博客的时候没有这个。302不是错误其实,只是默认是不开启自动跳转的。
怎么设置Origin
最新版本有直接的属性可用。
怎么设置keep-alive
如何自动处理cookies的能举个例子吗,比如需要用A网页的cookies,才能取到 B网页数据
这个的意思是所有交互过程中的Cookies是自动处理的。。。不需要手动处理。你这问题不对头啊,直接去A网页请求不就好了吗。
未能加载文件或程序集“Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)
嗯,库的版本依赖有问题。。乃把JSON库升级到7.0,或用BindingRedirect做个重定向就行了。依赖的版本号是7.0,但是manifest里写成了6.0,自动装的话,装的是6.0的库。
鱼大,如何上传一个 multipart/form-data 格式的二进制图片到网站?谢谢
参见2.7的示范代码
这个和微软的HttpClient比起来有什么优点呢, 要说4.0的话引了BCL和HttpClient也能支持的啊, 我看12306助手也引了BCL的啊
使用简单,功能足够。
大神你好;我用你的类库,出现一个问题;http://www.pbc.gov.cn/ 这个网站用你的类库怎么也打不开,老是提示“请开启JavaScript并刷新该页.”;这个怎么弄呢?还请不吝赐教,谢谢!
用类库打开是啥意思?
看了一下,那个应该是用了360安全宝做了拦截,需要在访问前执行一段js,目的应该是为了阻止非浏览器抓取。
那执行那段代码呢?如何执行,请赐教,谢谢!
那段js就在你看到的那个提示页面里,自己看下,就是设置了三个Cookeis
实在不好意思,我都不好意思再问了,我看了半天,看不懂;我对这方面是基本文盲,如果你有时间还请帮我写个demo;谢谢啦,实在不好意思!:-)
先试试再说
我要远程登录一个网站。那个网站是用.net拖控件搞的。页面是尽是视图状态的Hidden代码,这种要怎么操作?
这个可能需要具体分析。如果需要post原始的状态数据的话,可能需要获得原来页面中所有的隐藏字段了。
哦就是需要先get一下,把隐藏字段拿 下来然后post的时候带上。。那种页面一般一个页面好几个button控件。我怎么确定我post的数据是post给哪个 button的事件呢?
一般post回去的时候点击的按钮是根据表单的字段区分的。具体抓个包就可以看到了。
鱼大就是厉害。
“System.InvalidOperationException”类型的未经处理的异常在 FSLib.Network.dll 中发生
其他信息: 对象的当前状态使该操作无效
原来是同步异步的问题,选择.Send(),不使用任务模式就OK了; 谢谢!
嘿嘿支持了,太好了。比网上苏飞net的要高级一些。 评论框框好可爱~~~
前辈,在上面的示例代码中,很关键的一点是对HttpClient类的使用,
您的网络库是不是包含了提供了这个类的?我们要使用这个类只需要
应用这个网络库就可以了?
这个网络库就是提供了这个类了啊。。。安装对应的包就有了的。
都是需要引用什么命名空间?我这里提示“未能找到类型或命名空间名称“HttpClient”(是否缺少 using 指令或程序集引用?) ”
DEMO怎么没下载地址呢
DEMO很简单啊。。几乎所有源码都提供了
还是想要个demo。楼主可能忽略了自己认为的基础部分,但是我等小白按照源码打上无法运行。
后面会有实例项目的,DEMO只是演示。。。
要是能开源就好了
不开源是暂定,不排除后续开源的可能。
期待开源啊!
请问, 这个库可以用来编写调用微信API的PC程序么?
只要是HTTP的都可以。
我擦。。。虽然看不懂但我居然看完了。。。
鱼儿,我在VS搜不到你的插件呢!。
在项目引用节点,单击右键,选择nuget程序包,然后选择联机,包含预发行版,输入关键字,OK了;
await context.SendTask();这个再NetWork的2.0里面不能用了吗?
错误 1 “FSLib.Network.Http.HttpContext”不包含“SendTask”的定义,并且找不到可接受类型为“FSLib.Network.Http.HttpContext”的第一个参数的扩展方法“SendTask”(是否缺少 using 指令或程序集引用?) F:\My Demos\Network_Test\WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs 28 27 WindowsFormsApplication1
SendTask() 方法已经改名为 SendAsync()