之前的文章写了如何自己实现动态代理,我这边是采用emit技术来实现的类的动态代理。
但是,项目要直接用到生产环境的话,还是得测试和运行一段时间,稳定后,才可以上正式环境。
这个时候,采用成熟的动态代理组件就显得很实用了。.
我这里就采用 Castle.Core 库来实现,当然,也有一些别的可以用,大致用法一致。
为啥实现HTTP RPC功能
首先,在日常对接API接口的过程中,大部分都需要客户端独自实现API接口的一个一个实现,如果遇到变动修改就有点多,比如微信公众号API对接等等。
那么有没有简单的方式,我想HTTP RPC方式就可以解决这个问题,它把请求的参数从 web api方式 直接函数话了。
如果是自己的服务端,还可以直接给注释信息,可以动态生成这样的接口给客户端,客户端直接就用了。
也是很便利的,在这种场景下。
废话不多说,开干。
Castle 的动态代理杂用
用着也非常的简单
大致需要三个步骤:
第一个步骤,实现 Castle.DynamicProxy 的 IInterceptor 接口
public class Interceptor<T> : IInterceptor
{
private Type InterfaceType { get; set; } = typeof(T);
public void Intercept(IInvocation invocation)
{
var Namespace = InterfaceType.Namespace;
var ClassName = InterfaceType.Name;
var MethodName = invocation.Method.Name;
}
}
这样就获取了类的相关信息。
第二步,实现 Castle.DynamicProxy 的 IInterceptorSelector 接口
/// <summary>
/// 过滤器
/// </summary>
public class InterceptorSelector : IInterceptorSelector
{
/// <summary>
///
/// </summary>
public InterceptorSelector() { }
/// <summary>
/// 只需要处理public的方法,其他类型的不处理
/// </summary>
public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
{
if (method.IsPublic)
{
return interceptors;
}
else
{
return null;
}
}
}
第三步,具体的使用
ProxyGenerator ProxyGenerator = new ProxyGenerator();
var result = ProxyGenerator.CreateInterfaceProxyWithoutTarget<T>(new ProxyGenerationOptions() { Selector = new InterceptorSelector()}, new Interceptor<T>());
是不是挺简单的。
这样我们就对它的使用有一个简单的了解了。
HTTP RPC
我们项目大体如下结构:

我们先要实现的是HTTP RPC库
此库也简单,实现完之后,就是 具体的测试了。
我这边通过一个mvc的项目和一个webapi的项目,然后,改成统一的api接口地址方式进行测试。
测试两遍,验证效果
HTTP RPC 核心原理
项目结构如下:

