热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【.NETCore项目实战统一认证平台】第十六章网关篇Ocelot集成RPC服务

【.NETCore项目实战-统一认证平台】开篇及目录索引一、什么是RPCRPC是“远程调用(RemoteProcedureCall)”的一个名称的缩写,并不是任何规范化的协议,也不

【.NET Core项目实战-统一认证平台】开篇及目录索引

一、什么是RPC

RPC是“远程调用(Remote Procedure Call)”的一个名称的缩写,并不是任何规范化的协议,也不是大众都认知的协议标准,我们更多时候使用时都是创建的自定义化(例如Socket,Netty)的消息方式进行调用,相比http协议,我们省掉了不少http中无用的消息内容。因此很多系统内部调用仍然采用自定义化的RPC调用模式进行通信,毕竟速度和性能是内网的关键指标之一,而标准化和语义无关性在外网中举足轻重。所以,为何API网关无法工作在RPC上,因为它没有一个像HTTP/HTTPS那样的通用标准。

二、CzarRpc简介

CzarRpc是作者基于Dotnetty实现的RPC通讯框架,参考了SurgingTars.Net优秀设计,目前正在内部使用中,下面就CzarRpc调用方式做一个简单介绍,测试结构如下:
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

1、服务接口

新建一个Czar.Rpc.Common类库,首先需要引用Czar.RpcNuget包。

Install-Package Czar.Rpc

然后定义测试接口IHelloRpc.cs,也是目前支持的调用方式。

using Czar.Rpc.Attributes;
using Czar.Rpc.Exceptions;
using Czar.Rpc.Metadata;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Czar.Rpc.Common
{
    /// 
    /// 测试Rpc实体
    /// 
    [BusinessExceptionInterceptor]
    [CzarRpc("Demo.Rpc.Hello")]
    public interface IHelloRpc: IRpcBaseService
    {
        string Hello(int no, string name);

        void HelloHolder(int no, out string name);

        Task<string> HelloTask(int no, string name);

        ValueTask<string> HelloValueTask(int no, string name);

        [CzarOneway]
        void HelloOneway(int no, string name);

        Task TestBusinessExceptionInterceptor();

        DemoModel HelloModel(int D1, string D2, DateTime D3);

        Task HelloModelAsync(int D1, string D2, DateTime D3);

        DemoModel HelloSendModel(DemoModel model);

        DemoModel HelloSendModelParm(string name,DemoModel model);

        List HelloSendModelList(List model);
    }
    public class DemoModel
    {
        /// 
        /// 测试1
        /// 
        public int T1 { get; set; }

        /// 
        /// 测试2
        /// 
        public string T2 { get; set; }

        /// 
        /// 测试3
        /// 
        public DateTime T3 { get; set; }

        public ChildModel Child { get; set; }
    }

    public class ChildModel
    {
        public string C1 { get; set; }
    }
}

2.服务端

新建一个控制台程序Czar.Rpc.Server,然后实现服务接口,因为都是测试数据,所以就随意实现了方法。

HelloRpcServer.cs

using Czar.Rpc.Exceptions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Net;
using Czar.Rpc.Common;

namespace Demo.Rpc.Server
{
    public class HelloRpcServer: IHelloRpc
    {
        public EndPoint CzarEndPoint { get; set; }

        public string Hello(int no, string name)
        {
            string result = $"{no}: Hi, {name}";
            Console.WriteLine(result);
            return result + " callback";
        }

        public void HelloHolder(int no, out string name)
        {
            name = no.ToString() + " out";
        }

        public void HelloOneway(int no, string name)
        {
            /*
                耗时操作
            */
            Console.WriteLine($"From oneway - {no}: Hi, {name}");
        }

        public Task<string> HelloTask(int no, string name)
        {
            return Task.FromResult(Hello(no, name));
        }

        public ValueTask<string> HelloValueTask(int no, string name)
        {
            return new ValueTask<string>(Hello(no, name));
        }

        public Task TestBusinessExceptionInterceptor()
        {
            throw new BusinessException()
            {
                CzarCode = "1",
                CzarMessage = "test"
            };
        }

        public DemoModel HelloModel(int D1, string D2, DateTime D3)
        {
            return new DemoModel()
            {
                T1 = D1 + 1,
                T2 = D2 + "2",
                T3 = D3.AddDays(1)
            };
        }

