1. 程式人生 > >.NET Core開源API網關 – Ocelot中文文檔

.NET Core開源API網關 – Ocelot中文文檔

跟著 handler pan 集群 nes 註入 靈活 質量 keys

原文:.NET Core開源API網關 – Ocelot中文文檔

Ocelot是一個用.NET Core實現並且開源的API網關,它功能強大,包括了:路由、請求聚合、服務發現、認證、鑒權、限流熔斷、並內置了負載均衡器與Service Fabric、Butterfly Tracing集成。這些功能只都只需要簡單的配置即可完成,下面我們會對這些功能的配置一一進行說明。

介紹

簡單的來說Ocelot是一堆的asp.net core middleware組成的一個管道。當它拿到請求之後會用一個request builder來構造一個HttpRequestMessage發到下遊的真實服務器,等下遊的服務返回response之後再由一個middleware將它返回的HttpResponseMessage映射到HttpResponse上。

API網關—— 它是系統的暴露在外部的一個訪問入口。這個有點像代理訪問的家夥,就像一個公司的門衛承擔著尋址、限制進入、安全檢查、位置引導、等等功能。

Ocelot的基本使用

用一臺web service來host Ocelot,在這裏有一個json配置文件,裏面設置了所有對當前這個網關的配置。它會接收所有的客戶端請求,並路由到對應的下遊服務器進行處理,再將請求結果返回。而這個上下遊請求的對應關系也被稱之為路由。

技術分享圖片

集成Identity Server

當我們涉及到認證和鑒權的時候,我們可以跟Identity Server進行結合。當網關需要請求認證信息的時候會與Identity Server服務器進行交互來完成。

技術分享圖片

網關集群

只有一個網關是很危險的,也就是我們通常所講的單點,只要它掛了,所有的服務全掛。這顯然無法達到高可用,所以我們也可以部署多臺網關。當然這個時候在多臺網關前,你還需要一臺負載均衡器。

技術分享圖片

Consul 服務發現

在Ocelot已經支持簡單的負載功能,也就是當下遊服務存在多個結點的時候,Ocelot能夠承擔起負載均衡的作用。但是它不提供健康檢查,服務的註冊也只能通過手動在配置文件裏面添加完成。這不夠靈活並且在一定程度下會有風險。這個時候我們就可以用Consul來做服務發現,它能與Ocelot完美結合。

技術分享圖片

集成網關

在asp.net core 2.0裏通過nuget即可完成集成,或者命令行dotnet add package Ocelot以及通過vs2017 UI添加Ocelot nuget引用都可以。

Install-Package Ocelot

配置

我們需要添加一個.json的文件用來添加Ocelot的配置,以下是最基本的配置信息。

{
    "ReRoutes": [],
    "GlobalConfiguration": {
        "BaseUrl": "https://api.mybusiness.com"
    }
}

要特別註意一下BaseUrl是我們外部暴露的Url,比如我們的Ocelot運行在http://123.111.1.1的一個地址上,但是前面有一個 nginx綁定了域名http://api.jessetalk.cn,那這裏我們的BaseUrl就是 http://api.jessetalk.cn。

將配置文件加入ASP.NET Core Configuration

我們需要通過WebHostBuilder將我們添加的json文件添加進asp.net core的配置

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration( (hostingContext,builder) => {
            builder
            .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
            .AddJsonFile("Ocelot.json");
        })
        .UseStartup<Startup>()
        .Build();

配置依賴註入與中間件

在startup.cs中我們首先需要引用兩個命名空間

using Ocelot.DependencyInjection;
using Ocelot.Middleware;

接下來就是添加依賴註入和中間件

public void ConfigureServices(IServiceCollection services)
{
    services.AddOcelot();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseOcelot().Wait();
}

Ocelot功能介紹

通過配置文件可以完成對Ocelot的功能配置:路由、服務聚合、服務發現、認證、鑒權、限流、熔斷、緩存、Header頭傳遞等。在配置文件中包含兩個根節點:ReRoutes和GlobalConfiguration。ReRoutes是一個數組,其中的每一個元素代表了一個路由,我們可以針對每一個路由進行以上功能配置。下面是一個完整的路由配置