Interceptor.cs
public class Interceptor<T> : IInterceptor
{
private Type InterfaceType { get; set; } = typeof(T);
private BaseTunnel ServerTunnel;
public Interceptor(BaseTunnel ServerTunnel)
{
this.ServerTunnel = ServerTunnel;
}
public void Intercept(IInvocation invocation)
{
var action = $"{InterfaceType.Namespace}-{InterfaceType.Name}-{invocation.Method.Name}";
RPCServer.APIActions.TryGetValue(action, out var methed);
var parameters = new Dictionary<string, object>();
var p = invocation.Method.GetParameters();
for (int i = 0; i < p.Length; i++)
{
parameters.Add(p[i].Name, invocation.Arguments[i]);
}
var response = ServerTunnel.Invoke(new TunnelRequest() { RemoteMethed = methed.path, Parameters = parameters, ReturnType = invocation.Method.ReturnType, HttpRequestMethod = methed.httpMethod });
if (response.StateCode != 200)
{
throw new Exception(response.Msg);
}
invocation.ReturnValue = response.Body;
}
}
此代码是项目的调用方法的动态代理逻辑部分。
Tunnel
TunnelType 类型,目前就实现了http,实际上,也可以根据此逻辑,封装socket,websocket,webserver 等等的通讯方式来实现
TunnelFactory
/// <summary>
/// 通道工厂
/// </summary>
public static class TunnelFactory
{
public static BaseTunnel CreateTunnel(string ServerApiUrl, TunnelType tunnelType = TunnelType.HTTP)
{
BaseTunnel BaseTunnel = null;
switch (tunnelType)
{
case TunnelType.HTTP:
{
BaseTunnel = new HTTPTunnelServer(ServerApiUrl);
}
break;
}
return BaseTunnel;
}
}
实现的话,就在工厂这里自行实现
HttpRequest
之前是用HttpWebRequest实现的功能,现在,直接通过高级的 HttpClient来实现,效率肯定会提升好多。
/// <summary>
/// http请求
/// </summary>
private static (string data, int httpState, string errMsg) HttpRequest(string url, string postData = null, HttpRequestMethodType methodType = HttpRequestMethodType.GET, string ContentType = null, Encoding encoding = null)
{
string Result = null;
int httpState = 500;
string errMsg = "请求失败!";
encoding ??= Encoding.UTF8;
try
{
HttpRequestMessage httpRequestMessage = new HttpRequestMessage();
httpRequestMessage.RequestUri = new Uri(url);
if (methodType == HttpRequestMethodType.GET)
{
httpRequestMessage.Method = HttpMethod.Get;
}
else
{
httpRequestMessage.Method = HttpMethod.Post;
httpRequestMessage.Content = new StringContent(postData, Encoding.UTF8, "application/json");
}
var HttpResponseMessage = httpClient.Send(httpRequestMessage);
httpState = (int)HttpResponseMessage.StatusCode;
if (HttpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
Result = HttpResponseMessage.Content.ReadAsStringAsync().Result;
errMsg = "";
}
else
{
errMsg = $"状态码不对:{httpState}";
}
}
catch (Exception ex)
{
errMsg = ex.Message;
}
return (Result, httpState, errMsg);
}
搞到这个地方的时候,本地代理把我搞的费了好长时间,建议测试的时候,关闭代理。
当然,如果没有问题,更好。
HTTPTunnelServer.cs
/*
默认按照json格式来
*/
public class HTTPTunnelServer : BaseTunnel
{
private string ServerApiUrl;
public HTTPTunnelServer(string ServerApiUrl) : base(TunnelType.HTTP)
{
this.ServerApiUrl = ServerApiUrl;
}
public override TunnelResponse Invoke(TunnelRequest request)
{
var souse = new TaskCompletionSource<TunnelResponse>();
Task.Run(() =>
{
try
{
var serverUrl = $"{ServerApiUrl}/{request.RemoteMethed}";
(string data, int httpState, string errMsg) result;
if (request.HttpRequestMethod == HttpRequestMethodType.GET)
{
result = HttpHelper.Get(serverUrl, request.Parameters);
}
else
{
result = HttpHelper.Post(serverUrl, request.Parameters.FirstOrDefault().Value);
}
if (result.httpState != 200)
{
throw new Exception(result.errMsg);
}
var body = Process(result.data, request);
souse.SetResult(new TunnelResponse() { StateCode = result.httpState, Body = body, Msg = result.errMsg });
}
catch (Exception e)
{
souse.SetResult(new TunnelResponse() { StateCode = 500, Body = null, Msg = e.Message.ToString() });
}
});
return souse.Task.Result;
}
private object Process(string data, TunnelRequest request)
{
if (string.IsNullOrWhiteSpace(data) || request.ReturnType == typeof(void))
{
return null;
}
if (request.ReturnType == typeof(string))
{
return data;
}
return JsonHelper.ToObject(data, request.ReturnType);
}
}
RPCServer.cs
下面就是调用服务的地方
/// <summary>
/// PRC服务
/// </summary>
public static class RPCServer
{
private static ProxyGenerator ProxyGenerator = new ProxyGenerator();
private static InterceptorSelector InterceptorSelector = new InterceptorSelector();
static RPCServer()
{
InitServer();//初始化加载
}
/// <summary>
/// 方法与远程方法的映射
/// </summary>
public static ConcurrentDictionary<string, (string path, HttpRequestMethodType httpMethod)> APIActions = new();
/// <summary>
/// 创建默认代理
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target"></param>
/// <returns></returns>
public static T Proxy<T>(BaseTunnel BaseTunnel = null) where T : class
{
if (BaseTunnel == null)
{
throw new ArgumentNullException(nameof(BaseTunnel));
}
var inter = new Interceptor<T>(BaseTunnel);
return ProxyGenerator.CreateInterfaceProxyWithoutTarget<T>(new ProxyGenerationOptions() { Selector = InterceptorSelector }, inter);
}
/// <summary>
/// 获取服务
/// </summary>
/// <returns></returns>
public static T GetServerce<T>(string ServerApiUrl, BaseTunnel BaseTunnel = null) where T : class
{
BaseTunnel ??= TunnelFactory.CreateTunnel(ServerApiUrl, TunnelType.HTTP);
return Proxy<T>(BaseTunnel);
}
public static void InitServer()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.GetInterface(nameof(IApiServer)) != null && type.IsInterface)
{
var typeName = type.Name;
var typeAttribute = type.GetCustomAttributes(false).Where(t => t is APIServerAttribute).Select(t => t as APIServerAttribute).ToList().FirstOrDefault();
var TypeNames = new List<string>();
if (typeAttribute != null && typeAttribute.GetActions()?.Any() == true)
{
TypeNames.AddRange(typeAttribute.GetActions());
}
else
{
TypeNames.Add(typeName);
}
foreach (var methodInfo in type.GetMethods())
{
var first = methodInfo.GetCustomAttributes(false).Where(t => t is APIServerAttribute).Select(t => t as APIServerAttribute).FirstOrDefault();
if (first != null && first.GetActions()?.Any() == true)
{
var path = APIServerAttribute.GetAction(TypeNames, first.GetActions());
var value = (path, first.HttpRequestMethod);
APIActions.AddOrUpdate($"{type.Namespace}-{typeName}-{methodInfo.Name}", value, (k, v) => v = (path, first.HttpRequestMethod));
}
}
}
}
}
}
}
接口部分要做的事情
/// <summary>
/// 服务端API
/// </summary>
[APIServer("Home")]
public interface TestApi : IApiServer
{
/// <summary>
/// 获取结果
/// </summary>
[APIServer(HttpRequestMethodType.GET, "GetResult")]
public string GetResult(string name);
/// <summary>
/// 获取结果
/// </summary>
[APIServer(HttpRequestMethodType.POST, "Result")]
public TestModel Result(TestModel testModel);
}
简单不,实际上就是继承一个接口 IApiServer,然后,按照指定方式,重写一个接口即可。
服务端接口大致样子
mvc
public IActionResult GetResult(string name)
{
return Content($"服务端返回:{name}");
}
public IActionResult Result([FromBody] TestModel testModel)
{
if (testModel == null)
{
return Json(new TestModel() { Msg = "对象获取为null" });
}
testModel.Msg = "服务器";
return Json(testModel);
}
webapi
[HttpGet("GetResult")]
public string GetResult(string name)
{
return $"服务端返回:{name}";
}
[HttpPost("Result")]
public TestModel Result(TestModel testModel)
{
testModel.Msg = "服务器";
return testModel;
}
客户端代码
static void Main(string[] args)
{
Console.Title = "HTTP RPC Test by 蓝创精英团队";
var TestApi = RPCServer.GetServerce<TestApi>("http://localhost:5000");
//http://localhost:10234/home/getresult?name=123
var getresult = TestApi.GetResult(DateTime.Now.ToString());
Console.WriteLine($"Get请求返回:{getresult}");
//http://localhost:10234/home/result
var result = TestApi.Result(new TestModel() { Name = "456", Age = 18, Address = "蓝创精英团队", Msg = "" });
Console.WriteLine($"Post请求返回:{result.ToJsonStr()}");
Console.WriteLine("测试 HttpRpc 完毕!");
Console.ReadLine();
}
调用方式,依旧如此简单,方便。
测试
测试的时候,先启动 测试服务 WebApi 或者 WebServer 然后,启动 WebAPITest 测试即可。
测试结果
Webapi

mvc

总结
搞代码确实挺耗时间的,验证过程中,也有各种问题,不过还是搞定了。
加油,每天进步一点点。
代码地址
https://github.com/kesshei/HttpRpc.git
https://gitee.com/kesshei/HttpRpc.git