Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the HttpCallProcessor and OpenApiCallProcessor to throw when a unexpected 3.x.x is returned by the remote server #476

Merged
merged 1 commit into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading