elsa-core/src/activities/Elsa.Activities.Http/Activities/HttpRequestAction.cs
2019-07-07 14:20:42 +02:00

185 lines
7.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Elsa.Activities.Http.Models;
using Elsa.Activities.Http.Services;
using Elsa.Core.Expressions;
using Elsa.Core.Extensions;
using Elsa.Core.Services;
using Elsa.Results;
using Elsa.Services;
using Elsa.Services.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace Elsa.Activities.Http.Activities
{
public class HttpRequestAction : Activity
{
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
private readonly HttpClient httpClient;
private readonly IEnumerable<IContentFormatter> contentFormatters;
public HttpRequestAction(
IWorkflowExpressionEvaluator expressionEvaluator,
IHttpClientFactory httpClientFactory,
IEnumerable<IContentFormatter> contentFormatters)
{
this.expressionEvaluator = expressionEvaluator;
httpClient = httpClientFactory.CreateClient(nameof(HttpRequestAction));
this.contentFormatters = contentFormatters;
}
/// <summary>
/// The URL to invoke.
/// </summary>
public WorkflowExpression<Uri> Url
{
get
{
var url = GetState<string>();
if (!string.IsNullOrWhiteSpace(url) && Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri))
return new WorkflowExpression<Uri>(PlainTextEvaluator.SyntaxName, uri.ToString());
return new WorkflowExpression<Uri>(PlainTextEvaluator.SyntaxName, "");
}
set => SetState(value.ToString());
}
/// <summary>
/// The HTTP method to use.
/// </summary>
public string Method
{
get => GetState(() => "GET");
set => SetState(value);
}
/// <summary>
/// The body to send along with the request.
/// </summary>
public WorkflowExpression<string> Body
{
get => GetState(() => new WorkflowExpression<string>(PlainTextEvaluator.SyntaxName, ""));
set => SetState(value);
}
/// <summary>
/// The Content Type header to send along with the request body.
/// </summary>
public WorkflowExpression<string> ContentType
{
get => GetState(() => new WorkflowExpression<string>(PlainTextEvaluator.SyntaxName, ""));
set => SetState(value);
}
/// <summary>
/// The headers to send along with the request.
/// </summary>
public WorkflowExpression<string> RequestHeaders
{
get => GetState(() => new WorkflowExpression<string>(PlainTextEvaluator.SyntaxName, ""));
set => SetState(value);
}
/// <summary>
/// A list of HTTP status codes this activity can handle.
/// </summary>
public HashSet<int> SupportedStatusCodes
{
get => GetState(() => new HashSet<int> { 200 });
set => SetState(value);
}
protected override async Task<ActivityExecutionResult> OnExecuteAsync(WorkflowExecutionContext workflowContext,
CancellationToken cancellationToken)
{
var request = await CreateRequestAsync(workflowContext, cancellationToken);
var response = await httpClient.SendAsync(request, cancellationToken);
var content = response.Content != null ? await response.Content.ReadAsStringAsync() : default(string);
var contentType = response.Content?.Headers.ContentType.MediaType;
var formatter = SelectContentFormatter(contentType);
var responseModel = new HttpResponseModel
{
StatusCode = response.StatusCode,
Headers = new HeaderDictionary(response.Headers.ToDictionary(x => x.Key, x => new StringValues(x.Value.ToArray()))),
Content = content,
FormattedContent = await formatter.ParseAsync(content, contentType)
};
workflowContext.SetLastResult(responseModel);
var statusEndpoint = ((int) response.StatusCode).ToString();
return Outcomes(new[] { OutcomeNames.Done, statusEndpoint });
}
private IContentFormatter SelectContentFormatter(string contentType)
{
var formatters = contentFormatters.OrderByDescending(x => x.Priority).ToList();
return formatters.FirstOrDefault(x => x.SupportedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase)) ?? formatters.Last();
}
private async Task<HttpRequestMessage> CreateRequestAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
var methodSupportsBody = GetMethodSupportsBody(Method);
var uri = await expressionEvaluator.EvaluateAsync(Url, workflowContext, cancellationToken);
var request = new HttpRequestMessage(new HttpMethod(Method), uri);
var requestHeaders = await ParseRequestHeadersAsync(workflowContext, cancellationToken);
if (methodSupportsBody)
{
var body = await expressionEvaluator.EvaluateAsync(Body, workflowContext, cancellationToken);
var contentType = await expressionEvaluator.EvaluateAsync(ContentType, workflowContext, cancellationToken);
if (!string.IsNullOrWhiteSpace(body))
{
request.Content = new StringContent(body, Encoding.UTF8, contentType);
}
}
foreach (var header in requestHeaders)
{
request.Headers.Add(header.Key, header.Value.AsEnumerable());
}
return request;
}
private async Task<IHeaderDictionary> ParseRequestHeadersAsync(WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
{
var headersText = await expressionEvaluator.EvaluateAsync(RequestHeaders, workflowContext, cancellationToken);
var headers = new HeaderDictionary();
if (headersText != null)
{
var headersQuery =
from line in Regex.Split(headersText, "\\n", RegexOptions.Multiline)
let pair = line.Split(':', '=')
select new KeyValuePair<string, string>(pair[0], pair[1]);
foreach (var header in headersQuery)
{
var headerValueExpression = new WorkflowExpression<string>(RequestHeaders.Syntax, header.Value);
var headerValue = await expressionEvaluator.EvaluateAsync(headerValueExpression, workflowContext, cancellationToken);
headers.Add(header.Key, headerValue);
}
}
return headers;
}
private bool GetMethodSupportsBody(string method)
{
var methods = new[] { "POST", "PUT", "PATCH" };
return methods.Contains(method, StringComparer.InvariantCultureIgnoreCase);
}
}
}