.NET Core 下的 API 网关

DotNetCore实战

共 10348字,需浏览 21分钟

 ·

2020-10-07 23:12

网关介绍

网关其实就是将我们写好的API全部放在一个统一的地址暴露在公网,提供访问的一个入口。在 .NET Core下可以使用Ocelot来帮助我们很方便的接入API 网关。与之类似的库还有ProxyKit,微软也发布了一个反向代理的库YARP

关于网关的介绍不多说了,网上文章也挺多的,这些都是不错的选择,听说后期Ocelot将会使用YARP来重写。本篇主要实践一下在.NET Core环境下使用Ocelot

  • Ocelot官网:https://threemammals.com/ocelot
  • Ocelot文档:https://ocelot.readthedocs.io
  • GitHub:https://github.com/ThreeMammals/Ocelot
  • Ocelot资源汇总:https://www.cnblogs.com/shanyou/p/10363360.html

接入使用

接口示例

先创建几个项目用于测试,创建两个默认的API项目,Api_A和Api_B,在创建一个网关项目Api_Gateway,网关项目可以选择空的模板。

现在分别在Api_A和Api_B中写几个api,将默认的WeatherForecastController中返回模型WeatherForecast添加一个字段Source,用于区分是哪个API返回的数据。

using System;

namespace Api_A
{
    public class WeatherForecast
    {
        public string Source { getset; } = "Api_A";

        public DateTime Date { getset; }

        public int TemperatureC { getset; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { getset; }
    }
}

using System;

namespace Api_B
{
    public class WeatherForecast
    {
        public string Source { getset; } = "Api_B";

        public DateTime Date { getset; }

        public int TemperatureC { getset; }

        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

        public string Summary { getset; }
    }
}

直接使用WeatherForecastController默认方法,在路由中添加api前缀。

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Api_A.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing""Bracing""Chilly""Cool""Mild""Warm""Balmy""Hot""Sweltering""Scorching"
        };

        [HttpGet]
        public IEnumerable Get()
        {
            var rng = new Random();
            return Enumerable.Range(15).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-2055),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray();
        }
    }
}

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Api_B.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing""Bracing""Chilly""Cool""Mild""Warm""Balmy""Hot""Sweltering""Scorching"
        };

        [HttpGet]
        public IEnumerable Get()
        {
            var rng = new Random();
            return Enumerable.Range(15).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-2055),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray();
        }
    }
}

再分别在Api_A和Api_B中添加两个控制器:ApiAController、ApiBController,然后加上几个简单的restful api。

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace Api_A.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ApiAController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<stringGet()
        {
            return new string[] { "value1""value2" };
        }

        [HttpGet("{id}")]
        public string Get(int id)
        {
            return $"Get:{id}";
        }

        [HttpPost]
        public string Post([FromForm] string value)
        {
            return $"Post:{value}";
        }

        [HttpPut("{id}")]
        public string Put(int id, [FromForm] string value)
        {
            return $"Put:{id}:{value}";
        }

        [HttpDelete("{id}")]
        public string Delete(int id)
        {
            return $"Delete:{id}";
        }
    }
}
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace Api_B.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ApiBController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<stringGet()
        {
            return new string[] { "value1""value2" };
        }

        [HttpGet("{id}")]
        public string Get(int id)
        {
            return $"Get:{id}";
        }

        [HttpPost]
        public string Post([FromForm] string value)
        {
            return $"Post:{value}";
        }

        [HttpPut("{id}")]
        public string Put(int id, [FromForm] string value)
        {
            return $"Put:{id}:{value}";
        }

        [HttpDelete("{id}")]
        public string Delete(int id)
        {
            return $"Delete:{id}";
        }
    }
}

方便查看接口,这里添加一下swagger组件,这样我们Api_A和Api_B项目分别就有了6个接口。

接着打包docker镜像,放在docker中运行这两个api项目。这一步可以用任何你熟悉的方式,run起来即可。

docker build -t api_a:dev -f ./Api_A/Dockerfile .
docker build -t api_b:dev -f ./Api_B/Dockerfile .

build成功后,指定两个端口运行api项目。

docker run -d -p 5050:80 --name api_a api_a:dev
docker run -d -p 5051:80 --name api_b api_b:dev

Api_A指定了5050端口,通过 http://localhost:5050/swagger打开可以看到swagger文档界面,Api_B指定了5051端口,通过 http://localhost:5051/swagger打开可以看到swagger文档界面,这样就大功告成了,接下来才是重点将两个api项目配置到Api_Gateway网关项目中。

配置网关

在网关项目Api_Gateway中都添加Ocelot组件包。

Install-Package Ocelot

Ocelot中最关键的就是配置路由信息,新建一个ocelot.json配置文件,将我们的两个API接口匹配规则放进去。

