Asp.Net Core API網關Ocelot


首先,讓我們簡單了解下什么是API網關?

      API網關是一個服務器,是系統的唯一入口。從面向對象設計的角度看,它與外觀模式類似。API網關封裝了系統內部架構,為每個客戶端提供一個定制的API。它可能還具有其它職責,如身份驗證、監控、負載均衡、緩存、請求分片與管理、靜態響應處理。
    API網關方式的核心要點是,所有的客戶端和消費端都通過統一的網關接入微服務,在網關層處理所有的非業務功能。通常,網關也是提供REST/HTTP的訪問API。服務端通過API-GW注冊和管理服務。

其次,我們了解下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。

Nuget控制台,執行Ocelot安裝。

PM>Install-Package Ocelot

  

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程序。

   /// <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();
        }

  

        /// <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文件,配置如下:

{
  "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

解決方案如下:

5、配置VS啟動端口:

依次類推,分別設置端口。

6、啟動項目。

配置多個項目啟動。

F5啟動項目。

再通過API網關,訪問商品服務:http://localhost:5000/api/product。

常見問題:

首次在啟動API網關時,觸發以下錯誤。

Sequence contains no matching element

 

根據錯誤詳細信息,可知原因是由於系統調用AddIdentityServer方法時,觸發異常。

剛開始,懷疑是配置文件configuration.json文件配置導致的,Ocelot2.0版,采用官方配置仍然觸發該異常,由此排除這種可能。接下來,直接從github上克隆源代碼,查看。

找到觸發錯誤的地方,

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);
                }
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

歡迎大家一起研究探討,開啟你的微服務之路。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM