Skip to content

Commit

Permalink
feat(Runner): Update the HttpCallProcessor and `OpenApiCallProcesso…
Browse files Browse the repository at this point in the history
…r` to throw when a unexpected 3.x.x is returned by the remote server

Signed-off-by: Charles d'Avernas <[email protected]>
  • Loading branch information
cdavernas committed Jan 10, 2025
1 parent 7c158a6 commit be38824
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 23 deletions.
4 changes: 4 additions & 0 deletions src/runner/Synapse.Runner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
services.AddPythonScriptExecutor();
services.AddAsyncApi();
services.AddAsyncApiClient(options => options.AddAllBindingHandlers());
services.AddHttpClient(RunnerDefaults.HttpClients.NoRedirect, _ => { }).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
{
AllowAutoRedirect = false
});
services.AddSingleton<SecretsManager>();
services.AddSingleton<ISecretsManager>(provider => provider.GetRequiredService<SecretsManager>());
services.AddSingleton<IHostedService>(provider => provider.GetRequiredService<SecretsManager>());
Expand Down
13 changes: 13 additions & 0 deletions src/runner/Synapse.Runner/RunnerDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ namespace Synapse;
public static class RunnerDefaults
{

/// <summary>
/// Exposes constants about <see cref="HttpClient"/>s
/// </summary>
public static class HttpClients
{

/// <summary>
/// Gets the name of the <see cref="HttpClient"/> configured to not automatically allow redirects
/// </summary>
public const string NoRedirect = "no-redirect";

}

/// <summary>
/// Exposes constants about the Synapse Runner command line
/// </summary>
Expand Down
39 changes: 17 additions & 22 deletions src/runner/Synapse.Runner/Services/Executors/HttpCallExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public class HttpCallExecutor(IServiceProvider serviceProvider, ILogger<HttpCall
protected ISerializerProvider SerializerProvider { get; } = serializerProvider;

/// <summary>
/// Gets the service used to perform HTTP requests
/// Gets the service used to create <see cref="HttpClient"/>s
/// </summary>
protected HttpClient HttpClient { get; } = httpClientFactory.CreateClient();
protected IHttpClientFactory HttpClientFactory { get; } = httpClientFactory;

/// <summary>
/// Gets the definition of the http call to perform
Expand All @@ -56,7 +56,8 @@ protected override async Task DoInitializeAsync(CancellationToken cancellationTo
{
this.Http = (HttpCallDefinition)this.JsonSerializer.Convert(this.Task.Definition.With, typeof(HttpCallDefinition))!;
var authentication = this.Http.Endpoint.Authentication == null ? null : await this.Task.Workflow.Expressions.EvaluateAsync<AuthenticationPolicyDefinition>(this.Http.Endpoint.Authentication, this.Task.Input, this.Task.Arguments, cancellationToken).ConfigureAwait(false);
await this.HttpClient.ConfigureAuthenticationAsync(authentication, this.ServiceProvider, this.Task.Workflow.Definition, cancellationToken).ConfigureAwait(false);
using var httpClient = this.HttpClientFactory.CreateClient();
await httpClient.ConfigureAuthenticationAsync(authentication, this.ServiceProvider, this.Task.Workflow.Definition, cancellationToken).ConfigureAwait(false);
}
catch(Exception ex)
{
Expand All @@ -76,9 +77,7 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken
{
if (this.Http == null) throw new InvalidOperationException("The executor must be initialized before execution");
ISerializer? serializer;
var defaultMediaType = this.Http.Body is string
? MediaTypeNames.Text.Plain
: MediaTypeNames.Application.Json;
var defaultMediaType = this.Http.Body is string ? MediaTypeNames.Text.Plain : MediaTypeNames.Application.Json;
if ((this.Http.Headers?.TryGetValue("Content-Type", out var mediaType) != true && this.Http.Headers?.TryGetValue("Content-Type", out mediaType) != true) || string.IsNullOrWhiteSpace(mediaType)) mediaType = defaultMediaType;
else mediaType = mediaType.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
var requestContent = (HttpContent?)null;
Expand Down Expand Up @@ -133,9 +132,19 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken
}
var uri = StringFormatter.NamedFormat(this.Http.EndpointUri.OriginalString, this.Task.Input.ToDictionary());
if (uri.IsRuntimeExpression()) uri = await this.Task.Workflow.Expressions.EvaluateAsync<string>(uri, this.Task.Input, this.GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false);
using var httpClient = this.Http.Redirect ? this.HttpClientFactory.CreateClient() : this.HttpClientFactory.CreateClient(RunnerDefaults.HttpClients.NoRedirect); ;
using var request = new HttpRequestMessage(new HttpMethod(this.Http.Method), uri) { Content = requestContent };
using var response = await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode) //todo: could be configurable on HTTP call?
if (this.Http.Headers != null)
{
foreach(var header in this.Http.Headers)
{
var headerValue = header.Value;
if (headerValue.IsRuntimeExpression()) headerValue = await this.Task.Workflow.Expressions.EvaluateAsync<string>(headerValue, this.Task.Input, this.GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false);
request.Headers.TryAddWithoutValidation(header.Key, headerValue);
}
}
using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
var detail = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
this.Logger.LogError("Failed to request '{method} {uri}'. The remote server responded with a non-success status code '{statusCode}'.", this.Http.Method, uri, response.StatusCode);
Expand Down Expand Up @@ -185,18 +194,4 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken
await this.SetResultAsync(result, this.Task.Definition.Then, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc/>
protected override async ValueTask DisposeAsync(bool disposing)
{
await base.DisposeAsync(disposing).ConfigureAwait(false);
this.HttpClient.Dispose();
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
this.HttpClient.Dispose();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken
request.Content.Headers.ContentType.MediaType = content.MediaType;
}
}
using var httpClient = this.HttpClientFactory.CreateClient();
using var httpClient = this.OpenApi.Redirect ? this.HttpClientFactory.CreateClient() : this.HttpClientFactory.CreateClient(RunnerDefaults.HttpClients.NoRedirect);
await httpClient.ConfigureAuthenticationAsync(this.OpenApi.Authentication, this.ServiceProvider, this.Task.Workflow.Definition, cancellationToken).ConfigureAwait(false);
using var response = await httpClient.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.ServiceUnavailable) continue;
Expand Down

0 comments on commit be38824

Please sign in to comment.