        public async Task HelloModelAsync(int D1, string D2, DateTime D3)
        {
            return await Task.FromResult(
               new DemoModel()
               {
                   T1 = D1 + 1,
                   T2 = D2 + "77777",
                   T3 = D3.AddDays(1)
               }
                );
        }

        public DemoModel HelloSendModel(DemoModel model)
        {
            model.T1 = model.T1 + 10;
            model.T2 = model.T2 + "11";
            model.T3 = model.T3.AddDays(12);
            return model;
        }

        public DemoModel HelloSendModelParm(string name, DemoModel model)
        {
            model.T1 = model.T1 + 10;
            model.T2 = model.T2 + "11";
            model.T3 = model.T3.AddDays(12);
            if (model.Child != null)
            {
                model.Child.C1 = name+"说:"+ model.Child.C1;
            }
            return model;
        }

        public List HelloSendModelList(List model)
        {
            return model.Select(t => new DemoModel() { T1=t.T1+10,T2=t.T2+"13",T3=t.T3.AddYears(1),Child=t.Child }).ToList();
        }
    }
}

然后启动服务端监听。

class Program
    {
        static void Main(string[] args)
        {
            var host = new HostBuilder()
                .ConfigureHostConfiguration(i => i.AddJsonFile("CzarConfig.json"))
                .ConfigureLogging((hostContext, configLogging) =>
                {
                    configLogging.AddConsole();
                })
                .UseCodec()
                .UseLibuvTcpHost()
                .UseProxy()
                .UseConsoleLifetime()
                .Build();

            host.RunAsync().Wait();
        }
    }

启用外部使用CzarConfig.json的配置文件,注意需要设置成始终复制。

{
  "CzarHost": {
    "Port": 7711, //监听端口
    "QuietPeriodSeconds": 2,  //退出静默时间   DotNetty特性
    "ShutdownTimeoutSeconds": 2, //关闭超时时间 DotNetty特性
    "IsSsl": "false",  //是否启用 SSL, 客户端需要保持一致
    "PfxPath": "cert/datasync.pfx", //证书
    "PfxPassword": "123456"  //证书密钥 
  }
}

到此服务器端搭载完成。

3、客户端

新建客户端控制台程序Czar.Rpc.Client,然后配置Rpc调用信息。

{
  "CzarHost": {
    "ProxyEndPoint": true, //是否启用动态服务地址,就是指定服务端IP
    "IsSsl": "false", //是否启用SSL
    "PfxPath": "cert/datasync.pfx", //证书
    "PfxPassword": "123456", //证书密钥
    "ClientConfig": {   //客户端配置
      "Demo.Rpc.Hello": {   //对应服务[CzarRpc("Demo.Rpc.Hello")] 值
        "Host": "127.0.0.1", //服务端IP 如果ProxyEndPoint=false 时使用
        "Port": 7711, //服务端端口 如果ProxyEndPoint=false 时使用
        "Timeout": 10, //调用超时时间
        "WriterIdleTimeSeconds";30  //空闲超时时间,默认为30秒,非内网环境建议设置成5分钟内。
      }
    }
  }
}

现在开始启用客户端信息。