{
  "Routes": [
    //ApiA
    {
      "DownstreamPathTemplate""/api/WeatherForecast",
      "DownstreamScheme""http",
      "DownstreamHostAndPorts": [
        {
          "Host""localhost",
          "Port"5050
        }
      ],
      "UpstreamPathTemplate""/ApiA/WeatherForecast",
      "UpstreamHttpMethod": [ "Get" ]
    },
    {
      "DownstreamPathTemplate""/api/ApiA",
      "DownstreamScheme""http",
      "DownstreamHostAndPorts": [
        {
          "Host""localhost",
          "Port"5050
        }
      ],
      "UpstreamPathTemplate""/ApiA",
      "UpstreamHttpMethod": [ "Get""POST" ]
    },
    {
      "DownstreamPathTemplate""/api/ApiA/{id}",
      "DownstreamScheme""http",
      "DownstreamHostAndPorts": [
        {
          "Host""localhost",
          "Port"5050
        }
      ],
      "UpstreamPathTemplate""/ApiA/{id}",
      "UpstreamHttpMethod": [ "Get""Put""Delete" ]
    },
    //ApiB
    {
      "DownstreamPathTemplate""/api/WeatherForecast",
      "DownstreamScheme""http",
      "DownstreamHostAndPorts": [
        {
          "Host""localhost",
          "Port"5051
        }
      ],
      "UpstreamPathTemplate""/ApiB/WeatherForecast",
      "UpstreamHttpMethod": [ "Get" ]
    },
    {
      "DownstreamPathTemplate""/api/ApiB",
      "DownstreamScheme""http",
      "DownstreamHostAndPorts": [
        {
          "Host""localhost",
          "Port"5051
        }
      ],
      "UpstreamPathTemplate""/ApiB",
      "UpstreamHttpMethod": [ "Get""POST" ]
    },
    {
      "DownstreamPathTemplate""/api/ApiB/{id}",
      "DownstreamScheme""http",
      "DownstreamHostAndPorts": [
        {
          "Host""localhost",
          "Port"5051
        }
      ],
      "UpstreamPathTemplate""/ApiB/{id}",
      "UpstreamHttpMethod": [ "Get""Put""Delete" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl""https://localhost:44335"
  }
}

关于配置文件中的各项具体含义,可以参考官方文档中的介绍。主要就是将DownstreamPathTemplate模板内容转换为UpstreamPathTemplate模板内容进行接口的访问,同时可以指定HTTP请求的方式等等。GlobalConfiguration中的BaseUrl为我们暴漏出去的网关地址。

设置好ocelot.json后,需要在代码中使用它,在Program.cs中添加配置文件。

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Api_Gateway
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((context, config) =>
                {
                    config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup();
                });
    }
}

Startup.cs中使用Ocelot

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;

namespace Api_Gateway
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOcelot();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/"async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });

            app.UseOcelot().Wait();
        }
    }
}

完成以上操作后,我们试着去调用接口看看能否正确获取预期数据。

curl -X GET "https://localhost:44335/ApiA"
curl -X GET "https://localhost:44335/ApiB"

curl -X POST "https://localhost:44335/ApiA" -H "Content-Type: multipart/form-data" -F "value=ApiA"
curl -X POST "https://localhost:44335/ApiB" -H "Content-Type: multipart/form-data" -F "value=ApiB"

curl -X GET "https://localhost:44335/ApiA/12345"
curl -X GET "https://localhost:44335/ApiB/12345"

curl -X PUT "https://localhost:44335/ApiA/12345" -H "Content-Type: multipart/form-data" -F "value=ApiA"
curl -X PUT "https://localhost:44335/ApiB/12345" -H "Content-Type: multipart/form-data" -F "value=ApiB"

curl -X DELETE "https://localhost:44335/ApiA/12345"
curl -X DELETE "https://localhost:44335/ApiB/12345"

curl -X GET "https://localhost:44335/ApiA/WeatherForecast"
curl -X GET "https://localhost:44335/ApiB/WeatherForecast"

可以看到,两个项目中的接口全部可以通过网关项目暴露的地址进行中转,是不是很方便?

本篇只是简单的应用,对于Ocelot的功能远不止于此,它非常强大,还可以实现请求聚合、服务发现、认证、鉴权、限流熔断、并内置了负载均衡器,而且这些功能都是只需要简单的配置即可完成。就不一一描述了,如有实际开发需求和问题,可以查看官方文档和示例。


往期精彩回顾




【推荐】.NET Core开发实战视频课程 ★★★

.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

【.NET Core微服务实战-统一身份认证】开篇及目录索引

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

用abp vNext快速开发Quartz.NET定时任务管理界面

在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了


浏览 39
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报