{
          "DownstreamPathTemplate": "/",
          "UpstreamPathTemplate": "/",
          "UpstreamHttpMethod": [
              "Get"
          ],
          "AddHeadersToRequest": {},
          "AddClaimsToRequest": {},
          "RouteClaimsRequirement": {},
          "AddQueriesToRequest": {},
          "RequestIdKey": "",
          "FileCacheOptions": {
              "TtlSeconds": 0,
              "Region": ""
          },
          "ReRouteIsCaseSensitive": false,
          "ServiceName": "",
          "DownstreamScheme": "http",
          "DownstreamHostAndPorts": [
              {
                  "Host": "localhost",
                  "Port": 51876,
              }
          ],
          "QoSOptions": {
              "ExceptionsAllowedBeforeBreaking": 0,
              "DurationOfBreak": 0,
              "TimeoutValue": 0
          },
          "LoadBalancer": "",
          "RateLimitOptions": {
              "ClientWhitelist": [],
              "EnableRateLimiting": false,
              "Period": "",
              "PeriodTimespan": 0,
              "Limit": 0
          },
          "AuthenticationOptions": {
              "AuthenticationProviderKey": "",
              "AllowedScopes": []
          },
          "HttpHandlerOptions": {
              "AllowAutoRedirect": true,
              "UseCookieContainer": true,
              "UseTracing": true
          },
          "UseServiceDiscovery": false
      }
  • Downstream是下遊服務配置
  • UpStream是上遊服務配置
  • Aggregates 服務聚合配置
  • ServiceName, LoadBalancer, UseServiceDiscovery 配置服務發現
  • AuthenticationOptions 配置服務認證
  • RouteClaimsRequirement 配置Claims鑒權
  • RateLimitOptions為限流配置
  • FileCacheOptions 緩存配置
  • QosOptions 服務質量與熔斷
  • DownstreamHeaderTransform頭信息轉發

我們接下來將對這些功能一一進行介紹和配置

路由

路由是API網關最基本也是最核心的功能、ReRoutes下就是由多個路由節點組成。

{
    "ReRoutes": [
    ]
}

而每一個路由由以下幾個基本信息組成:

下面這個配置信息就是將用戶的請求 /post/1 轉發到 localhost/api/post/1

{
    "DownstreamPathTemplate": "/api/post/{postId}",
    "DownstreamScheme": "https",
    "DownstreamHostAndPorts": [
            {
                "Host": "localhost",
                "Port": 80,
            }
        ],
    "UpstreamPathTemplate": "/post/{postId}",
    "UpstreamHttpMethod": [ "Get"]
}
  • DownstreamPathTemplate:下遊戲
  • DownstreamScheme:下遊服務http schema
  • DownstreamHostAndPorts:下遊服務的地址,如果使用LoadBalancer的話這裏可以填多項
  • UpstreamPathTemplate: 上遊也就是用戶輸入的請求Url模板
  • UpstreamHttpMethod: 上遊請求http方法,可使用數組

萬能模板

萬能模板即所有請求全部轉發,UpstreamPathTemplate 與DownstreamPathTemplate 設置為 “/{url}”

{
    "DownstreamPathTemplate": "/{url}",
    "DownstreamScheme": "https",
    "DownstreamHostAndPorts": [
            {
                "Host": "localhost",
                "Port": 80,
            }
        ],
    "UpstreamPathTemplate": "/{url}",
    "UpstreamHttpMethod": [ "Get" ]
}

萬能模板的優先級最低,只要有其它的路由模板,其它的路由模板則會優先生效。

上遊Host
上遊Host也是路由用來判斷的條件之一,由客戶端訪問時的Host來進行區別。比如當a.jesetalk.cn/users/{userid}和b.jessetalk.cn/users/{userid}兩個請求的時候可以進行區別對待。

{
    "DownstreamPathTemplate": "/",
    "DownstreamScheme": "https",
    "DownstreamHostAndPorts": [
            {
                "Host": "10.0.10.1",
                "Port": 80,
            }
        ],
    "UpstreamPathTemplate": "/",
    "UpstreamHttpMethod": [ "Get" ],
    "UpstreamHost": "a.jessetalk.cn"
}