class Program
    {
        public static IServiceProvider service;
        public static IConfiguration config;
        static async Task Main(string[] args)
        {
            try
            {
                var builder = new ConfigurationBuilder();
                cOnfig= builder.AddJsonFile("CzarConfig.json").Build();
                
              service = new ServiceCollection()
                    .AddSingleton(config)
                    .AddLogging(j => j.AddConsole())
                    .AddLibuvTcpClient(config)
                    .AddProxy()
                    .BuildDynamicProxyServiceProvider();

                var rpc = service.GetRequiredService();
                //使用的内部指定的服务器地址
                rpc.CzarEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7711);
                var result = string.Empty;
                
                string t = "基本调用";
                result = rpc.Hello(18, t);
                Console.WriteLine(result);

                result = "无返回结果";
                rpc.HelloHolder(1, out result);
                Console.WriteLine(result);
                result = await rpc.HelloTask(2, "异步任务");
                Console.WriteLine(result);
                result = "单向";
                rpc.HelloOneway(3, "单向调用");
                Console.WriteLine(result);
                result = await rpc.HelloValueTask(4, "ValueTask任务");
                Console.WriteLine(result);

                var modelResult = rpc.HelloModel(5, "返回实体", DateTime.Now);
                Console.WriteLine($"{modelResult.T1} {modelResult.T2} {modelResult.T3.ToLongDateString()}");


                var modelResult1 = await rpc.HelloModelAsync(6, "返回Task实体", DateTime.Now);
                Console.WriteLine($"{modelResult1.T1} {modelResult1.T2} {modelResult1.T3.ToLongDateString()}");

                var mm = new DemoModel()
                {
                    T1 = 7,
                    T2 = "传实体返回实体",
                    T3 = DateTime.Now,
                    Child = new ChildModel()
                    {
                        C1 = "子类1"
                    }
                };
                var model2 = rpc.HelloSendModel(mm);
                Console.WriteLine($"{model2.T1} {model2.T2} {model2.T3.ToLongDateString()}  {model2.Child.C1}");

                var list = new List();
                var mm1 = new DemoModel()
                {
                    T1 = 8,
                    T2 = "传List返回List",
                    T3 = DateTime.Now,
                    Child = new ChildModel()
                    {
                        C1 = "子类2"
                    }
                };
                var mm3 = new DemoModel()
                {
                    T1 = 9,
                    T2 = "传List返回List",
                    T3 = DateTime.Now,
                    Child = new ChildModel()
                    {
                        C1 = "子类3"
                    }
                };
                list.Add(mm1);
                list.Add(mm3);
                var list3 = rpc.HelloSendModelList(list);
                Console.WriteLine($"{list3[0].T1} {list3[0].T2} {list3[0].T3.ToLongDateString()} {list3[0].Child?.C1}");


                var mm4 = new DemoModel()
                {
                    T1 = 9,
                    T2 = "HelloSendModelParm",
                    T3 = DateTime.Now,
                    Child = new ChildModel()
                    {
                        C1 = "子类4"
                    }
                };
                var dd = rpc.HelloSendModelParm("HelloSendModelParm", mm4);
                Console.WriteLine($"{dd.T1} {dd.T2} {dd.T3.ToLongDateString()}  {dd.Child.C1}");

                //异常调用
                await rpc.TestBusinessExceptionInterceptor();
            }
            catch (BusinessException e)
            {
                Console.WriteLine($"CzarCode:{e.CzarCode} CzarMessage:{e.CzarMessage}");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            Console.ReadLine();
        }
    }

现在整个RPC调用搭建完毕,然后分别启动服务器端和客户端,就可以看到屏幕输出内容如下。

客户端输出:
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

服务器端输出:
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

至此整个CzarRpc的基本使用已经介绍完毕,感兴趣的朋友可以自行测试。

三、Ocelot增加RPC支持

有了CzarRpc的通讯框架后,现在在Ocelot上实现Rpc功能简直易如反掌,现在开始添加我们的Rpc中间件,也让我们扩展的网关灵活起来。

还记得我介绍网关篇时添加中间件的步骤吗?如果不记得的可以先回去回顾下。

首先如何让网关知道这个后端调用是http还是Rpc呢?这时应该会想到Ocelot路由配置里的DownstreamScheme,可以在这里判断我们定义的是http还是rpc即可。同时我们希望之前定义的所有中间件都生效,最后一步请求时如果配置下端路由rpc,使用rpc调用,否则使用http调用,这样可以重复利用之前所有的中间件功能,减少重复开发。

在之前的开发的自定义限流和自定义授权中间件开发中,我们知道开发完的中间件放到哪里使用,这里就不介绍原理了,直接添加到BuildCzarOcelotPipeline里如下代码。

