2020-12-24 13:30:55 +00:00
|
|
|
using System;
|
2021-01-29 15:16:45 +00:00
|
|
|
using System.Collections.Generic;
|
2020-10-17 10:49:41 +00:00
|
|
|
using System.Linq;
|
2020-10-30 20:55:27 +00:00
|
|
|
using System.Net;
|
2021-01-29 15:16:45 +00:00
|
|
|
using System.Threading;
|
2020-10-17 07:15:46 +00:00
|
|
|
using System.Threading.Tasks;
|
2021-01-29 15:16:45 +00:00
|
|
|
using Elsa.Activities.Http.Bookmarks;
|
2020-10-30 20:55:27 +00:00
|
|
|
using Elsa.Activities.Http.Extensions;
|
2021-01-29 15:16:45 +00:00
|
|
|
using Elsa.Bookmarks;
|
2021-01-30 11:47:50 +00:00
|
|
|
using Elsa.Models;
|
2020-12-11 20:35:22 +00:00
|
|
|
using Elsa.Persistence;
|
2021-01-30 11:47:50 +00:00
|
|
|
using Elsa.Serialization;
|
2020-10-17 10:49:41 +00:00
|
|
|
using Elsa.Services;
|
2021-01-29 15:16:45 +00:00
|
|
|
using Elsa.Services.Models;
|
2020-10-17 07:15:46 +00:00
|
|
|
using Microsoft.AspNetCore.Http;
|
2020-10-30 20:55:27 +00:00
|
|
|
using Open.Linq.AsyncExtensions;
|
2020-10-17 07:15:46 +00:00
|
|
|
|
|
|
|
|
namespace Elsa.Activities.Http.Middleware
|
|
|
|
|
{
|
2020-10-17 10:49:41 +00:00
|
|
|
public class ReceiveHttpRequestMiddleware
|
2020-10-17 07:15:46 +00:00
|
|
|
{
|
2021-01-29 15:16:45 +00:00
|
|
|
// TODO: Figure out how to start jobs across multiple tenants / how to get a list of all tenants.
|
|
|
|
|
private const string TenantId = default;
|
2020-10-17 07:15:46 +00:00
|
|
|
private readonly RequestDelegate _next;
|
|
|
|
|
|
2020-10-17 10:49:41 +00:00
|
|
|
public ReceiveHttpRequestMiddleware(RequestDelegate next)
|
2020-10-17 07:15:46 +00:00
|
|
|
{
|
|
|
|
|
_next = next;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-29 15:16:45 +00:00
|
|
|
public async Task InvokeAsync(
|
|
|
|
|
HttpContext httpContext,
|
|
|
|
|
IWorkflowRegistry workflowRegistry,
|
|
|
|
|
IBookmarkFinder bookmarkFinder,
|
|
|
|
|
IWorkflowRunner workflowRunner,
|
|
|
|
|
IWorkflowInstanceStore workflowInstanceStore,
|
|
|
|
|
IWorkflowBlueprintReflector workflowBlueprintReflector,
|
2021-01-30 11:47:50 +00:00
|
|
|
IContentSerializer contentSerializer)
|
2020-10-17 07:15:46 +00:00
|
|
|
{
|
2020-10-17 10:49:41 +00:00
|
|
|
var path = httpContext.Request.Path;
|
2021-01-29 15:16:45 +00:00
|
|
|
var method = httpContext.Request.Method!;
|
2020-10-17 11:51:27 +00:00
|
|
|
var cancellationToken = httpContext.RequestAborted;
|
2021-01-30 11:47:50 +00:00
|
|
|
var serviceProvider = httpContext.RequestServices;
|
2020-10-30 20:55:27 +00:00
|
|
|
httpContext.Request.TryGetCorrelationId(out var correlationId);
|
2020-10-17 13:38:27 +00:00
|
|
|
|
2021-01-29 15:16:45 +00:00
|
|
|
// Find workflow definitions starting with an HttpRequestReceived activity and a matching Path and Method.
|
2021-01-30 11:47:50 +00:00
|
|
|
var definitions = await FindHttpWorkflowBlueprints(workflowRegistry, workflowBlueprintReflector, serviceProvider, cancellationToken);
|
2021-01-29 15:16:45 +00:00
|
|
|
var matchingDefinitions = definitions.Where(definition => AreSame(definition.Path, path) && (string.IsNullOrWhiteSpace(definition.Method) || AreSame(definition.Method!, method))).ToList();
|
|
|
|
|
|
|
|
|
|
if (matchingDefinitions.Count > 1)
|
|
|
|
|
{
|
|
|
|
|
httpContext.Response.StatusCode = (int) HttpStatusCode.BadRequest;
|
|
|
|
|
await httpContext.Response.WriteAsync("Request matches multiple workflows.", cancellationToken);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matchingDefinitions.Count == 1)
|
|
|
|
|
{
|
|
|
|
|
var definition = matchingDefinitions.First();
|
|
|
|
|
var workflowBlueprint = definition.WorkflowBlueprint;
|
|
|
|
|
var activityId = definition.ActivityBlueprint.Id;
|
2021-01-30 11:47:50 +00:00
|
|
|
var workflowInstance = await workflowRunner.RunWorkflowAsync(workflowBlueprint, activityId, cancellationToken: cancellationToken);
|
|
|
|
|
await HandleWorkflowInstanceResponseAsync(httpContext, workflowInstance, contentSerializer, cancellationToken);
|
2021-01-29 15:16:45 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find workflow instances blocked on an HttpRequestReceived activity and a matching Path and Method.
|
|
|
|
|
var triggerWithMethod = new HttpRequestReceivedBookmark
|
|
|
|
|
{
|
|
|
|
|
Path = ((string) path).ToLowerInvariant(),
|
|
|
|
|
Method = method.ToLowerInvariant(),
|
|
|
|
|
CorrelationId = correlationId?.ToLowerInvariant()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var triggerWithoutMethod = new HttpRequestReceivedBookmark
|
|
|
|
|
{
|
|
|
|
|
Path = ((string) path).ToLowerInvariant(),
|
|
|
|
|
CorrelationId = correlationId?.ToLowerInvariant()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var triggers = new[] { triggerWithMethod, triggerWithoutMethod };
|
2021-03-01 20:10:40 +00:00
|
|
|
var results = await bookmarkFinder.FindBookmarksAsync<HttpEndpoint>(triggers, TenantId, cancellationToken).ToList();
|
2020-10-17 07:15:46 +00:00
|
|
|
|
2020-10-30 20:55:27 +00:00
|
|
|
if (!results.Any())
|
|
|
|
|
{
|
2020-10-17 10:49:41 +00:00
|
|
|
await _next(httpContext);
|
2020-10-30 20:55:27 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (results.Count > 1)
|
|
|
|
|
{
|
2021-01-29 15:16:45 +00:00
|
|
|
httpContext.Response.StatusCode = (int) HttpStatusCode.BadRequest;
|
2020-10-30 20:55:27 +00:00
|
|
|
await httpContext.Response.WriteAsync("Request matches multiple workflows.", cancellationToken);
|
|
|
|
|
}
|
2021-01-30 11:47:50 +00:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var result = results.First();
|
|
|
|
|
var workflowInstance = await workflowInstanceStore.FindByIdAsync(result.WorkflowInstanceId, cancellationToken);
|
2020-10-30 20:55:27 +00:00
|
|
|
|
2021-01-30 11:47:50 +00:00
|
|
|
if (workflowInstance == null)
|
|
|
|
|
{
|
|
|
|
|
await _next(httpContext);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-10-30 20:55:27 +00:00
|
|
|
|
2021-01-30 11:47:50 +00:00
|
|
|
workflowInstance = await workflowRunner.RunWorkflowAsync(workflowInstance, result.ActivityId, cancellationToken: cancellationToken);
|
|
|
|
|
|
|
|
|
|
await HandleWorkflowInstanceResponseAsync(httpContext, workflowInstance, contentSerializer, cancellationToken);
|
2020-10-30 20:55:27 +00:00
|
|
|
}
|
2021-01-30 11:47:50 +00:00
|
|
|
}
|
2020-10-30 20:55:27 +00:00
|
|
|
|
2021-01-30 11:47:50 +00:00
|
|
|
private async Task HandleWorkflowInstanceResponseAsync(HttpContext httpContext, WorkflowInstance workflowInstance, IContentSerializer contentSerializer, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
if (workflowInstance.WorkflowStatus == WorkflowStatus.Faulted)
|
|
|
|
|
{
|
|
|
|
|
httpContext.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
|
|
|
|
|
httpContext.Response.ContentType = "application/json";
|
|
|
|
|
|
|
|
|
|
var model = new
|
|
|
|
|
{
|
|
|
|
|
WorkflowInstanceId = workflowInstance.Id,
|
|
|
|
|
WorkflowStatus = workflowInstance.WorkflowStatus,
|
|
|
|
|
Fault = workflowInstance.Fault
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await httpContext.Response.WriteAsync(contentSerializer.Serialize(model), cancellationToken);
|
|
|
|
|
}
|
2021-01-29 15:16:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<IEnumerable<(IWorkflowBlueprint WorkflowBlueprint, IActivityBlueprint ActivityBlueprint, PathString Path, string? Method)>> FindHttpWorkflowBlueprints(
|
|
|
|
|
IWorkflowRegistry workflowRegistry,
|
|
|
|
|
IWorkflowBlueprintReflector workflowBlueprintReflector,
|
2021-01-30 11:47:50 +00:00
|
|
|
IServiceProvider serviceProvider,
|
2021-01-29 15:16:45 +00:00
|
|
|
CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
// Find workflows starting with HttpRequestReceived.
|
2021-01-30 19:11:08 +00:00
|
|
|
var workflows = await workflowRegistry.ListAsync(cancellationToken);
|
2021-01-29 15:16:45 +00:00
|
|
|
|
|
|
|
|
var httpWorkflows =
|
|
|
|
|
from workflow in workflows
|
|
|
|
|
where workflow.TenantId == TenantId
|
2021-03-01 20:10:40 +00:00
|
|
|
from activity in workflow.GetStartActivities<HttpEndpoint>()
|
2021-01-29 15:16:45 +00:00
|
|
|
select (workflow, activity);
|
|
|
|
|
|
|
|
|
|
var matches = new List<(IWorkflowBlueprint, IActivityBlueprint, PathString, string?)>();
|
2020-10-30 20:55:27 +00:00
|
|
|
|
2021-01-29 15:16:45 +00:00
|
|
|
foreach (var httpWorkflow in httpWorkflows)
|
|
|
|
|
{
|
|
|
|
|
var workflow = httpWorkflow.workflow;
|
|
|
|
|
var activity = httpWorkflow.activity;
|
2021-01-30 11:47:50 +00:00
|
|
|
var workflowWrapper = await workflowBlueprintReflector.ReflectAsync(serviceProvider, workflow, cancellationToken);
|
2021-03-01 20:10:40 +00:00
|
|
|
var activityWrapper = workflowWrapper.GetActivity<HttpEndpoint>(activity.Id)!;
|
2021-01-29 15:16:45 +00:00
|
|
|
var path = await activityWrapper.GetPropertyValueAsync(x => x.Path, cancellationToken);
|
|
|
|
|
var method = await activityWrapper.GetPropertyValueAsync(x => x.Method, cancellationToken);
|
|
|
|
|
|
|
|
|
|
matches.Add((workflow, activity, path, method));
|
2020-10-30 20:55:27 +00:00
|
|
|
}
|
2021-01-29 15:16:45 +00:00
|
|
|
|
|
|
|
|
return matches;
|
2020-10-17 07:15:46 +00:00
|
|
|
}
|
2020-12-24 13:30:55 +00:00
|
|
|
|
|
|
|
|
private static bool AreSame(string a, string b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
|
2020-10-17 07:15:46 +00:00
|
|
|
}
|
|
|
|
|
}
|