首先,让我们简单了解下什么是API网关?

      API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。
    API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

其次,我们了解下Ocelot框架

 Ocelot的目标是使用.NET运行微服务/面向服务架构,我们需要一个统一的入口进入我们的服务,提供监控、鉴权、负载均衡等机制,也可以通过编写中间件的形式,来扩展Ocelot的功能。  Ocelot是一堆特定顺序的中间件。

 Ocelot框架内部集成了IdentityServer和Consul(服务注册发现),还引入了Polly来处理进行故障处理,关于Polly,可以在这篇博客中了解更多《已被.NET基金会认可的弹性和瞬态故障处理库Polly介绍》

 Ocelot开源地址:https://github.com/TomPallister/Ocelot

接下来,我们就针对Ocelot的具体用法展开介绍。

这里使用的Ocelot版本为2.0,.Net Core版本2.0

1、搭建Asp.net Core WebApi项目,引用Ocelot.dll。

net core体系-API-1Ocelot-(3)项目实战 随笔 第1张

Nuget控制台,执行Ocelot安装。

1 PM>Install-Package Ocelot

  

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 GET https: //api .nuget.org /v3/registration3-gz-semver2/ocelot/index .json    OK https: //api .nuget.org /v3/registration3-gz-semver2/ocelot/index .json 104 毫秒    GET https: //api .nuget.org /v3/registration3-gz-semver2/ocelot/page/1 .5.0-unstable0134 /2 .0.0.json    OK https: //api .nuget.org /v3/registration3-gz-semver2/ocelot/page/1 .5.0-unstable0134 /2 .0.0.json 108 毫秒 正在还原 J:\Demo\RichCodeBox\APIGatewayApp\APIGatewayApp.csproj 的包...    GET https: //api .nuget.org /v3-flatcontainer/ocelot/index .json    OK https: //api .nuget.org /v3-flatcontainer/ocelot/index .json 476 毫秒    GET https: //api .nuget.org /v3-flatcontainer/ocelot/2 .0.0 /ocelot .2.0.0.nupkg    OK https: //api .nuget.org /v3-flatcontainer/ocelot/2 .0.0 /ocelot .2.0.0.nupkg 125 毫秒    GET https: //api .nuget.org /v3-flatcontainer/identityserver4 .accesstokenvalidation /index .json    GET https: //api .nuget.org /v3-flatcontainer/cachemanager .core /index .json    GET https: //api .nuget.org /v3-flatcontainer/cachemanager .microsoft.extensions.configuration /index .json    GET https: //api .nuget.org /v3-flatcontainer/cachemanager .microsoft.extensions.logging /index .json    GET https: //api .nuget.org /v3-flatcontainer/consul/index .json    GET https: //api .nuget.org /v3-flatcontainer/polly/index .json    GET https: //api .nuget.org /v3-flatcontainer/identityserver4/index .json    OK https: //api .nuget.org /v3-flatcontainer/identityserver4 .accesstokenvalidation /index .json 133 毫秒    GET https: //api .nuget.org /v3-flatcontainer/identityserver4 .accesstokenvalidation /2 .1.0 /identityserver4 .accesstokenvalidation.2.1.0.nupkg    OK https: //api .nuget.org /v3-flatcontainer/cachemanager .microsoft.extensions.logging /index .json 286 毫秒    OK https: //api .nuget.org /v3-flatcontainer/polly/index .json 287 毫秒    OK https: //api .nuget.org /v3-flatcontainer/identityserver4 .accesstokenvalidation /2 .1.0 /identityserver4 .accesstokenvalidation.2.1.0.nupkg 160 毫秒    GET https: //api .nuget.org /v3-flatcontainer/cachemanager .microsoft.extensions.logging /1 .1.1 /cachemanager .microsoft.extensions.logging.1.1.1.nupkg

  2、修改Startup程序。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /// <summary>       ///       /// </summary>       /// <param name="environment"></param>       public  Startup(IHostingEnvironment environment)       {           var  builder =  new  Microsoft.Extensions.Configuration.ConfigurationBuilder();           builder.SetBasePath(environment.ContentRootPath)                  .AddJsonFile( "appsettings.json" false , reloadOnChange:  true )                  .AddJsonFile($ "appsettings.{environment.EnvironmentName}.json" , optional:  false , reloadOnChange:  true )                  .AddJsonFile( "configuration.json" , optional:  false , reloadOnChange:  true )                  .AddEnvironmentVariables();               Configuration = builder.Build();       }

  

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 /// <summary> ///modified:配置 /// </summary> public  IConfigurationRoot Configuration {  get ; }   /// <summary> /// 配置服务 /// </summary> /// <param name="services"></param> public  void  ConfigureServices(IServiceCollection services) {      Action<ConfigurationBuilderCachePart> settings = (x) =>      {          x.WithMicrosoftLogging(log =>          {              log.AddConsole(LogLevel.Debug);            }).WithDictionaryHandle();      };      services.AddOcelot(Configuration, settings);      //services.AddMvc(); }   /// <summary> /// 配置Ocelot /// </summary> /// <param name="app"></param> /// <param name="env"></param> public  async  void  Configure(IApplicationBuilder app, IHostingEnvironment env) {      //if (env.IsDevelopment())      //{      //    app.UseDeveloperExceptionPage();      //}      await app.UseOcelot();      //app.UseMvc(); } /// <summary> /// 入口程序 /// </summary> /// <param name="args"></param> public  static  void  Main( string [] args) {      IWebHostBuilder builder =  new  WebHostBuilder();      builder.ConfigureServices(s =>      {          s.AddSingleton(builder);      });      builder.UseKestrel()             .UseContentRoot(Directory.GetCurrentDirectory())             .UseIISIntegration()             .UseStartup<Startup>()             .UseApplicationInsights();      var  host = builder.Build();      host.Run(); }

  