public static OcelotRequestDelegate BuildCzarOcelotPipeline(this IOcelotPipelineBuilder builder,
            OcelotPipelineConfiguration pipelineConfiguration)
        {
           
            // 注册一个全局异常
            builder.UseExceptionHandlerMiddleware();

            // 如果请求是websocket使用单独的管道
            builder.MapWhen(cOntext=> context.HttpContext.WebSockets.IsWebSocketRequest,
                app =>
                {
                    app.UseDownstreamRouteFinderMiddleware();
                    app.UseDownstreamRequestInitialiser();
                    app.UseLoadBalancingMiddleware();
                    app.UseDownstreamUrlCreatorMiddleware();
                    app.UseWebSocketsProxyMiddleware();
                });

            // 添加自定义的错误管道
            builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);

            //使用自定义的输出管道
            builder.UseCzarResponderMiddleware();

            // 下游路由匹配管道
            builder.UseDownstreamRouteFinderMiddleware();

            //增加自定义扩展管道
            if (pipelineConfiguration.MapWhenOcelotPipeline != null)
            {
                foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline)
                {
                    builder.MapWhen(pipeline);
                }
            }

            // 使用Http头部转换管道
            builder.UseHttpHeadersTransformationMiddleware();

            // 初始化下游请求管道
            builder.UseDownstreamRequestInitialiser();

            // 使用自定义限流管道
            builder.UseRateLimiting();

            //使用请求ID生成管道
            builder.UseRequestIdMiddleware();

            //使用自定义授权前管道
            builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware);

            //根据请求判断是否启用授权来使用管道
            if (pipelineConfiguration.AuthenticatiOnMiddleware== null)
            {
                builder.UseAuthenticationMiddleware();
            }
            else
            {
                builder.Use(pipelineConfiguration.AuthenticationMiddleware);
            }

            //添加自定义限流中间件 2018-11-18 金焰的世界
            builder.UseCzarClientRateLimitMiddleware();

            //添加自定义授权中间件  2018-11-15 金焰的世界
            builder.UseAhphAuthenticationMiddleware();

            //启用自定义的认证之前中间件
            builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);

            //是否使用自定义的认证中间件
            if (pipelineConfiguration.AuthorisatiOnMiddleware== null)
            {
                builder.UseAuthorisationMiddleware();
            }
            else
            {
                builder.Use(pipelineConfiguration.AuthorisationMiddleware);
            }

            // 使用自定义的参数构建中间件
            builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);

            // 使用负载均衡中间件
            builder.UseLoadBalancingMiddleware();

            // 使用下游地址创建中间件
            builder.UseDownstreamUrlCreatorMiddleware();

            // 使用缓存中间件
            builder.UseOutputCacheMiddleware();

            //判断下游的是否启用rpc通信,切换到RPC处理
            builder.MapWhen(cOntext=> context.DownstreamReRoute.DownstreamScheme.Equals("rpc", StringComparison.OrdinalIgnoreCase), app =>
            {
                app.UseCzarRpcMiddleware();
            });

            //使用下游请求中间件
            builder.UseCzaHttpRequesterMiddleware();

            return builder.Build();
        }

这里是在最后请求前判断使用的下游请求方式,如果DownstreamScheme使用的rpc,就使用rpc中间件处理。

Rpc处理的完整逻辑是,如何从http请求中获取想要解析的参数,这里需要设置匹配的优先级,目前设计的优先级为。

1、首先提取路由参数,如果匹配上就是用路由参数名称为key,值为value,按顺序组成第一批参数。

2、提取query参数,如有有值按顺序组成第二批参数。

3、如果非Get请求,提取body内容,如果非空,组成第三批参数

4、从配置库里提取rpc路由调用的服务名称和函数名称,以及是否单向调用。

5、按照获取的数据进行rpc调用并等待返回。

看了上面的设计是不是思路很清晰了呢?

1、rpc路由表设计

CREATE TABLE AhphReRouteRpcConfig
(
    RpcId int IDENTITY(1,1) NOT NULL,
    ReRouteId int,                      //路由表主键
    ServantName varchar(100) NOT NULL,  //调用的服务名称
    FuncName varchar(100) NOT NULL,     //调用的方法名称
    IsOneway bit NOT NULL               //是否单向调用
)

2、提取远程调用方法

根据上游路由获取远程调用的配置项目

public interface IRpcRepository
{
    /// 
    /// 根据模板地址获取RPC请求方法
    /// 
    /// 上游模板
    /// 
    Task GetRemoteMethodAsync(string UpUrl);
}

public class SqlServerRpcRepository : IRpcRepository
    {
        private readonly CzarOcelotConfiguration _option;
        public SqlServerRpcRepository(CzarOcelotConfiguration option)
        {
            _option = option;
        }

        /// 
        /// 获取RPC调用方法
        /// 
        /// 
        /// 
        public async Task GetRemoteMethodAsync(string UpUrl)
        {
            using (var cOnnection= new SqlConnection(_option.DbConnectionStrings))
            {
                string sql = @"select T4.* from AhphGlobalConfiguration t1 inner join AhphConfigReRoutes T2 on
T1.AhphId=t2.AhphId inner join AhphReRoute T3 on T2.ReRouteId=T3.ReRouteId 
INNER JOIN AhphReRouteRpcConfig T4 ON T3.ReRouteId=T4.ReRouteId
where IsDefault=1 and T1.InfoStatus=1 AND T3.InfoStatus=1 AND UpstreamPathTemplate=@URL";
                var result = await connection.QueryFirstOrDefaultAsync(sql, new { URL = UpUrl });
                return result;
            }
        }
    }

