mirror of https://github.com/abpframework/abp.git
committed by
GitHub
4 changed files with 94 additions and 405 deletions
@ -1,296 +0,0 @@ |
|||
// ------------------------------------------------------------------------
|
|||
// Copyright 2021 The Dapr Authors
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
// you may not use this file except in compliance with the License.
|
|||
// You may obtain a copy of the License at
|
|||
// http://www.apache.org/licenses/LICENSE-2.0
|
|||
// Unless required by applicable law or agreed to in writing, software
|
|||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
// See the License for the specific language governing permissions and
|
|||
// limitations under the License.
|
|||
// ------------------------------------------------------------------------
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Dapr |
|||
{ |
|||
/// <summary>
|
|||
/// This class defines configurations for the subscribe endpoint.
|
|||
/// </summary>
|
|||
public class AbpSubscribeOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or Sets a value which indicates whether to enable or disable processing raw messages.
|
|||
/// </summary>
|
|||
public bool EnableRawPayload { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// An optional delegate used to configure the subscriptions.
|
|||
/// </summary>
|
|||
public Func<List<AbpSubscription>, Task>? SubscriptionsCallback { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This class defines subscribe endpoint response
|
|||
/// </summary>
|
|||
public class AbpSubscription |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the topic name.
|
|||
/// </summary>
|
|||
public string Topic { get; set; } = default!; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the pubsub name
|
|||
/// </summary>
|
|||
public string PubsubName { get; set; } = default!; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the route
|
|||
/// </summary>
|
|||
public string? Route { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the routes
|
|||
/// </summary>
|
|||
public AbpRoutes? Routes { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the metadata.
|
|||
/// </summary>
|
|||
public AbpMetadata? Metadata { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the deadletter topic.
|
|||
/// </summary>
|
|||
public string? DeadLetterTopic { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This class defines the metadata for subscribe endpoint.
|
|||
/// </summary>
|
|||
public class AbpMetadata : Dictionary<string, string> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the Metadata class.
|
|||
/// </summary>
|
|||
public AbpMetadata() { } |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the Metadata class.
|
|||
/// </summary>
|
|||
/// <param name="dictionary"></param>
|
|||
public AbpMetadata(IDictionary<string, string> dictionary) : base(dictionary) { } |
|||
|
|||
/// <summary>
|
|||
/// RawPayload key
|
|||
/// </summary>
|
|||
internal const string RawPayload = "rawPayload"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This class defines the routes for subscribe endpoint.
|
|||
/// </summary>
|
|||
public class AbpRoutes |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the default route
|
|||
/// </summary>
|
|||
public string? Default { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the routing rules
|
|||
/// </summary>
|
|||
public List<AbpRule>? Rules { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This class defines the rule for subscribe endpoint.
|
|||
/// </summary>
|
|||
public class AbpRule |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the CEL expression to match this route.
|
|||
/// </summary>
|
|||
public string Match { get; set; } = default!; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the path of the route.
|
|||
/// </summary>
|
|||
public string Path { get; set; } = default!; |
|||
} |
|||
} |
|||
|
|||
namespace Microsoft.AspNetCore.Builder |
|||
{ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text.Json; |
|||
using System.Text.Json.Serialization; |
|||
using Dapr; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Routing; |
|||
using Microsoft.AspNetCore.Routing.Patterns; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
|
|||
/// <summary>
|
|||
/// Contains extension methods for <see cref="IEndpointRouteBuilder" />.
|
|||
/// </summary>
|
|||
public static class AbpDaprEndpointRouteBuilderExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Maps an endpoint that will respond to requests to <c>/dapr/subscribe</c> from the
|
|||
/// Dapr runtime.
|
|||
/// </summary>
|
|||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder" />.</param>
|
|||
/// <returns>The <see cref="IEndpointConventionBuilder" />.</returns>
|
|||
public static IEndpointConventionBuilder MapAbpSubscribeHandler(this IEndpointRouteBuilder endpoints) |
|||
{ |
|||
return CreateSubscribeEndPoint(endpoints); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Maps an endpoint that will respond to requests to <c>/dapr/subscribe</c> from the
|
|||
/// Dapr runtime.
|
|||
/// </summary>
|
|||
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder" />.</param>
|
|||
/// <param name="options">Configuration options</param>
|
|||
/// <returns>The <see cref="IEndpointConventionBuilder" />.</returns>
|
|||
/// <seealso cref="MapAbpSubscribeHandler(IEndpointRouteBuilder)"/>
|
|||
public static IEndpointConventionBuilder MapAbpSubscribeHandler(this IEndpointRouteBuilder endpoints, AbpSubscribeOptions options) |
|||
{ |
|||
return CreateSubscribeEndPoint(endpoints, options); |
|||
} |
|||
|
|||
private static IEndpointConventionBuilder CreateSubscribeEndPoint(IEndpointRouteBuilder endpoints, AbpSubscribeOptions? options = null) |
|||
{ |
|||
if (endpoints is null) |
|||
{ |
|||
throw new System.ArgumentNullException(nameof(endpoints)); |
|||
} |
|||
|
|||
return endpoints.MapGet("dapr/subscribe", async context => |
|||
{ |
|||
var logger = context.RequestServices.GetService<ILoggerFactory>()?.CreateLogger("DaprTopicSubscription"); |
|||
var dataSource = context.RequestServices.GetRequiredService<EndpointDataSource>(); |
|||
var subscriptions = dataSource.Endpoints |
|||
.OfType<RouteEndpoint>() |
|||
.Where(e => e.Metadata.GetOrderedMetadata<ITopicMetadata>().Any(t => t.Name != null)) // only endpoints which have TopicAttribute with not null Name.
|
|||
.SelectMany(e => |
|||
{ |
|||
var topicMetadata = e.Metadata.GetOrderedMetadata<ITopicMetadata>(); |
|||
var originalTopicMetadata = e.Metadata.GetOrderedMetadata<IOriginalTopicMetadata>(); |
|||
|
|||
var subs = new List<(string PubsubName, string Name, string? DeadLetterTopic, bool? EnableRawPayload, string Match, int Priority, Dictionary<string, string[]> OriginalTopicMetadata, string? MetadataSeparator, RoutePattern RoutePattern)>(); |
|||
|
|||
for (int i = 0; i < topicMetadata.Count(); i++) |
|||
{ |
|||
subs.Add((topicMetadata[i].PubsubName, |
|||
topicMetadata[i].Name, |
|||
(topicMetadata[i] as IDeadLetterTopicMetadata)?.DeadLetterTopic, |
|||
(topicMetadata[i] as IRawTopicMetadata)?.EnableRawPayload, |
|||
topicMetadata[i].Match, |
|||
topicMetadata[i].Priority, |
|||
originalTopicMetadata.Where(m => (topicMetadata[i] as IOwnedOriginalTopicMetadata)?.OwnedMetadatas?.Any(o => o.Equals(m.Id)) == true || string.IsNullOrEmpty(m.Id)) |
|||
.GroupBy(c => c.Name) |
|||
.ToDictionary(m => m.Key, m => m.Select(c => c.Value).Distinct().ToArray()), |
|||
(topicMetadata[i] as IOwnedOriginalTopicMetadata)?.MetadataSeparator, |
|||
e.RoutePattern)); |
|||
} |
|||
|
|||
return subs; |
|||
}) |
|||
.Distinct() |
|||
.GroupBy(e => new { e.PubsubName, e.Name }) |
|||
.Select(e => e.OrderBy(e => e.Priority)) |
|||
.Select(e => |
|||
{ |
|||
var first = e.First(); |
|||
var rawPayload = e.Any(e => e.EnableRawPayload.GetValueOrDefault()); |
|||
var metadataSeparator = e.FirstOrDefault(e => !string.IsNullOrEmpty(e.MetadataSeparator)).MetadataSeparator?.ToString() ?? ","; |
|||
var rules = e.Where(e => !string.IsNullOrEmpty(e.Match)).ToList(); |
|||
var defaultRoutes = e.Where(e => string.IsNullOrEmpty(e.Match)).Select(e => RoutePatternToString(e.RoutePattern)).ToList(); |
|||
var defaultRoute = defaultRoutes.FirstOrDefault(); |
|||
|
|||
//multiple identical names. use comma separation.
|
|||
var metadata = new AbpMetadata(e.SelectMany(c => c.OriginalTopicMetadata).GroupBy(c => c.Key).ToDictionary(c => c.Key, c => string.Join(metadataSeparator, c.SelectMany(c => c.Value).Distinct()))); |
|||
if (rawPayload || options?.EnableRawPayload is true) |
|||
{ |
|||
metadata.Add(AbpMetadata.RawPayload, "true"); |
|||
} |
|||
|
|||
if (logger != null) |
|||
{ |
|||
if (defaultRoutes.Count > 1) |
|||
{ |
|||
logger.LogError("A default subscription to topic {name} on pubsub {pubsub} already exists.", first.Name, first.PubsubName); |
|||
} |
|||
|
|||
var duplicatePriorities = rules.GroupBy(e => e.Priority) |
|||
.Where(g => g.Count() > 1) |
|||
.ToDictionary(x => x.Key, y => y.Count()); |
|||
|
|||
foreach (var entry in duplicatePriorities) |
|||
{ |
|||
logger.LogError("A subscription to topic {name} on pubsub {pubsub} has duplicate priorities for {priority}: found {count} occurrences.", first.Name, first.PubsubName, entry.Key, entry.Value); |
|||
} |
|||
} |
|||
|
|||
var subscription = new AbpSubscription |
|||
{ |
|||
Topic = first.Name, |
|||
PubsubName = first.PubsubName, |
|||
Metadata = metadata.Count > 0 ? metadata : null, |
|||
}; |
|||
|
|||
if (first.DeadLetterTopic != null) |
|||
{ |
|||
subscription.DeadLetterTopic = first.DeadLetterTopic; |
|||
} |
|||
|
|||
// Use the V2 routing rules structure
|
|||
if (rules.Count > 0) |
|||
{ |
|||
subscription.Routes = new AbpRoutes |
|||
{ |
|||
Rules = rules.Select(e => new AbpRule |
|||
{ |
|||
Match = e.Match, |
|||
Path = RoutePatternToString(e.RoutePattern), |
|||
}).ToList(), |
|||
Default = defaultRoute, |
|||
}; |
|||
} |
|||
// Use the V1 structure for backward compatibility.
|
|||
else |
|||
{ |
|||
subscription.Route = defaultRoute; |
|||
} |
|||
|
|||
return subscription; |
|||
}) |
|||
.OrderBy(e => (e.PubsubName, e.Topic)) |
|||
.ToList(); |
|||
|
|||
await options?.SubscriptionsCallback!(subscriptions)!; |
|||
await context.Response.WriteAsync(JsonSerializer.Serialize(subscriptions, |
|||
new JsonSerializerOptions |
|||
{ |
|||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
|||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull |
|||
})); |
|||
}); |
|||
} |
|||
|
|||
private static string RoutePatternToString(RoutePattern routePattern) |
|||
{ |
|||
return string.Join("/", routePattern.PathSegments |
|||
.Select(segment => string.Concat(segment.Parts.Cast<RoutePatternLiteralPart>() |
|||
.Select(part => part.Content)))); |
|||
} |
|||
} |
|||
} |
|||
@ -1,6 +0,0 @@ |
|||
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus; |
|||
|
|||
public class AbpAspNetCoreMvcDaprPubSubConsts |
|||
{ |
|||
public const string DaprEventCallbackUrl = "api/abp/dapr/event"; |
|||
} |
|||
@ -1,62 +0,0 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Volo.Abp.Dapr; |
|||
using Volo.Abp.EventBus.Dapr; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Dapr.EventBus.Controllers; |
|||
|
|||
[Area("abp")] |
|||
[RemoteService(Name = "abp")] |
|||
public class AbpAspNetCoreMvcDaprEventsController : AbpController |
|||
{ |
|||
[HttpPost(AbpAspNetCoreMvcDaprPubSubConsts.DaprEventCallbackUrl)] |
|||
public virtual async Task<IActionResult> EventAsync() |
|||
{ |
|||
HttpContext.ValidateDaprAppApiToken(); |
|||
|
|||
var daprSerializer = HttpContext.RequestServices.GetRequiredService<IDaprSerializer>(); |
|||
var body = (await JsonDocument.ParseAsync(HttpContext.Request.Body)); |
|||
|
|||
var pubSubName = body.RootElement.GetProperty("pubsubname").GetString(); |
|||
var topic = body.RootElement.GetProperty("topic").GetString(); |
|||
var data = body.RootElement.GetProperty("data").GetRawText(); |
|||
if (pubSubName.IsNullOrWhiteSpace() || topic.IsNullOrWhiteSpace() || data.IsNullOrWhiteSpace()) |
|||
{ |
|||
Logger.LogError("Invalid Dapr event request."); |
|||
return BadRequest(); |
|||
} |
|||
|
|||
var distributedEventBus = HttpContext.RequestServices.GetRequiredService<DaprDistributedEventBus>(); |
|||
|
|||
if (IsAbpDaprEventData(data)) |
|||
{ |
|||
var daprEventData = daprSerializer.Deserialize(data, typeof(AbpDaprEventData)).As<AbpDaprEventData>(); |
|||
var eventData = daprSerializer.Deserialize(daprEventData.JsonData, distributedEventBus.GetEventType(daprEventData.Topic)); |
|||
await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(daprEventData.Topic), eventData, daprEventData.MessageId, daprEventData.CorrelationId); |
|||
} |
|||
else |
|||
{ |
|||
var eventData = daprSerializer.Deserialize(data, distributedEventBus.GetEventType(topic!)); |
|||
await distributedEventBus.TriggerHandlersAsync(distributedEventBus.GetEventType(topic!), eventData); |
|||
} |
|||
|
|||
return Ok(); |
|||
} |
|||
|
|||
protected virtual bool IsAbpDaprEventData(string data) |
|||
{ |
|||
var document = JsonDocument.Parse(data); |
|||
var objects = document.RootElement.EnumerateObject().ToList(); |
|||
return objects.Count == 5 && |
|||
objects.Any(x => x.Name.Equals("PubSubName", StringComparison.CurrentCultureIgnoreCase)) && |
|||
objects.Any(x => x.Name.Equals("Topic", StringComparison.CurrentCultureIgnoreCase)) && |
|||
objects.Any(x => x.Name.Equals("MessageId", StringComparison.CurrentCultureIgnoreCase)) && |
|||
objects.Any(x => x.Name.Equals("JsonData", StringComparison.CurrentCultureIgnoreCase)) && |
|||
objects.Any(x => x.Name.Equals("CorrelationId", StringComparison.CurrentCultureIgnoreCase)); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue