diff --git a/src/runner/Synapse.Runner/Program.cs b/src/runner/Synapse.Runner/Program.cs index 48662b4a..a42ba7d9 100644 --- a/src/runner/Synapse.Runner/Program.cs +++ b/src/runner/Synapse.Runner/Program.cs @@ -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(); services.AddSingleton(provider => provider.GetRequiredService()); services.AddSingleton(provider => provider.GetRequiredService()); diff --git a/src/runner/Synapse.Runner/RunnerDefaults.cs b/src/runner/Synapse.Runner/RunnerDefaults.cs index 0195a86c..b7dbdcc4 100644 --- a/src/runner/Synapse.Runner/RunnerDefaults.cs +++ b/src/runner/Synapse.Runner/RunnerDefaults.cs @@ -19,6 +19,19 @@ namespace Synapse; public static class RunnerDefaults { + /// + /// Exposes constants about s + /// + public static class HttpClients + { + + /// + /// Gets the name of the configured to not automatically allow redirects + /// + public const string NoRedirect = "no-redirect"; + + } + /// /// Exposes constants about the Synapse Runner command line /// diff --git a/src/runner/Synapse.Runner/Services/Executors/HttpCallExecutor.cs b/src/runner/Synapse.Runner/Services/Executors/HttpCallExecutor.cs index d4ad03c4..0520d40d 100644 --- a/src/runner/Synapse.Runner/Services/Executors/HttpCallExecutor.cs +++ b/src/runner/Synapse.Runner/Services/Executors/HttpCallExecutor.cs @@ -40,9 +40,9 @@ public class HttpCallExecutor(IServiceProvider serviceProvider, ILogger - /// Gets the service used to perform HTTP requests + /// Gets the service used to create s /// - protected HttpClient HttpClient { get; } = httpClientFactory.CreateClient(); + protected IHttpClientFactory HttpClientFactory { get; } = httpClientFactory; /// /// Gets the definition of the http call to perform @@ -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(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) { @@ -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; @@ -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(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(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); @@ -185,18 +194,4 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken await this.SetResultAsync(result, this.Task.Definition.Then, cancellationToken).ConfigureAwait(false); } - /// - protected override async ValueTask DisposeAsync(bool disposing) - { - await base.DisposeAsync(disposing).ConfigureAwait(false); - this.HttpClient.Dispose(); - } - - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - this.HttpClient.Dispose(); - } - } diff --git a/src/runner/Synapse.Runner/Services/Executors/OpenApiCallExecutor.cs b/src/runner/Synapse.Runner/Services/Executors/OpenApiCallExecutor.cs index 179de207..3d6e3d5a 100644 --- a/src/runner/Synapse.Runner/Services/Executors/OpenApiCallExecutor.cs +++ b/src/runner/Synapse.Runner/Services/Executors/OpenApiCallExecutor.cs @@ -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;