3、重写返回结果

由于rpc调用后是返回的Json封装的信息,需要解析成对应的HttpContent。

using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace Czar.Gateway.Rpc
{
    public class RpcHttpContent : HttpContent
    {
        private string result;

        public RpcHttpContent(string result)
        {
            this.result = result;
        }

        public RpcHttpContent(object result)
        {
            this.result = Newtonsoft.Json.JsonConvert.SerializeObject(result);
        }

        protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
        {
            var writer = new StreamWriter(stream);
            await writer.WriteAsync(result);
            await writer.FlushAsync();
        }

        protected override bool TryComputeLength(out long length)
        {
            length = result.Length;
            return true;
        }
    }
}

4、rpc中间件逻辑处理

有了前面的准备信息,现在基本可以完成逻辑代码的开发了,详细的中间件代码如下。

using Czar.Gateway.Errors;
using Czar.Rpc.Clients;
using Ocelot.Logging;
using Ocelot.Middleware;
using Ocelot.Responses;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;

namespace Czar.Gateway.Rpc.Middleware
{
    public class CzarRpcMiddleware : OcelotMiddleware
    {
        private readonly OcelotRequestDelegate _next;
        private readonly IRpcClientFactory _clientFactory;
        private readonly ICzarRpcProcessor _czarRpcProcessor;
        public CzarRpcMiddleware(OcelotRequestDelegate next, IRpcClientFactory clientFactory,
            IOcelotLoggerFactory loggerFactory, ICzarRpcProcessor czarRpcProcessor) : base(loggerFactory.CreateLogger())
        {
            _next = next;
            _clientFactory = clientFactory;
            _czarRpcProcessor = czarRpcProcessor;
        }

        public async Task Invoke(DownstreamContext context)
        {
            var httpStatusCode = HttpStatusCode.OK;
            var _param = new List<object>();
            //1、提取路由参数
            var tmpInfo = context.TemplatePlaceholderNameAndValues;
            if (tmpInfo != null && tmpInfo.Count > 0)
            {
                foreach (var tmp in tmpInfo)
                {
                    _param.Add(tmp.Value);
                }
            }
            //2、提取query参数
            foreach (var _q in context.HttpContext.Request.Query)
            {
                _param.Add(_q.Value.ToString());
            }
            //3、从body里提取内容
            if (context.HttpContext.Request.Method.ToUpper() != "GET")
            {
                context.DownstreamRequest.Scheme = "http";
                var requert = context.DownstreamRequest.ToHttpRequestMessage();
                if (requert.Content!=null)
                {
                    var json = "{}";
                    json = await requert.Content.ReadAsStringAsync();
                    _param.Add(json);
                }
            }
            //从缓存里提取
            var req = await _czarRpcProcessor.GetRemoteMethodAsync(context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue);
            if (req != null)
            {
                req.Parameters = _param.ToArray();
                var result = await _clientFactory.SendAsync(req, GetEndPoint(context.DownstreamRequest.Host, context.DownstreamRequest.Port));
                OkResponse httpResponse;
                if (result.CzarCode == Czar.Rpc.Utilitys.RpcStatusCode.Success)
                {
                    httpRespOnse= new OkResponse(new RpcHttpContent(result.CzarResult?.ToString()));
                }
                else
                {
                    httpRespOnse= new OkResponse(new RpcHttpContent(result));
                }
                context.HttpContext.Response.COntentType= "application/json";
                context.DownstreamRespOnse= new DownstreamResponse(httpResponse.Data, httpStatusCode, httpResponse.Data.Headers, "OK");
            }
            else
            {//输出错误
                var error = new InternalServerError($"请求路由 {context.HttpContext.Request.Path}未配置后端转发");
                Logger.LogWarning($"{error}");
                SetPipelineError(context, error);
            }
        }
        private EndPoint GetEndPoint(string ipaddress, int port)
        {
            if (IPAddress.TryParse(ipaddress, out IPAddress ip))
            {
                return new IPEndPoint(ip, port);
            }
            else
            {
                return new DnsEndPoint(ipaddress, port);
            }
        }
    }
}

