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创建客户端,该客户端从星尘注册中心取得三个提供者服务地址,直接调用相应业务接口。