Http接口(WebApi)已经成为当下最流行的接口通信方式,即使不是标准RESTful,也不可否认它的遍地开花。HttpClient已经当之无愧成为最流行的Http客户端,没有之一。然而诸多Http接口都会对请求和响应制订了相应的规范,这里封装的ApiHttpClient遵循了常见的Http接口规范,让Http接口调用变得更简单。
Nuget包:NewLife.Core
源码地址:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Remoting/ApiHttpClient.cs
快速入门
首先看一个星尘服务的探针接口(http://star.newlifex.com:6600/api),返回内容如下
{ "code": 0, "data": { "name": "StarServer", "title": "星尘服务平台", "fileVersion": "2.0.2022.0816", "compile": "2022-08-16 08:25:58", "oS": "Microsoft Windows NT 10.0.17763.0", "userHost": "139.227.13.249", "remote": "139.227.13.249", "port": 6600, "time": "2022-08-29 21:16:15", "state": null } }
新建控制台项目,nuget引入 NewLife.Core,接着使用ApiHttpClient访问接口
var client = new ApiHttpClient("http://star.newlifex.com:6600"); var rs = await client.GetAsync<Object>("/api"); Console.WriteLine(rs.ToJson(true));
服务地址:http://star.newlifex.com:6600,接口名:/api,运行效果如下
返回类型Object,从输出可以看到没有了外面一层(code=0),只有data。
ApiHttpClient的核心用途就是封装请求以及解码响应!
GetAsync/PostAsync请求目标接口,并对响应数据进行解码,把data段内容反序列化到返回类型。上面指定返回Object,本质上就是IDictionary<String, Object>。
封装请求
星尘api接口实际上有个state入参,可以接受 /api?state=abcd1234 形式的参数
上面的例程稍微修改,送入两个参数 state 和 state2
var state = Rand.NextString(8); var state2 = Rand.NextString(8); XTrace.WriteLine("state={0}", state); XTrace.WriteLine("state2={0}", state2); var client = new ApiHttpClient("http://star.newlifex.com:6600"); var rs = await client.GetAsync<IDictionary<String, Object>>("api", new { state, state2 }); XTrace.WriteLine(rs.ToJson(true));
运行效果
可以看到/api接口接收了state参数并在响应中提现,而state2虽然也传递过去,但是接口并没有接收处理。
GetAsync 通过在url中拼接名值对来传递参数,只能传递简单参数;
PostAsync 通过在body中提交json来传递参数,能够传递复杂参数;
注意:参数匿名对象中的字段名,就是接口入参参数名。
异常响应
如果接口返回的code不是0或200,则认为处理异常,在客户端抛出ApiException异常。
try { var client = new ApiHttpClient("http://star.newlifex.com:6600"); var rs = await client.PostAsync<Object>("node/ping", new { ip = "127.0.0.1" }); XTrace.WriteLine(rs.ToJson(true)); } catch (ApiException aex) { XTrace.WriteLine("aex[{0}]={1}", aex.Code, aex.Message); } // 直接访问,返回Byte[],内部不再包装ApiException { var client = new ApiHttpClient("http://star.newlifex.com:6600"); var rs = await client.PostAsync<Byte[]>("node/ping", new { ip = "127.0.0.1" }); XTrace.WriteLine(rs.ToStr()); }
这里故意访问一个需要令牌验证的接口,实际上没有传递令牌。运行输出如下
22:02:02.463 13 Y - aex[403]=认证失败 22:02:02.484 13 Y - {"code":403,"data":"认证失败","traceId":null}
服务端返回了code=403的错误码,错误内容是“认证失败”。
测试代码访问了两次接口,第二次返回类型设为Byte[],只是为了看到完整的响应报文,而不会抛出异常。
访问令牌
基于安全需要,许多接口需要验证访问令牌。还是上面的示例,这里加上令牌(来自星尘节点在线数据)。
try { var client = new ApiHttpClient("http://star.newlifex.com:6600"); client.Token = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJTdGFyU2VydmVyIiwic3ViIjoiQTU4MDI2MUQiLCJleHAiOjE2NjE4MTQ5MzEsImlhdCI6MTY2MTgwNzczMSwianRpIjoiTnVxWFU0QkwifQ.LUxx_G4FQIn6m0GTZWOgyJDBKUVEJbOO4NzxGwofeEA"; var rs = await client.PostAsync<Object>("node/ping", new { ip = "127.0.0.1" }); XTrace.WriteLine(rs.ToJson(true)); } catch (ApiException aex) { XTrace.WriteLine("aex[{0}]={1}", aex.Code, aex.Message); }
运行效果如下
22:05:24.694 13 Y - { "time": 0, "serverTime": "2022-08-29 14:05:26 UTC", "period": 60, "token": null, "commands": null, "services": null }
直接返回了接口调用结果,而不再是403未验证异常。
到这里,细心的朋友会说,如果对方只提供appid+secret,要求先验证拿到访问令牌怎么办?
显然,我们可以先调用验证接口,得到令牌后赋值给ApiHttpClient的Token属性,后续所有请求自动带上令牌。
负载均衡&故障转移
ApiHttpClient支持负载均衡,把请求分摊到多节点,形成客户端实现的负载均衡。
var client = new ApiHttpClient("http://star.newlifex.com:6600,http://star2.newlifex.com:6600") { Timeout = 5_000, RoundRobin = true, ShieldingTime = 60 };
故障转移
构造函数入参可以填写逗号隔开的多个服务地址。默认使用第一个地址,访问出现网络故障时将会自动切换到后续地址,并在若干时间后探测回归第一地址。
该设置适用于绝大多数主备场景,双节点可轻松提供99.99%的高可用性。
负载均衡
设置RoundRobin=true后,打开轮询式负载均衡,每次请求切换下一个地址,均匀把流量分摊到多个节点上去。
一般不推荐使用负载均衡,因为单节点带业务也可以轻松实现2000tps的吞吐性能,满足绝大部分应用场景。
注册中心(分布式服务)
ApiHttpClient还是星尘分布式网络的基石,接入星尘网络后,可以根据服务名直接创建客户端,调用提供者的服务。
示例项目增加nuget引用 NewLife.Stardust,同时本机安装有 StarAgent,或者在StarFactory构造函数中指定星尘服务端地址 http://star.newlifex.com:6600。
var star = new StarFactory(); var client = await star.CreateForServiceAsync("StarWeb"); var rs = await client.InvokeAsync<Object>("cube/info"); XTrace.WriteLine(rs.ToJson(true));
执行效果如下
这里接入星尘注册中心后,按照服务名StarWeb创建客户端,该客户端从星尘注册中心取得三个提供者服务地址,直接调用相应业务接口。