产品及架构介绍
定义
命名空间:TouchSocket.WebApi
程序集:TouchSocket.WebApi.dll
一、说明
WebApi是通用的Rpc调用,与编程语言无关,与操作系统无关。其路由机制模仿AspNetCore,可实现很多路由机制。但是因为http兼容性错综复杂,所以目前TouchSocket的WebApi仅支持GET、POST函数。使用体验接近于AspNetCore。
二、特点
- 高性能,100个客户端,每个客户端10w次调用,仅用时17s。
- 全异常反馈 。
- 支持大部分路由规则。
- 支持js、Android等调用。
三、定义服务
在服务器端中新建一个类,继承于RpcServer类(或实现IRpcServer),然后在该类中写公共方法,并用WebApi属性标签标记。
public partial class ApiServer : RpcServer
{
private readonly ILog m_logger;
public ApiServer(ILog logger)
{
this.m_logger = logger;
}
[Router("[api]/[action]ab")]//此路由会以"/Server/Sumab"实现
[Router("[api]/[action]")]//此路由会以"/Server/Sum"实现
[WebApi(HttpMethodType.GET)]
public int Sum(int a, int b)
{
return a + b;
}
[WebApi(HttpMethodType.POST)]
public int TestPost(MyClass myClass)
{
return myClass.A + myClass.B;
}
/// <summary>
/// 使用调用上下文,响应文件下载。
/// </summary>
/// <param name="callContext"></param>
[WebApi(HttpMethodType.GET)]
public Task<string> DownloadFile(IWebApiCallContext callContext, string id)
{
if (id == "rrqm")
{
callContext.HttpContext.Response.FromFile(@"D:\System\Windows.iso", callContext.HttpContext.Request);
return Task.FromResult("ok");
}
return Task.FromResult("id不正确。");
}
/// <summary>
/// 使用调用上下文,获取实际请求体。
/// </summary>
/// <param name="callContext"></param>
[WebApi(HttpMethodType.POST)]
[Router("[api]/[action]")]
public Task<string> PostContent(IWebApiCallContext callContext)
{
if (callContext.Caller is ISocketClient socketClient)
{
this.m_logger.Info($"IP:{socketClient.IP},Port:{socketClient.Port}");//获取Ip和端口
}
if (callContext.HttpContext.Request.TryGetContent(out var content))
{
this.m_logger.Info($"共计:{content.Length}");
}
return Task.FromResult("ok");
}
}
public class MyClass
{
public int A { get; set; }
public int B { get; set; }
}
四、启动服务器
更多注册Rpc的方法请看注册Rpc服务
var service = new HttpService();
service.Setup(new TouchSocketConfig()
.SetListenIPHosts(7789)
.ConfigureContainer(a =>
{
a.AddConsoleLogger();
a.AddRpcStore(store =>
{
store.RegisterServer<ApiServer>();//注册服务
#if DEBUG
//下列代码,会生成客户端的调用代码。
var codeString = store.GetProxyCodes("WebApiProxy", typeof(WebApiAttribute));
File.WriteAllText("../../../WebApiProxy.cs", codeString);
#endif
});
})
.ConfigurePlugins(a =>
{
a.UseCheckClear();
a.UseWebApi();
//此插件是http的兜底插件,应该最后添加。作用是当所有路由不匹配时返回404.且内部也会处理Option请求。可以更好的处理来自浏览器的跨域探测。
a.UseDefaultHttpServicePlugin();
}));
service.Start();
Console.WriteLine("以下连接用于测试webApi");
Console.WriteLine($"使用:http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20");
五、规则
5.1 Get规则
使用Get进行请求时,服务方法可以声明多个参数,但是每个参数都必须是基础类型或者字符串类型。
[WebApi(HttpMethodType.GET)]
public int Get(int a)
{
return a;
}
[WebApi(HttpMethodType.GET)]
public int Sum(int a, int b)
{
return a + b;
}
如果想要获取实际请求的调用上下文(在上下文中可以获取IP,端口等信息),可以将第一个参数声明为IWebApiCallContext
类型。
/// <summary>
/// 使用调用上下文,响应文件下载。
// </summary>
/// <param name="callContext"></param>
[WebApi(HttpMethodType.GET)]
public Task<string> DownloadFile(IWebApiCallContext callContext, string id)
{
if (id == "rrqm")
{
callContext.HttpContext.Response.FromFile(@"D:\System\Windows.iso", callContext.HttpContext.Request);
return Task.FromResult("ok");
}
return Task.FromResult("id不正确。");
}
5.2 Post规则
使用Post
进行请求时,服务方法可以声明多个参数,但是当参数是基础类型或者字符串类型时,它也会来源于Query
参数,同时,有且只有当最后一个参数为其他类型时,才会从Body
解析。
以下参数依然来自Query,Body为空也可以。
[WebApi(HttpMethodType.Post)]
public int Sum(int a, int b)
{
return a + b;
}
以下参数,前两个来自Query,MyClass将从Body解析。
[WebApi(HttpMethodType.Post)]
public int Sum(int a, int b, MyClass myClass)
{
return a + b;
}
如果想要获取实际请求的调用上下文(在上下文中可以获取IP,端口等信息),可以将第一个参数声明为IWebApiCallContext
类型。
5.3 路由规则
框架的路由规则比较简单,默认情况下,以服务的名称+方法名称作为路由。
例如下列:
将会以/ApiServer/Sum
为请求url(不区分大小写)。
public class ApiServer : RpcServer
{
[WebApi(HttpMethodType.GET)]
public int Sum(int a, int b)
{
return a + b;
}
}
当需要定制路由消息时,可用[api]
替代服务名,[action]
替代方法名。
例如下列:
将会以user/ApiServer/test/Sum
为请求url(不区分大小写)。
[Router("/user/[api]/test/[action]")]
public class ApiServer : RpcServer
{
[WebApi(HttpMethodType.GET)]
public int Sum(int a, int b)
{
return a + b;
}
}
Router
特性不仅可以用于服务,也可以用于方法。而且可以多个使用。
六、调用服务
6.1 直接调用
直接调用,则是不使用任何代理,直接Call RPC,使用比较简单,浏览器也能直接调用实现。
【Url请求】
http://127.0.0.1:7789/ApiServer/Sum?a=10&b=20
6.2 内置HttpClient调用
内置WebApi的客户端和大家所熟识的有一些差距,TouchSocket的WebApi使用的是先连接,后请求的逻辑。请求时,先标记GET/POST的函数。如果是GET,则必须留空URL,如果是POST,则只写URL即可。
private static WebApiClient CreateWebApiClient()
{
var client = new WebApiClient();
client.Connect("127.0.0.1:7789");
Console.WriteLine("连接成功");
return client;
}
var client = CreateWebApiClient();
int sum1 = client.InvokeT<int>("GET:/ApiServer/Sum?a={0}&b={1}", null, 10, 20);
Console.WriteLine($"Get调用成功,结果:{sum1}");
int sum2 = client.InvokeT<int>("POST:/ApiServer/TestPost", null, new MyClass() { A = 10, B = 20 });
Console.WriteLine($"Post调用成功,结果:{sum2}");
6.3 Dotnet自带HttpClient调用
Dotnet自带HttpClient则是通过连接池的方式访问。详情看HttpClient
private static WebApiClientSlim CreateWebApiClientSlim()
{
var client = new WebApiClientSlim(new System.Net.Http.HttpClient());
client.Setup(new TouchSocketConfig()
.SetRemoteIPHost("http://127.0.0.1:7789")
.ConfigurePlugins(a =>
{
}));
return client;
}
var client = CreateWebApiClientSlim();
int sum1 = client.InvokeT<int>("GET:/ApiServer/Sum?a={0}&b={1}", null, 10, 20);
Console.WriteLine($"Get调用成功,结果:{sum1}");
int sum2 = client.InvokeT<int>("POST:/ApiServer/TestPost", null, new MyClass() { A = 10, B = 20 });
Console.WriteLine($"Post调用成功,结果:{sum2}");
按照微软建议,HttpClient应该保证整个程序中单实例使用,所以可以在WebApiClientSlim
构造函数中传入已存在的对象。
WebApiClientSlim
仅在net6.0+,net481可用。