16 changed files with 825 additions and 33 deletions
@ -0,0 +1,168 @@ |
|||
using LINGYUN.Abp.AspNetCore.HttpOverrides; |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.DataProtection; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.AspNetCore.Routing; |
|||
using Microsoft.AspNetCore.Server.Kestrel.Core; |
|||
using Microsoft.Extensions.Caching.StackExchangeRedis; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Ocelot.Configuration.Repository; |
|||
using Ocelot.DependencyInjection; |
|||
using Ocelot.Extenssions; |
|||
using Ocelot.Middleware.Multiplexer; |
|||
using Ocelot.Provider.Polly; |
|||
using StackExchange.Redis; |
|||
using System; |
|||
using System.Text; |
|||
using Volo.Abp; |
|||
using Volo.Abp.AspNetCore; |
|||
using Volo.Abp.Autofac; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.Caching.StackExchangeRedis; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.Security.Encryption; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.ApiGateway |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpAutofacModule), |
|||
typeof(AbpHttpClientModule), |
|||
typeof(AbpCachingStackExchangeRedisModule), |
|||
typeof(AbpAspNetCoreModule), |
|||
typeof(AbpAspNetCoreHttpOverridesModule) |
|||
)] |
|||
public class AbpApiGatewayHostModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddHttpClient("_AbpApiDefinitionClient"); |
|||
context.Services.AddSingleton<IFileConfigurationRepository, AbpApiDescriptionFileConfigurationRepository>(); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
// 解决某些不支持类型的序列化
|
|||
Configure<AbpJsonOptions>(options => |
|||
{ |
|||
// See: https://docs.abp.io/en/abp/4.0/Migration-Guides/Abp-4_0#always-use-the-newtonsoft-json
|
|||
options.UseHybridSerializer = false; |
|||
}); |
|||
// 中文序列化的编码问题
|
|||
//Configure<AbpSystemTextJsonSerializerOptions>(options =>
|
|||
//{
|
|||
// options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
|
|||
//});
|
|||
|
|||
Configure<AbpApiGatewayOptions>(configuration.GetSection("ApiGateway")); |
|||
|
|||
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) |
|||
.AddJwtBearer(options => |
|||
{ |
|||
options.Authority = configuration["AuthServer:Authority"]; |
|||
options.RequireHttpsMetadata = false; |
|||
options.Audience = configuration["AuthServer:ApiName"]; |
|||
}); |
|||
|
|||
Configure<AbpDistributedCacheOptions>(options => |
|||
{ |
|||
// 最好统一命名,不然某个缓存变动其他应用服务有例外发生
|
|||
options.KeyPrefix = "LINGYUN.Abp.Application"; |
|||
// 滑动过期30天
|
|||
options.GlobalCacheEntryOptions.SlidingExpiration = TimeSpan.FromDays(30); |
|||
// 绝对过期60天
|
|||
options.GlobalCacheEntryOptions.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60); |
|||
}); |
|||
|
|||
Configure<RedisCacheOptions>(options => |
|||
{ |
|||
var redisConfig = ConfigurationOptions.Parse(options.Configuration); |
|||
options.ConfigurationOptions = redisConfig; |
|||
options.InstanceName = configuration["Redis:InstanceName"]; |
|||
}); |
|||
|
|||
// 加解密
|
|||
Configure<AbpStringEncryptionOptions>(options => |
|||
{ |
|||
var encryptionConfiguration = configuration.GetSection("Encryption"); |
|||
if (encryptionConfiguration.Exists()) |
|||
{ |
|||
options.DefaultPassPhrase = encryptionConfiguration["PassPhrase"] ?? options.DefaultPassPhrase; |
|||
options.DefaultSalt = encryptionConfiguration.GetSection("Salt").Exists() |
|||
? Encoding.ASCII.GetBytes(encryptionConfiguration["Salt"]) |
|||
: options.DefaultSalt; |
|||
options.InitVectorBytes = encryptionConfiguration.GetSection("InitVector").Exists() |
|||
? Encoding.ASCII.GetBytes(encryptionConfiguration["InitVector"]) |
|||
: options.InitVectorBytes; |
|||
} |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpApiGatewayHostModule>(); |
|||
}); |
|||
|
|||
var mvcBuilder = context.Services.AddMvc(); |
|||
mvcBuilder.AddApplicationPart(typeof(AbpApiGatewayHostModule).Assembly); |
|||
|
|||
Configure<AbpEndpointRouterOptions>(options => |
|||
{ |
|||
options.EndpointConfigureActions.Add(endpointContext => |
|||
{ |
|||
endpointContext.Endpoints.MapControllerRoute("defaultWithArea", "{area}/{controller=Home}/{action=Index}/{id?}"); |
|||
endpointContext.Endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}"); |
|||
}); |
|||
}); |
|||
|
|||
if (!hostingEnvironment.IsDevelopment()) |
|||
{ |
|||
// Ssl证书
|
|||
var sslOptions = configuration.GetSection("App:SslOptions"); |
|||
if (sslOptions.Exists()) |
|||
{ |
|||
var fileName = sslOptions["FileName"]; |
|||
var password = sslOptions["Password"]; |
|||
Configure<KestrelServerOptions>(options => |
|||
{ |
|||
options.ConfigureEndpointDefaults(cfg => |
|||
{ |
|||
cfg.UseHttps(fileName, password); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); |
|||
context.Services |
|||
.AddDataProtection() |
|||
.PersistKeysToStackExchangeRedis(redis, "ApiGatewayHost-Protection-Keys"); |
|||
} |
|||
|
|||
context.Services |
|||
.AddOcelot() |
|||
.AddPolly() |
|||
.AddSingletonDefinedAggregator<AbpResponseMergeAggregator>(); |
|||
} |
|||
|
|||
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
var app = context.GetApplicationBuilder(); |
|||
|
|||
app.UseForwardedHeaders(); |
|||
app.UseAuditing(); |
|||
app.UseStaticFiles(); |
|||
app.UseRouting(); |
|||
app.UseAuthentication(); |
|||
// 启用ws协议
|
|||
app.UseWebSockets(); |
|||
app.UseOcelot().Wait(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using Ocelot.Configuration.File; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.ApiGateway |
|||
{ |
|||
public class AbpApiGatewayOptions |
|||
{ |
|||
public FileGlobalConfiguration GlobalConfiguration { get; set; } = new FileGlobalConfiguration(); |
|||
public List<string> AggrageRouteUrls { get; set; } = new List<string>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
FROM mcr.microsoft.com/dotnet/aspnet:5.0 |
|||
LABEL maintainer="colin.in@foxmail.com" |
|||
WORKDIR /app |
|||
|
|||
COPY . /app |
|||
|
|||
ENV TZ=Asia/Shanghai |
|||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo '$TZ' > /etc/timezone |
|||
|
|||
EXPOSE 80/tcp |
|||
VOLUME [ "./app/Logs" ] |
|||
|
|||
ENTRYPOINT ["dotnet", "LINGYUN.ApiGateway.Host.dll"] |
|||
@ -0,0 +1,28 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net5.0</TargetFramework> |
|||
<RootNamespace>LINGYUN.Abp.ApiGateway</RootNamespace> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="5.0.*" /> |
|||
<PackageReference Include="Ocelot.Provider.Polly" Version="16.0.1" /> |
|||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" /> |
|||
<PackageReference Include="Serilog.Enrichers.Assembly" Version="2.0.0" /> |
|||
<PackageReference Include="Serilog.Enrichers.Process" Version="2.0.1" /> |
|||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" /> |
|||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" /> |
|||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" /> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Authentication.JwtBearer" Version="4.3.0" /> |
|||
<PackageReference Include="Volo.Abp.Autofac" Version="4.3.0" /> |
|||
<PackageReference Include="Volo.Abp.AspNetCore" Version="4.3.0" /> |
|||
<PackageReference Include="Volo.Abp.Http.Client" Version="4.3.0" /> |
|||
<PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="4.3.0" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\modules\common\LINGYUN.Abp.AspNetCore.HttpOverrides\LINGYUN.Abp.AspNetCore.HttpOverrides.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<PropertyGroup> |
|||
<ActiveDebugProfile>IIS Express</ActiveDebugProfile> |
|||
</PropertyGroup> |
|||
</Project> |
|||
@ -0,0 +1,185 @@ |
|||
using LINGYUN.Abp.ApiGateway; |
|||
using Microsoft.Extensions.Options; |
|||
using Ocelot.Configuration.File; |
|||
using Ocelot.Middleware.Multiplexer; |
|||
using Ocelot.Responses; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.DynamicProxying; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
namespace Ocelot.Configuration.Repository |
|||
{ |
|||
/// <summary>
|
|||
/// 通过定义的RemoteServices来寻找abp端点定义的服务列表
|
|||
/// 在AbpApiGatewayOptions.AggrageRouteUrls中定义聚合的url
|
|||
/// 可以在发现相同端点后加入到聚合路由,聚合路由下游端点会自动加入聚合标识,以发现的聚合数量作为前缀
|
|||
/// 例如/api/abp/api-definition、/api/abp/application-configuration
|
|||
/// 下游端点将变成/aggregate1/api/abp/api-definition、/aggregate1/api/abp/application-configuration
|
|||
/// 上游服务直接调用/api/abp/api-definition、/api/abp/application-configuration即可唤起路由聚合
|
|||
/// </summary>
|
|||
public class AbpApiDescriptionFileConfigurationRepository : IFileConfigurationRepository |
|||
{ |
|||
private readonly AbpRemoteServiceOptions _remoteServiceOptions; |
|||
private readonly AbpApiGatewayOptions _apiGatewayOptions; |
|||
|
|||
private readonly IApiDescriptionFinder _apiDescriptionFinder; |
|||
private readonly IHttpClientFactory _httpClientFactory; |
|||
public AbpApiDescriptionFileConfigurationRepository( |
|||
IHttpClientFactory httpClientFactory, |
|||
IApiDescriptionFinder apiDescriptionFinder, |
|||
IOptions<AbpApiGatewayOptions> apiGatewayOptions, |
|||
IOptions<AbpRemoteServiceOptions> remoteServiceOptions |
|||
) |
|||
{ |
|||
_httpClientFactory = httpClientFactory; |
|||
_apiDescriptionFinder = apiDescriptionFinder; |
|||
|
|||
_apiGatewayOptions = apiGatewayOptions.Value; |
|||
_remoteServiceOptions = remoteServiceOptions.Value; |
|||
} |
|||
public async Task<Response<FileConfiguration>> Get() |
|||
{ |
|||
var fileConfiguration = new FileConfiguration |
|||
{ |
|||
GlobalConfiguration = _apiGatewayOptions.GlobalConfiguration |
|||
}; |
|||
var apiDescriptionModels = await GetApiDescriptionsAsync(); |
|||
int foundAggrageRouteCount = 0; |
|||
foreach (var apiDescriptionModel in apiDescriptionModels) |
|||
{ |
|||
foreach (var moduleModel in apiDescriptionModel.Value.Modules) |
|||
{ |
|||
foreach (var controllerModel in moduleModel.Value.Controllers) |
|||
{ |
|||
foreach (var actionModel in controllerModel.Value.Actions) |
|||
{ |
|||
var action = actionModel.Value; |
|||
var downstreamUrl = action.Url.EnsureStartsWith('/'); |
|||
|
|||
// TODO: 多个相同的下游路由地址应组合为聚合路由
|
|||
|
|||
// TODO: 下游路由地址已添加
|
|||
var route = fileConfiguration.Routes |
|||
.Where(route => HasBeenAddedRoute(route, downstreamUrl)) |
|||
.LastOrDefault(); |
|||
string aggregateKey = ""; |
|||
|
|||
if (route != null) |
|||
{ |
|||
// TODO: 下游方法已添加
|
|||
if (route.UpstreamHttpMethod.Any(method => method.Equals(action.HttpMethod, StringComparison.CurrentCultureIgnoreCase))) |
|||
{ |
|||
if (_apiGatewayOptions.AggrageRouteUrls.Any(url => url.Equals(downstreamUrl))) |
|||
{ |
|||
foundAggrageRouteCount++; |
|||
var aggregateRoute = fileConfiguration.Aggregates |
|||
.Where(route => HasBeenAddedAggregateRoute(route, downstreamUrl)) |
|||
.FirstOrDefault(); |
|||
if (aggregateRoute == null) |
|||
{ |
|||
aggregateRoute = new FileAggregateRoute |
|||
{ |
|||
RouteIsCaseSensitive = false, |
|||
// TODO: 可实现自定义的聚合提供装置
|
|||
Aggregator = nameof(AbpResponseMergeAggregator), |
|||
UpstreamPathTemplate = downstreamUrl, |
|||
RouteKeys = new List<string>() |
|||
}; |
|||
fileConfiguration.Aggregates.Add(aggregateRoute); |
|||
} |
|||
if (route.Key.IsNullOrWhiteSpace()) |
|||
{ |
|||
route.Key = $"aggregate{foundAggrageRouteCount}"; |
|||
route.UpstreamPathTemplate = $"/{route.Key}{route.UpstreamPathTemplate}"; |
|||
aggregateRoute.RouteKeys.Add(route.Key); |
|||
foundAggrageRouteCount++; |
|||
} |
|||
aggregateKey = $"aggregate{foundAggrageRouteCount}"; |
|||
aggregateRoute.RouteKeys.Add(aggregateKey); |
|||
} |
|||
else |
|||
{ |
|||
continue; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
route.UpstreamHttpMethod.Add(action.HttpMethod); |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
var newRoute = new FileRoute |
|||
{ |
|||
Key = aggregateKey, |
|||
UpstreamPathTemplate = (aggregateKey + downstreamUrl).EnsureStartsWith('/'), |
|||
DownstreamPathTemplate = downstreamUrl, |
|||
DangerousAcceptAnyServerCertificateValidator = false |
|||
}; |
|||
|
|||
var baseUrl = apiDescriptionModel.Key; |
|||
baseUrl = baseUrl.StartsWith("http://") ? baseUrl[7..] : baseUrl; |
|||
baseUrl = baseUrl.StartsWith("https://") ? baseUrl[8..] : baseUrl; |
|||
baseUrl = baseUrl.EndsWith("/") ? baseUrl[0..^1] : baseUrl; |
|||
|
|||
var addresses = baseUrl.Split(":"); |
|||
var hostAndPort = new FileHostAndPort |
|||
{ |
|||
Host = addresses[0] |
|||
}; |
|||
if (addresses.Length == 2) |
|||
{ |
|||
hostAndPort.Port = int.Parse(addresses[1]); |
|||
} |
|||
newRoute.DownstreamHostAndPorts.Add(hostAndPort); |
|||
newRoute.UpstreamHttpMethod.Add(action.HttpMethod); |
|||
|
|||
// abp api版本号支持通过query发送
|
|||
//newRoute.DownstreamHttpVersion = action.SupportedVersions.Last();
|
|||
|
|||
fileConfiguration.Routes.Add(newRoute); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return new OkResponse<FileConfiguration>(fileConfiguration); |
|||
} |
|||
|
|||
private static bool HasBeenAddedRoute(FileRoute route, string downstreamUrl) |
|||
{ |
|||
return route.DownstreamPathTemplate.Equals(downstreamUrl, StringComparison.CurrentCultureIgnoreCase); |
|||
} |
|||
|
|||
private static bool HasBeenAddedAggregateRoute(FileAggregateRoute route, string upstreamUrl) |
|||
{ |
|||
return route.UpstreamPathTemplate.Equals(upstreamUrl, StringComparison.CurrentCultureIgnoreCase); |
|||
} |
|||
|
|||
public async Task<Response> Set(FileConfiguration fileConfiguration) |
|||
{ |
|||
return await Task.FromResult(new OkResponse<FileConfiguration>(fileConfiguration)); |
|||
} |
|||
|
|||
protected virtual async Task<Dictionary<string, ApplicationApiDescriptionModel>> GetApiDescriptionsAsync() |
|||
{ |
|||
var client = _httpClientFactory.CreateClient("_AbpApiDefinitionClient"); |
|||
|
|||
var apiDescriptionModels = new Dictionary<string, ApplicationApiDescriptionModel>(); |
|||
|
|||
foreach (var remoteService in _remoteServiceOptions.RemoteServices) |
|||
{ |
|||
var model = await _apiDescriptionFinder.GetApiDescriptionAsync(client, remoteService.Value.BaseUrl); |
|||
|
|||
apiDescriptionModels.Add(remoteService.Value.BaseUrl, model); |
|||
} |
|||
|
|||
return apiDescriptionModels; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,123 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Ocelot.Configuration; |
|||
using Ocelot.Configuration.Creator; |
|||
using Ocelot.Configuration.Repository; |
|||
using Ocelot.DependencyInjection; |
|||
using Ocelot.Logging; |
|||
using Ocelot.Middleware; |
|||
using Ocelot.Responses; |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Ocelot.Extenssions |
|||
{ |
|||
public static class OcelotMiddlewareExtensions |
|||
{ |
|||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder) |
|||
{ |
|||
await builder.UseOcelot(new OcelotPipelineConfiguration()); |
|||
return builder; |
|||
} |
|||
|
|||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, Action<OcelotPipelineConfiguration> pipelineConfiguration) |
|||
{ |
|||
var config = new OcelotPipelineConfiguration(); |
|||
pipelineConfiguration?.Invoke(config); |
|||
return await builder.UseOcelot(config); |
|||
} |
|||
|
|||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) |
|||
{ |
|||
var configuration = await CreateConfiguration(builder); |
|||
|
|||
ConfigureDiagnosticListener(builder); |
|||
|
|||
return CreateOcelotPipeline(builder, pipelineConfiguration); |
|||
} |
|||
|
|||
public static Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder app, Action<IApplicationBuilder, OcelotPipelineConfiguration> builderAction) |
|||
=> UseOcelot(app, builderAction, new OcelotPipelineConfiguration()); |
|||
|
|||
public static async Task<IApplicationBuilder> UseOcelot(this IApplicationBuilder app, Action<IApplicationBuilder, OcelotPipelineConfiguration> builderAction, OcelotPipelineConfiguration configuration) |
|||
{ |
|||
await CreateConfiguration(app); |
|||
|
|||
ConfigureDiagnosticListener(app); |
|||
|
|||
builderAction?.Invoke(app, configuration ?? new OcelotPipelineConfiguration()); |
|||
|
|||
app.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; |
|||
|
|||
return app; |
|||
} |
|||
|
|||
private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) |
|||
{ |
|||
builder.BuildOcelotPipeline(pipelineConfiguration); |
|||
|
|||
/* |
|||
inject first delegate into first piece of asp.net middleware..maybe not like this |
|||
then because we are updating the http context in ocelot it comes out correct for |
|||
rest of asp.net.. |
|||
*/ |
|||
|
|||
builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; |
|||
|
|||
return builder; |
|||
} |
|||
private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder) |
|||
{ |
|||
var fileConfigRepo = builder.ApplicationServices.GetRequiredService<IFileConfigurationRepository>(); |
|||
var fileConfig = await fileConfigRepo.Get(); |
|||
// IOptionsMonitor<FileConfiguration> fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>();
|
|||
IInternalConfigurationCreator internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>(); |
|||
Response<IInternalConfiguration> response = await internalConfigCreator.Create(fileConfig.Data); |
|||
if (response.IsError) |
|||
{ |
|||
ThrowToStopOcelotStarting(response); |
|||
} |
|||
IInternalConfigurationRepository internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>(); |
|||
internalConfigRepo.AddOrReplace(response.Data); |
|||
return GetOcelotConfigAndReturn(internalConfigRepo); |
|||
} |
|||
|
|||
private static bool AdministrationApiInUse(IAdministrationPath adminPath) |
|||
{ |
|||
return adminPath != null; |
|||
} |
|||
|
|||
private static bool IsError(Response response) |
|||
{ |
|||
return response == null || response.IsError; |
|||
} |
|||
|
|||
private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) |
|||
{ |
|||
var ocelotConfiguration = provider.Get(); |
|||
|
|||
if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) |
|||
{ |
|||
ThrowToStopOcelotStarting(ocelotConfiguration); |
|||
} |
|||
|
|||
return ocelotConfiguration.Data; |
|||
} |
|||
|
|||
private static void ThrowToStopOcelotStarting(Response config) |
|||
{ |
|||
throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); |
|||
} |
|||
|
|||
private static void ConfigureDiagnosticListener(IApplicationBuilder builder) |
|||
{ |
|||
var env = builder.ApplicationServices.GetService<IWebHostEnvironment>(); |
|||
var listener = builder.ApplicationServices.GetService<OcelotDiagnosticListener>(); |
|||
var diagnosticListener = builder.ApplicationServices.GetService<DiagnosticListener>(); |
|||
diagnosticListener.SubscribeWithAdapter(listener); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Linq; |
|||
using Ocelot.Multiplexer; |
|||
using System.Collections.Generic; |
|||
using System.Net; |
|||
using System.Net.Http; |
|||
using System.Net.Http.Headers; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Ocelot.Middleware.Multiplexer |
|||
{ |
|||
public class AbpResponseMergeAggregator : IDefinedAggregator |
|||
{ |
|||
public async Task<DownstreamResponse> Aggregate(List<HttpContext> responses) |
|||
{ |
|||
return await MapAbpApiDefinitionAggregateContentAsync(responses); |
|||
} |
|||
|
|||
protected virtual async Task<DownstreamResponse> MapAbpApiDefinitionAggregateContentAsync(List<HttpContext> responses) |
|||
{ |
|||
JObject responseObject = null; |
|||
JsonMergeSettings mergeSetting = new JsonMergeSettings |
|||
{ |
|||
MergeArrayHandling = MergeArrayHandling.Union, |
|||
PropertyNameComparison = System.StringComparison.CurrentCultureIgnoreCase |
|||
}; |
|||
foreach (var httpResponse in responses) |
|||
{ |
|||
var content = await httpResponse.Items.DownstreamResponse().Content.ReadAsStringAsync(); |
|||
var contentObject = JsonConvert.DeserializeObject(content); |
|||
if (responseObject == null) |
|||
{ |
|||
responseObject = JObject.FromObject(contentObject); |
|||
} |
|||
else |
|||
{ |
|||
responseObject.Merge(contentObject, mergeSetting); |
|||
} |
|||
} |
|||
var stringContent = new StringContent(responseObject.ToString()) |
|||
{ |
|||
Headers = { ContentType = new MediaTypeHeaderValue("application/json") } |
|||
}; |
|||
stringContent.Headers.Add("_AbpErrorFormat", "true"); |
|||
return new DownstreamResponse(stringContent, HttpStatusCode.OK, |
|||
new List<KeyValuePair<string, IEnumerable<string>>>(), "OK"); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using Microsoft.AspNetCore.Hosting; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.Hosting; |
|||
using Serilog; |
|||
using System; |
|||
using System.IO; |
|||
|
|||
namespace LINGYUN.Abp.ApiGateway |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static int Main(string[] args) |
|||
{ |
|||
try |
|||
{ |
|||
var hostBuilder = CreateHostBuilder(args).Build(); |
|||
Log.Information("Starting web host."); |
|||
hostBuilder.Run(); |
|||
|
|||
return 0; |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Log.Fatal(ex, "Host terminated unexpectedly!"); |
|||
return 1; |
|||
} |
|||
finally |
|||
{ |
|||
Log.CloseAndFlush(); |
|||
} |
|||
} |
|||
|
|||
internal static IHostBuilder CreateHostBuilder(string[] args) => |
|||
Host.CreateDefaultBuilder(args) |
|||
.ConfigureWebHostDefaults(webBuilder => |
|||
{ |
|||
webBuilder |
|||
.UseKestrel() |
|||
.UseStartup<Startup>(); |
|||
}) |
|||
.UseSerilog((context, provider, config) => |
|||
{ |
|||
config.ReadFrom.Configuration(context.Configuration); |
|||
}) |
|||
.UseAutofac(); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
{ |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:54450/", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"LINGYUN.Abp.ApiGateway.Host": { |
|||
"commandName": "Project", |
|||
"launchBrowser": false, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
}, |
|||
"applicationUrl": "http://localhost:30000" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.AspNetCore.Builder; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.ApiGateway |
|||
{ |
|||
public class Startup |
|||
{ |
|||
public void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddApplication<AbpApiGatewayHostModule>(); |
|||
} |
|||
|
|||
public void Configure(IApplicationBuilder app) |
|||
{ |
|||
app.InitializeApplication(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue