elsa-core/src/activities/Elsa.Activities.Http/Middleware/HttpEndpointMiddleware.cs
2021-05-18 21:17:00 +02:00

99 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Http.Bookmarks;
using Elsa.Activities.Http.Extensions;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Persistence;
using Elsa.Persistence.Specifications.WorkflowInstances;
using Elsa.Services;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Open.Linq.AsyncExtensions;
namespace Elsa.Activities.Http.Middleware
{
public class HttpEndpointMiddleware
{
// TODO: Design multi-tenancy.
private const string? TenantId = default;
private readonly RequestDelegate _next;
public HttpEndpointMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(
HttpContext httpContext,
IWorkflowLaunchpad workflowLaunchpad,
IWorkflowInstanceStore workflowInstanceStore,
IWorkflowRegistry workflowRegistry,
IWorkflowBlueprintReflector workflowBlueprintReflector,
IEnumerable<IHttpRequestBodyParser> contentParsers)
{
var cancellationToken = CancellationToken.None; // Prevent half-way request abortion (which also happens when WriteHttpResponse writes to the response).
var path = httpContext.Request.Path.Value.ToLowerInvariant();
var method = httpContext.Request.Method!.ToLowerInvariant();
var request = httpContext.Request;
request.TryGetCorrelationId(out var correlationId);
var useDispatch = httpContext.Request.GetUseDispatch();
const string activityType = nameof(HttpEndpoint);
var trigger = new HttpEndpointBookmark(path, method, null);
var bookmark = new HttpEndpointBookmark(path, method, correlationId?.ToLowerInvariant());
var collectWorkflowsContext = new CollectWorkflowsContext(activityType, bookmark, trigger, correlationId, default, default, TenantId);
var pendingWorkflows = await workflowLaunchpad.CollectWorkflowsAsync(collectWorkflowsContext, cancellationToken).ToList();
var pendingWorkflowInstanceIds = pendingWorkflows.Select(x => x.WorkflowInstanceId).Distinct();
var pendingWorkflowInstances = (await workflowInstanceStore.FindManyAsync(new WorkflowInstanceIdsSpecification(pendingWorkflowInstanceIds), cancellationToken: cancellationToken)).ToDictionary(x => x.Id);
var workflowDefinitionIds = pendingWorkflowInstances.Values.Select(x => x.DefinitionId).Distinct().ToHashSet();
var workflowBlueprints = (await workflowRegistry.FindManyAsync(x => x.IsPublished && workflowDefinitionIds.Contains(x.Id), cancellationToken)).ToDictionary(x => x.Id);
var serviceProvider = httpContext.RequestServices;
var workflowBlueprintWrappers = (await Task.WhenAll(workflowBlueprints.Values.Select(async x => await workflowBlueprintReflector.ReflectAsync(serviceProvider, x, cancellationToken)))).ToDictionary(x => x.WorkflowBlueprint.Id);
var commonInputModel = new HttpRequestModel(
new Uri(request.Path.ToString(), UriKind.Relative),
request.Method,
request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()),
request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString())
);
var orderedContentParsers = contentParsers.OrderByDescending(x => x.Priority).ToList();
var simpleContentType = request.ContentType?.Split(';').First();
var contentParser = orderedContentParsers.FirstOrDefault(x => x.SupportedContentTypes.Contains(simpleContentType, StringComparer.OrdinalIgnoreCase)) ?? orderedContentParsers.Last();
foreach (var pendingWorkflow in pendingWorkflows)
{
var pendingWorkflowInstance = pendingWorkflowInstances[pendingWorkflow.WorkflowInstanceId];
var workflowBlueprintWrapper = workflowBlueprintWrappers[pendingWorkflowInstance.DefinitionId];
var activityWrapper = workflowBlueprintWrapper.GetActivity<HttpEndpoint>(pendingWorkflow.ActivityId!);
var readContent = await activityWrapper!.GetPropertyValueAsync(x => x.ReadContent, cancellationToken);
var inputModel = commonInputModel;
if (readContent)
{
var targetType = await activityWrapper.GetPropertyValueAsync(x => x.TargetType, cancellationToken);
inputModel = inputModel with { Body = await contentParser.ParseAsync(request, targetType, cancellationToken) };
}
if (useDispatch)
await workflowLaunchpad.DispatchPendingWorkflowAsync(pendingWorkflow, inputModel, cancellationToken);
else
await workflowLaunchpad.ExecutePendingWorkflowsAsync(pendingWorkflows, inputModel, cancellationToken);
}
if (pendingWorkflows.Count > 0)
{
if (useDispatch)
{
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(JsonConvert.SerializeObject(pendingWorkflows), cancellationToken);
}
return;
}
await _next(httpContext);
}
}
}