3、配置Ocelot。

我们新建一个名为configuration的json文件,配置如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 {    "ReRoutes" : [      {        "DownstreamPathTemplate" "/api/values" ,        "DownstreamScheme" "http" ,        "DownstreamHost" "localhost" ,        "DownstreamPort" : 8801,        "UpstreamPathTemplate" "/api/values" ,        "UpstreamHttpMethod" : [  "Get"  ],        "QoSOptions" : {          "ExceptionsAllowedBeforeBreaking" : 3,          "DurationOfBreak" : 10,          "TimeoutValue" : 5000        },        "HttpHandlerOptions" : {          "AllowAutoRedirect" false ,          "UseCookieContainer" false        },        "AuthenticationOptions" : {          }      },      {        "DownstreamPathTemplate" "/api/product" ,        "DownstreamScheme" "http" ,        "DownstreamPort" : 8802,        "DownstreamHost" "localhost" ,        "UpstreamPathTemplate" "/api/product" ,        "UpstreamHttpMethod" : [  "Get"  ],        "QoSOptions" : {          "ExceptionsAllowedBeforeBreaking" : 3,          "DurationOfBreak" : 10,          "TimeoutValue" : 5000        },        "AuthenticationOptions" : {          }      }    ],    "GlobalConfiguration" : {      "RequestIdKey" "OcRequestId" ,      "AdministrationPath" "/admin"    } }

  

在这里,我们配置了两个服务,端口分别为8801和8802的。

Ocelot支持负载均衡(提供轮询、最少访问)。Ocelot大部分功能,都可以通过中间件来完成,也可以实现和重写中间件。

Ocelot原理非常简单,这里的配置文件,体现了上游请求和下游服务间的映射关系,你可以理解为,上游是客户端直接调用的URL ,下游,则是对应我们开发的服务。

4、新增两个WebApi项目,分别为APIUserServiec和APIProductService。

API服务 端口(Port)
APIUserServiec 8801
APIProductService 8802

