Ocelot使用與設定路由Routing

以往清泉發表於2023-03-18

一、安裝Ocelot

在程式包管理器控制檯輸入以下命令安裝Ocelot

Install-Package Ocelot

二、新建兩個專案

我們新建兩個.Net Core WebAPI專案如下:

 

直接就是最初始化的專案,只是我們在ExternalGateway專案中安裝Ocelot,並且新增一個ocelot.json檔案(也可以新增多個配置檔案然後合併),檔案內容如下:

{
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:5000"
  },
  "Routes": [
    {
      "DownstreamPathTemplate": "/{everything}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/api/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }
  ]
}

然後注入Ocelot的服務和配置請求管道

builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
builder.Services.AddOcelot(); //...... app.UseOcelot().Wait();

 然後我們執行兩個專案就能透過ExternalGateway專案地址訪問uthServer的地址

 

 三、Routing的引數配置說明

1、路由

    {
      "DownstreamPathTemplate": "/{everything}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/api/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post" ]
    }

其中DownstreamPathTemplateDownstreamSchemeDownstreamHostAndPorts定義了請求將被轉發到的URL。

引數DownstreamHostAndPorts是一個集合定義了請求轉到任何下游服務的主機和埠,一般是單個即可,但是如果存在負載均衡到下游服務那就需要填寫多個,並進行相關的負載均衡的配置。

引數UpstreamPathTemplate是標識用於給定下游請求的DownstreamPathTemplate對應的URL

引數UpstreamHttpMethod便於區分不同的HTTP的請求到相同的URL,可以設定為空允許全部的請求。

Ocelot中可以以{something}的形式為模板中的變數新增佔位符,但是該佔位符變數必須同時出現在DownstreamPathTemplateUpstreamPathTemplate的上下游配置中,Ocelot會嘗試進行佔位符值的替換。

預設請求路徑的配置是不區分大小寫的,如果需要修改透過以下引數配置:

"RouteIsCaseSensitive": true

2、全部捕獲

Ocelot中還支援捕獲全部路徑的路由,使用者可以指定他們想要匹配的所有流量。

像是如下的配置,所有的請求都將被代理。但是佔位符{url}的名稱不重要,可以使用任何名稱。

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

但是全部捕獲的優先順序是最低的,如果存在其他配置,將會優先於此匹配。

3、優先順序

可以透過Priorty引數來這是匹配上游請求路徑的優先順序,如下配置的情況下請求地址為/goods/delete的時候優先匹配/goods/{catchAll},因為0是最低的優先順序,Priorty越大優先順序越高。

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

4、上游主機

引數UpstreamHost允許我們設定該路由的上游主機,配置後僅當請求頭的主機為我們的配置值,才會匹配該路由配置。如果沒有配置UpstreamHost那就是任何主機頭都可以。

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

如上述程式碼僅當主機頭為somedomain.com的請求,才會匹配上述路由。如果存在兩個相同的路由配置,但是一個設定了UpstreamHost一個沒有設定,這樣會匹配設定了的路由。

5、查詢字串

Ocelot中允許將查詢字串作為DownstreamPathTemplate的一部分,如下所示上游路徑模板中的{unitId}將作為下游路徑模板中的查詢字串引數unitId的值。

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
            "UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates",
            "UpstreamHttpMethod": [
                "Get"
            ],
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 50110
                }
            ]
        }
    ],
    "GlobalConfiguration": {
    }
}

此外Ocelot還允許將查詢字串放置在UpstreamPathTemplate中,以便將某些查詢匹配到對應的服務,如下所示只能匹配查詢引數為unitId的請求。

{
    "Routes": [
        {
            "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates",
            "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}",
            "UpstreamHttpMethod": [
                "Get"
            ],
            "DownstreamScheme": "http",
            "DownstreamHostAndPorts": [
                {
                    "Host": "localhost",
                    "Port": 50110
                }
            ]
        }
    ],
    "GlobalConfiguration": {
    }
}

 四、聚合路由

Ocelot中允許使用聚合路由,聚合路由就是將多個路由的結果結合成一個進行返回。

首先我們將ocelot.json改為下面的配置,可以看到兩個路由下各自有自己的Key,然後多了一個聚合路由Aggregates裡面設定了對應的兩個Key,並且該聚合路由的路徑也被設定為了/getweatherforecastaggregate/{everything}

{
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:5000"
  },
  "Routes": [
    {
      "DownstreamPathTemplate": "/{everything}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/api/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "Key": "WeatherForecast1"
    },
    {
      "DownstreamPathTemplate": "/{everything}",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/api2/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "Key": "WeatherForecast2"
    }
  ],
  "Aggregates": [
    {
      "RouteKeys": [
        "WeatherForecast1",
        "WeatherForecast2"
      ],
      "UpstreamPathTemplate": "/getweatherforecastaggregate/{everything}"
    }
  ]
}

然後我們請求對應的地址就可以看到返回了以兩個Key為鍵的對應路由地址介面返回的資訊,如果介面報錯則返回空。這裡需要注意,聚合路由Aggregates中的上游路徑UpstreamPathTemplate其實對應的就是Route中的UpstreamPathTemplate,也就是說路由中的上游路徑就是聚合路由的下游路徑,對應的變數佔位符啥的都會傳遞。

 如果我們不滿意返回的結果可以自定義聚合路由的來處理返回的結果,我們現在將oeclot.json中的聚合路由修改如下增加Aggregator引數

  "Aggregates": [
    {
      "RouteKeys": [
        "WeatherForecast1",
        "WeatherForecast2"
      ],
      "UpstreamPathTemplate": "/getweatherforecastaggregate/{everything}",
      "Aggregator": "MyAggregator"
    }
  ]

然後我們建立一個與Aggregator引數同名的重寫類,並且繼承IDefinedAggregator介面重寫Aggregate(List<HttpContext> responses)方法如下:

    public class MyAggregator: IDefinedAggregator
    {
        public async Task<DownstreamResponse> Aggregate(List<HttpContext> responses)
        {
            var one = await ((DownstreamResponse)responses[0].Response.HttpContext.Items["DownstreamResponse"]).Content.ReadAsStringAsync();
            //Response.Body不能獲取,只能透過HttpContext.Items
            //using var resReader1 = new StreamReader(responses[0].Response.Body);
            //var one = await resReader1.ReadToEndAsync();
            var two = await ((DownstreamResponse)responses[1].Response.HttpContext.Items["DownstreamResponse"]).Content.ReadAsStringAsync();
            var my = $"\"{Guid.NewGuid()}\":{{comment:\"我是自定義聚合器返回內容\"}}";
            var merge = $"{one}, {two},{my}";
            List<Header> headers = new List<Header>();
            return await Task.FromResult(new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"));
        }
    }

然後在註冊該類,可以是瞬態註冊等此處單例

builder.Services.AddOcelot().AddSingletonDefinedAggregator<MyAggregator>();

然後我們訪問地址就可看到返回了我們新增的內容

 

 需要注意的是,聚合路由只支援Get請求聚合,並且下游服務返回如果是404則返回空,只返回json字串。

相關文章