.Net Castle.Core 实现 HTTP RPC 功能,方便接口开发

之前的文章写了如何自己实现动态代理,我这边是采用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

我们项目大体如下结构:

.Net Castle.Core 实现 HTTP RPC 功能,方便接口开发

我们先要实现的是HTTP RPC库

此库也简单,实现完之后,就是 具体的测试了。

我这边通过一个mvc的项目和一个webapi的项目,然后,改成统一的api接口地址方式进行测试。

测试两遍,验证效果

HTTP RPC 核心原理

项目结构如下:

.Net Castle.Core 实现 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

.Net Castle.Core 实现 HTTP RPC 功能,方便接口开发

mvc

.Net Castle.Core 实现 HTTP RPC 功能,方便接口开发

总结

搞代码确实挺耗时间的,验证过程中,也有各种问题,不过还是搞定了。

加油,每天进步一点点。

代码地址

https://github.com/kesshei/HttpRpc.git

https://gitee.com/kesshei/HttpRpc.git