解决方案如下:

net core体系-API-1Ocelot-(3)项目实战 随笔 第2张

5、配置VS启动端口:

net core体系-API-1Ocelot-(3)项目实战 随笔 第3张

依次类推,分别设置端口。

6、启动项目。

配置多个项目启动。

net core体系-API-1Ocelot-(3)项目实战 随笔 第4张

F5启动项目。

net core体系-API-1Ocelot-(3)项目实战 随笔 第5张

再通过API网关,访问商品服务:http://localhost:5000/api/product。

net core体系-API-1Ocelot-(3)项目实战 随笔 第6张

常见问题:

首次在启动API网关时,触发以下错误。

Sequence contains no matching element

net core体系-API-1Ocelot-(3)项目实战 随笔 第7张

 

根据错误详细信息,可知原因是由于系统调用AddIdentityServer方法时,触发异常。

net core体系-API-1Ocelot-(3)项目实战 随笔 第8张

刚开始,怀疑是配置文件configuration.json文件配置导致的,Ocelot2.0版,采用官方配置仍然触发该异常,由此排除这种可能。接下来,直接从github上克隆源代码,查看。

net core体系-API-1Ocelot-(3)项目实战 随笔 第9张

找到触发错误的地方,

net core体系-API-1Ocelot-(3)项目实战 随笔 第10张
private static void AddIdentityServer(this IServiceCollection services, IIdentityServerConfiguration identityServerConfiguration, IConfigurationRoot configurationRoot) 
        {
            services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
            services.TryAddSingleton<IHashMatcher, HashMatcher>();
            var identityServerBuilder = services
                .AddIdentityServer(o => {
                    o.IssuerUri = "Ocelot";
                })
                .AddInMemoryApiResources(Resources(identityServerConfiguration))
                .AddInMemoryClients(Client(identityServerConfiguration))
                .AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();

            //todo - refactor a method so we know why this is happening
            var whb = services.First(x => x.ServiceType == typeof(IWebHostBuilder));//这个地方触发了错误
            var urlFinder = new BaseUrlFinder((IWebHostBuilder)whb.ImplementationInstance);
            var baseSchemeUrlAndPort = urlFinder.Find();
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
                .AddIdentityServerAuthentication(o =>
                {
                    var adminPath = configurationRoot.GetValue("GlobalConfiguration:AdministrationPath", string.Empty);
                    o.Authority = baseSchemeUrlAndPort + adminPath;
                    o.ApiName = identityServerConfiguration.ApiName;
                    o.RequireHttpsMetadata = identityServerConfiguration.RequireHttps;
                    o.SupportedTokens = SupportedTokens.Both;
                    o.ApiSecret = identityServerConfiguration.ApiSecret;
                });

                //todo - refactor naming..
                if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
                {
                    identityServerBuilder.AddDeveloperSigningCredential();
                }
                else
                {
                    //todo - refactor so calls method?
                    var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
                    identityServerBuilder.AddSigningCredential(cert);
                }
net core体系-API-1Ocelot-(3)项目实战 随笔 第11张
var whb = services.First(x => x.ServiceType == typeof(IWebHostBuilder));

这就代码触发了错误,是由于表达式条件不成立,导致First发生异常,这就简单了,我们修改Main函数,

把这句代码:

 var builder = new WebHostBuilder();

改成:

IWebHostBuilder builder = new WebHostBuilder();

这样,就解决了问题,API网关启动成功。另外,官方例子https://github.com/TomPallister/Ocelot/blob/develop/test/Ocelot.ManualTest/Program.cs中,写法是正确的,大家自己写的时候,注意把IWebHostBuilder换成var会引发错误。

这样,使用Ocelot框架搭建API网关的工作已经完成,尝试通过访问API网关,来访问下游的服务。

此实例源码:https://gitee.com/lichaoqiang/RichCodeBox.git

欢迎大家一起研究探讨,开启你的微服务之路。

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