Prioirty優先級
對多個產生沖突的路由設置優化級

{
    "UpstreamPathTemplate": "/goods/{catchAll}"
    "Priority": 0
}
{
    "UpstreamPathTemplate": "/goods/delete"
    "Priority": 1
}

比如你有同樣兩個路由,當請求/goods/delete的時候,則下面那個會生效。也就是說Prority是大的會被優先選擇。

路由負載均衡

當下遊服務有多個結點的時候,我們可以在DownstreamHostAndPorts中進行配置。

{
    "DownstreamPathTemplate": "/api/posts/{postId}",
    "DownstreamScheme": "https",
    "DownstreamHostAndPorts": [
            {
                "Host": "10.0.1.10",
                "Port": 5000,
            },
            {
                "Host": "10.0.1.11",
                "Port": 5000,
            }
        ],
    "UpstreamPathTemplate": "/posts/{postId}",
    "LoadBalancer": "LeastConnection",
    "UpstreamHttpMethod": [ "Put", "Delete" ]
}

LoadBalancer將決定負載均衡的算法

  • LeastConnection – 將請求發往最空閑的那個服務器
  • RoundRobin – 輪流發送
  • NoLoadBalance – 總是發往第一個請求或者是服務發現

在負載均衡這裏,我們還可以和Consul結合來使用服務發現,我們將在後面的小節中進行詳述。

請求聚合

即將多個API請求結果合並為一個返回。要實現請求聚合我們需要給其它參與的路由起一個Key。

{
    "ReRoutes": [
        {
            "DownstreamPathTemplate": "/",
            "UpstreamPathTemplate": "/laura",
            "UpstreamHttpMethod": [
                "Get"
            ],
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 51881
                }
            ],
            "Key": "Laura"
        },
        {
            "DownstreamPathTemplate": "/",
            "UpstreamPathTemplate": "/tom",
            "UpstreamHttpMethod": [
                "Get"
            ],
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 51882
                }
            ],
            "Key": "Tom"
        }
    ],
    "Aggregates": [
        {
            "ReRouteKeys": [
                "Tom",
                "Laura"
            ],
            "UpstreamPathTemplate": "/"
        }
    ]
}

當我們請求/的時候,會將/tom和/laura兩個結果合並到一個response返回

{"Tom":{"Age": 19},"Laura":{"Age": 25}}

需要註意的是:

  • 聚合服務目前只支持返回json
  • 目前只支持Get方式請求下遊服務
  • 任何下遊的response header並會被丟棄
  • 如果下遊服務返回404,聚合服務只是這個key的value為空,它不會返回404

有一些其它的功能會在將來實現

  • 下遊服務很慢的處理
  • 做一些像 GraphQL的處理對下遊服務返回結果進行處理
  • 404的處理

限流

對請求進行限流可以防止下遊服務器因為訪問過載而崩潰,這個功能就是我們的張善友張隊進添加進去的。非常優雅的實現,我們只需要在路由下加一些簡單的配置即可以完成。

"RateLimitOptions": {
    "ClientWhitelist": [],
    "EnableRateLimiting": true,
    "Period": "1s",
    "PeriodTimespan": 1,
    "Limit": 1
}
  • ClientWihteList 白名單
  • EnableRateLimiting 是否啟用限流
  • Period 統計時間段:1s, 5m, 1h, 1d
  • PeroidTimeSpan 多少秒之後客戶端可以重試
  • Limit 在統計時間段內允許的最大請求數量

在 GlobalConfiguration下我們還可以進行以下配置

"RateLimitOptions": {
  "DisableRateLimitHeaders": false,
  "QuotaExceededMessage": "Customize Tips!",
  "HttpStatusCode": 999,
  "ClientIdHeader" : "Test"
}
  • Http頭 X-Rate-Limit 和 Retry-After 是否禁用
  • QuotaExceedMessage 當請求過載被截斷時返回的消息
  • HttpStatusCode 當請求過載被截斷時返回的http status
  • ClientIdHeader 用來識別客戶端的請求頭,默認是 ClientId

