Net Core API網關Ocelot


Net Core API網關Ocelot

Ocelot在github的地址 https://github.com/TomPallister/Ocelot , 非常給力的是在課程當天完成了.NET Core 2.0的升級,升級過程請看https://github.com/TomPallister/Ocelot/issues/114 。昨天我花了半小時就把我的另外一個POC項目Nanofabric   https://github.com/geffzhang/NanoFabric 升級到了.NET Core 2.0, 這個POC項目也是我的分享的項目的原型,可以這么說.NET Core 2.0 8月份正式發布,經過3 個月時間的發展,社區生態已經都已經做好了准備,開發新項目可以采用.NET Core 2,Ocelot 是一個集成社區中眾多優秀開源項目的代表。

image

image

業務的飛速發展,產生的非常多的對外的服務接口,分散在組織的各個地方需要進行統一的管理,而且我們的環境是linux和windows的混合環境,我們的目標是統一在公司的Linux環境,.NET Core對於.NET 技術團隊來說是一個非常棒的技術,而且.NET Core本身的架構非常好,性能就更好了。

image

image

這里列出了Ocelot目前支持的特性:

  • Routing
    • 用戶可以指定上游請求之間的映射,並將其轉發到下游服務上的不同URL。
  • Service Discovery
    • Ocelot可以查看你的服務發現,並找到它應該轉發下游請求的服務。它可以在這些服務之間進行負載平衡。.
  • Authentication using IdentityServer
    • 您可以將端點標記為已認證,並使用IdentityServer承載標記對您的用戶進行身份驗證.
  • Authorisation using Claims
    • 如果使用 bearer tokens, 可以使用 claims 標記特定 endpoints是授權的
  • Claims Transformation
    • Ocelot提供了一種語法來轉換給下游請求,並將聲明數據添加到標題,URL參數,其他聲明等等
  • Quality of service
    • Retries, circuit breaker, timeouts etc.
  • Request / Correlation Ids
  • Caching
  • Logging
  • Custom Middleware

更詳細的內容參看文檔 https://github.com/TomPallister/Ocelot/wiki 

上面介紹了Ocelot的功能特性,接下來我們進入介紹Ocelot 的實現原理剖析,核心是是ASP.NET Core Middleware 以及 ASP.NET Core DependencyInjection:

image

ASP.NET Core 傳統的ASP.NET 在架構上有很大的改進,更加的模塊化,下圖形象的說明了他們之間區別,Application 和 Middleware 是平等的,比如ASP.NET Core MVC也是一個Middleware,通過Middleware這樣的結構我們非常容易的擴展我們的應用程序。

image

Ocelot就是使用Middleware來完成網關的所有功能,每個小功能就是一個Middleware,具體可以看代碼 https://github.com/TomPallister/Ocelot/blob/develop/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs ,Ocelot 是如何把各個Middleware串起來協同完成一個API網關的功能。 asp.net core 非常巧妙的設計,把Middleware抽象成了一個委托RequestDelegate, ASP.NET Core 的每個 Request 都會經過每個所注冊的 Middleware,Response 也是逐一回傳,以先進后出的方式處理每一個封包:

image

具體內容參考: ASP.NET Core HTTP 管道中的那些事兒 和 如何一秒鍾從頭構建一個 ASP.NET Core 中間件, 我們在Middleware的編程過程中需要關注HttpContext 以及管道的注冊者和構建者 ApplicationBuilder。

 ASP.NET Core 教學 - Middleware - 運作方式

ASP.NET Core 使用了大量的 DI (Dependency Injection) 設計,同樣我們在Ocelot的設計中也使用了大量的DI設計,具體參看源碼https://github.com/TomPallister/Ocelot/blob/develop/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs 

注冊 Service 有分三種方式:

  • Transient  每次注入時,都重新 new 一個新的實體。
  • Scoped    每個 Request 都重新 new 一個新的實體。
  • Singleton 程序啟動后會 new 一個實體。也就是運行期間只會有一個實體。

下面這張圖來自https://blog.johnwu.cc/article/asp-net-core-dependency-injection.html ,形象的演示了對象生命周期。

ASP.NET Core 教學 - Dependency Injection - 運作方式動畫

  • A 為 Singleton
  • B 為 Scoped
  • C 為 Transient

上面介紹完了Ocelot開發的基本原理,目前Ocelot 由17 個Middleware 來完成,在每個Middleware的內部實現上還有涉及到很多業務的知識,本篇文章先不做展開,后續寫具體的文章詳細解析。接下來我們來說說如何自定義擴展,在我們的項目中主要在三個方面進行了擴展:

1、自定義擴展API 接口驗證

image

Ocelot 默認支持基於IdentityServer4的認證,需要自定義認證,可以參考 https://github.com/TomPallister/Ocelot/pull/110,添加自定義的驗證,但是.net core 2.0 認證部分基本上重寫了。

2、自定義擴展下游通訊協議

image

Ocelot 默認支持Http的通訊,在我們的實際項目中有很多老的服務是RPC調用,使用的是私有的Relay通訊框架,在API網關上需要做協議轉換,自動將Http的請求轉換成Relay的tcp通訊。

3、自定義管理控制台

image

ocelot 有管理API,可以基於管理API 做自定義的管理控制台,github 有個 https://github.com/dbarkwell/Ocelot.ConfigEditor,這個項目實現了asp.net core mvc 的在線編輯路由。

首先,讓我們簡單了解下什么是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安裝。

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

解決方案如下:

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