5、启动Rpc客户端配置

目前Rpc的客户端配置我们还没启动,只需要在AddCzarOcelot中添加相关注入即可。

var service = builder.First(x => x.ServiceType == typeof(IConfiguration));
var cOnfiguration= (IConfiguration)service.ImplementationInstance;
//Rpc应用
builder.AddSingleton();
builder.AddSingleton();
builder.AddLibuvTcpClient(configuration);

6、配置客户端

最后别忘了配置Rpc客户端信息是否启用证书信息,为了配置信息的内容。

{
  "CzarHost": {
    "ProxyEndPoint": true,
    "IsSsl": "false",
    "PfxPath": "cert/datasync.pfx",
    "PfxPassword": "bl123456",
    "ClientConfig": {
      "Demo.Rpc.Hello": {
        "Host": "127.0.0.1",
        "Port": 7711,
        "Timeout": 20
      }
    }
  }
}

现在让网关集成Rpc功能全部配置完毕。

四、网关Rpc功能测试

本次测试我在原有的网关基础上,增加不同类型的Rpc调用,就按照不同维度测试Rpc调用功能,本次测试案例是建立在Czar.Rpc 服务端基础上,正好可以测试。
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

1、测试路由参数

请求路径/hello/{no}/{name},调用的服务端方法Hello,传入的两个参数分别是no ,name

可以在服务器端添加断点调试,发现确实接收到请求信息,并正常返回,下面是PostMan测试结果。
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

2、使用Query方式传递参数

请求路径/rpc/query,调用的服务端方法还是Hello,参数分别是no ,name
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

3、使用Post方式传递Json

请求路径/rpc/body,调用的服务器方法是HelloSendModel
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

4、混合参数使用

请求的路径/rpc/bodyparm/{name},调用的服务器端方法是HelloSendModelParm
【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

所有的返回结果可自行调试测试,发现都能达到预期结果。

同时此网关还是支持默认的http请求的,这里就不一一测试了。

五、总结

本篇我介绍了什么是Rpc,以及Czar.Rpc的基本使用,然后使用Czar.Rpc框架集成到我们基于Ocelot扩展网关中,并实现了不能方式的Rpc调用,可以在几乎不改变现有流程的情况下很快速的集成进去,这也是Ocelot开发框架的魅力所在。

如果在使用过程中有什么问题或建议,可以在.NET Core项目实战交流群(637326624)中联系作者。

最后本文涉及的所有的源代码可在https://github.com/jinyancao/czar.gateway中下载预览。


推荐阅读
  • 本文介绍了在Ubuntu 11.10 x64环境下安装Android开发环境的步骤,并提供了解决常见问题的方法。其中包括安装Eclipse的ADT插件、解决缺少GEF插件的问题以及解决无法找到'userdata.img'文件的问题。此外,还提供了相关插件和系统镜像的下载链接。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • linuxmint20.3 安装anaconda、换源及创建新环境
    本文用来记录linuxmint20.3安装anaconda、换源及创建新环境的过程,同时记录一些相关命令目录anaconda安装相关命令创建新环境显示问题 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文讨论了在ASP中创建RazorFunctions.cshtml文件时出现的问题,即ASP.global_asax不存在于命名空间ASP中。文章提供了解决该问题的代码示例,并详细解释了代码中涉及的关键概念,如HttpContext、Request和RouteData等。通过阅读本文,读者可以了解如何解决该问题并理解相关的ASP概念。 ... [详细]
  • 精讲代理设计模式
    代理设计模式为其他对象提供一种代理以控制对这个对象的访问。代理模式实现原理代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色ÿ ... [详细]
  • docker安装到基本使用
    记录docker概念,安装及入门日常使用Docker安装查看官方文档,在&quot;Debian上安装Docker&quot;,其他平台在&quot;这里查 ... [详细]
  • Abp+MongoDb改造默认的审计日志存储位置
    一、背景在实际项目的开发当中,使用AbpZero自带的审计日志功能写入效率比较低。其次审计日志数据量中后期十分庞大,不适合与业务数据存放在一起。所以我们可以重新实现A ... [详细]
author-avatar
手机用户2602927805
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有