服務質量與熔斷

熔斷的意思是停止將請求轉發到下遊服務。當下遊服務已經出現故障的時候再請求也是功而返,並且增加下遊服務器和API網關的負擔。這個功能是用的Pollly來實現的,我們只需要為路由做一些簡單配置即可

"QoSOptions": {
    "ExceptionsAllowedBeforeBreaking":3,
    "DurationOfBreak":5,
    "TimeoutValue":5000
}
  • ExceptionsAllowedBeforeBreaking 允許多少個異常請求
  • DurationOfBreak 熔斷的時間,單位為秒
  • TimeoutValue 如果下遊請求的處理時間超過多少則自如將請求設置為超時

緩存

Ocelot可以對下遊請求結果進行緩存 ,目前緩存的功能還不是很強大。它主要是依賴於CacheManager 來實現的,我們只需要在路由下添加以下配置即可

"FileCacheOptions": { "TtlSeconds": 15, "Region": "somename" }

Region是對緩存進行的一個分區,我們可以調用Ocelot的 administration API來移除某個區下面的緩存 。

認證

如果我們需要對下遊API進行認證以及鑒權服務的,則首先Ocelot 網關這裏需要添加認證服務。這和我們給一個單獨的API或者ASP.NET Core Mvc添加認證服務沒有什麽區別。

public void ConfigureServices(IServiceCollection services)
{
    var authenticationProviderKey = "TestKey";

    services.AddAuthentication()
        .AddJwtBearer(authenticationProviderKey, x =>
        {
        });
}

然後在ReRoutes的路由模板中的AuthenticationOptions進行配置,只需要我們的AuthenticationProviderKey一致即可。

"ReRoutes": [{
        "DownstreamHostAndPorts": [
            {
                "Host": "localhost",
                "Port": 51876,
            }
        ],
        "DownstreamPathTemplate": "/",
        "UpstreamPathTemplate": "/",
        "UpstreamHttpMethod": ["Post"],
        "ReRouteIsCaseSensitive": false,
        "DownstreamScheme": "http",
        "AuthenticationOptions": {
            "AuthenticationProviderKey": "TestKey",
            "AllowedScopes": []
        }
    }]

JWT Tokens

要讓網關支持JWT 的認證其實和讓API支持JWT Token的認證是一樣的

public void ConfigureServices(IServiceCollection services)
{
    var authenticationProviderKey = "TestKey";

    services.AddAuthentication()
        .AddJwtBearer(authenticationProviderKey, x =>
        {
            x.Authority = "test";
            x.Audience = "test";
        });

    services.AddOcelot();
}

Identity Server Bearer Tokens

添加Identity Server的認證也是一樣

public void ConfigureServices(IServiceCollection services)
{
    var authenticationProviderKey = "TestKey";
    var options = o =>
        {
            o.Authority = "https://whereyouridentityserverlives.com";
            o.ApiName = "api";
            o.SupportedTokens = SupportedTokens.Both;
            o.ApiSecret = "secret";
        };

    services.AddAuthentication()
        .AddIdentityServerAuthentication(authenticationProviderKey, options);

    services.AddOcelot();
}

Allowed Scopes

這裏的Scopes將從當前 token 中的 claims中來獲取,我們的鑒權服務將依靠於它來實現 。當前路由的下遊API需要某個權限時,我們需要在這裏聲明 。和oAuth2中的 scope意義一致。

鑒權

我們通過認證中的AllowedScopes 拿到claims之後,如果要進行權限的鑒別需要添加以下配置

"RouteClaimsRequirement": {
    "UserType": "registered"
}

當前請求上下文的token中所帶的claims如果沒有 name=”UserType” 並且 value=”registered” 的話將無法訪問下遊服務。

請求頭轉化

請求頭轉發分兩種:轉化之後傳給下遊和從下遊接收轉化之後傳給客戶端。在Ocelot的配置裏面叫做Pre Downstream Request和Post Downstream Request。目前的轉化只支持查找和替換。我們用到的配置主要是 UpstreamHeaderTransform 和 DownstreamHeaderTransform

Pre Downstream Request

"Test": "http://www.bbc.co.uk/, http://ocelot.com/"

比如我們將客戶端傳過來的Header中的 Test 值改為 http://ocelot.com/之後再傳給下遊

 "UpstreamHeaderTransform": {
    "Test": "http://www.bbc.co.uk/, http://ocelot.com/"
},

Post Downstream Request

而我們同樣可以將下遊Header中的Test再轉為 http://www.bbc.co.uk/之後再轉給客戶端。

"DownstreamHeaderTransform": {
    "Test": "http://www.bbc.co.uk/, http://ocelot.com/"
},

變量

在請求頭轉化這裏Ocelot為我們提供了兩個變量:BaseUrl和DownstreamBaseUrl。BaseUrl就是我們在GlobalConfiguration裏面配置的BaseUrl,後者是下遊服務的Url。這裏用301跳轉做一個示例如何使用這兩個變量。

默認的301跳轉,我們會返回一個Location的頭,於是我們希望將http://www.bbc.co.uk 替換為 http://ocelot.com,後者者網關對外的域名。

"DownstreamHeaderTransform": {
    "Location": "http://www.bbc.co.uk/, http://ocelot.com/"
},
 "HttpHandlerOptions": {
    "AllowAutoRedirect": false,
},

我們通過DownstreamHeaderTranfrom將下遊返回的請求頭中的Location替換為了網關的域名,而不是下遊服務的域名。所以在這裏我們也可以使用BaseUrl來做為變量替換。

"DownstreamHeaderTransform": {
    "Location": "http://localhost:6773, {BaseUrl}"
},
 "HttpHandlerOptions": {
    "AllowAutoRedirect": false,
},

當我們的下遊服務有多個的時候,我們就沒有辦法找到前面的那個http://localhost:6773,因為它可能是多個值。所以這裏我們可以使用DownstreamBaseUrl。

"DownstreamHeaderTransform": {
    "Location": "{DownstreamBaseUrl}, {BaseUrl}"
},
 "HttpHandlerOptions": {
    "AllowAutoRedirect": false,
},

Claims轉化

Claims轉化功能可以將Claims中的值轉化到請求頭、Query String、或者下遊的Claims中,對於Claims的轉化,比較特殊的一點是它提供了一種對字符串進行解析的方法。舉個例子,比如我們有一個sub的claim。這個claims的 name=”sub” value=”usertypevalue|useridvalue”,實際上我們不會弄這麽復雜的value,它是拼接來的,但是我們為了演示這個字符串解析的功能,所以使用了這麽一個復雜的value。

Ocelot為我們提供的功能分為三段,第一段是Claims[sub],很好理解[] 裏面是我們的claim的名稱。第二段是 > 表示對字符串進行拆分, 後面跟著拆分完之後我們要取的那個數組裏面的某一個元素用 value[index]來表示,取第0位元素也可以直接用value。第三段也是以 > 開頭後面跟著我們的分隔符,在我們上面的例子分隔符是 |

所以在這裏如果我們要取 usertype這個claim就會這樣寫: Claims[sub] > value[0] > |

Claim取到之後我們如果要放到請求頭、QueryString、以及Claim當中對應有以下三個配置。

Claims to Claims

"AddClaimsToRequest": {
    "UserType": "Claims[sub] > value[0] > |",
    "UserId": "Claims[sub] > value[1] > |"
}

Claims to Headers

"AddHeadersToRequest": {
    "CustomerId": "Claims[sub] > value[1] > |"
}

這裏我們還是用的上面那個 sub = usertypevalue|useridvalue 的claim來進行處理和轉化。

Claims to Query String

"AddQueriesToRequest": {
    "LocationId": "Claims[LocationId] > value",
}

這裏沒有進行分隔,所以直接取了value。

Consul服務發現

由於Consul服務發現更多的是Consul的安裝、配置、以及使用,所以本小節內容將由另一篇文章來進行詳細介紹,歡迎關註。

.NET Core開源API網關 – Ocelot中文文檔