Skip to content

Commit 3b4ed23

Browse files
authored
Merge pull request #476 from serverlessworkflow/feat-http-openapi-redirect
Update the `HttpCallProcessor` and `OpenApiCallProcessor` to throw when a unexpected 3.x.x is returned by the remote server
2 parents 7c158a6 + be38824 commit 3b4ed23

File tree

4 files changed

+35
-23
lines changed

4 files changed

+35
-23
lines changed

src/runner/Synapse.Runner/Program.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@
102102
services.AddPythonScriptExecutor();
103103
services.AddAsyncApi();
104104
services.AddAsyncApiClient(options => options.AddAllBindingHandlers());
105+
services.AddHttpClient(RunnerDefaults.HttpClients.NoRedirect, _ => { }).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
106+
{
107+
AllowAutoRedirect = false
108+
});
105109
services.AddSingleton<SecretsManager>();
106110
services.AddSingleton<ISecretsManager>(provider => provider.GetRequiredService<SecretsManager>());
107111
services.AddSingleton<IHostedService>(provider => provider.GetRequiredService<SecretsManager>());

src/runner/Synapse.Runner/RunnerDefaults.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ namespace Synapse;
1919
public static class RunnerDefaults
2020
{
2121

22+
/// <summary>
23+
/// Exposes constants about <see cref="HttpClient"/>s
24+
/// </summary>
25+
public static class HttpClients
26+
{
27+
28+
/// <summary>
29+
/// Gets the name of the <see cref="HttpClient"/> configured to not automatically allow redirects
30+
/// </summary>
31+
public const string NoRedirect = "no-redirect";
32+
33+
}
34+
2235
/// <summary>
2336
/// Exposes constants about the Synapse Runner command line
2437
/// </summary>

src/runner/Synapse.Runner/Services/Executors/HttpCallExecutor.cs

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ public class HttpCallExecutor(IServiceProvider serviceProvider, ILogger<HttpCall
4040
protected ISerializerProvider SerializerProvider { get; } = serializerProvider;
4141

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

4747
/// <summary>
4848
/// Gets the definition of the http call to perform
@@ -56,7 +56,8 @@ protected override async Task DoInitializeAsync(CancellationToken cancellationTo
5656
{
5757
this.Http = (HttpCallDefinition)this.JsonSerializer.Convert(this.Task.Definition.With, typeof(HttpCallDefinition))!;
5858
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);
59-
await this.HttpClient.ConfigureAuthenticationAsync(authentication, this.ServiceProvider, this.Task.Workflow.Definition, cancellationToken).ConfigureAwait(false);
59+
using var httpClient = this.HttpClientFactory.CreateClient();
60+
await httpClient.ConfigureAuthenticationAsync(authentication, this.ServiceProvider, this.Task.Workflow.Definition, cancellationToken).ConfigureAwait(false);
6061
}
6162
catch(Exception ex)
6263
{
@@ -76,9 +77,7 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken
7677
{
7778
if (this.Http == null) throw new InvalidOperationException("The executor must be initialized before execution");
7879
ISerializer? serializer;
79-
var defaultMediaType = this.Http.Body is string
80-
? MediaTypeNames.Text.Plain
81-
: MediaTypeNames.Application.Json;
80+
var defaultMediaType = this.Http.Body is string ? MediaTypeNames.Text.Plain : MediaTypeNames.Application.Json;
8281
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;
8382
else mediaType = mediaType.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Trim();
8483
var requestContent = (HttpContent?)null;
@@ -133,9 +132,19 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken
133132
}
134133
var uri = StringFormatter.NamedFormat(this.Http.EndpointUri.OriginalString, this.Task.Input.ToDictionary());
135134
if (uri.IsRuntimeExpression()) uri = await this.Task.Workflow.Expressions.EvaluateAsync<string>(uri, this.Task.Input, this.GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false);
135+
using var httpClient = this.Http.Redirect ? this.HttpClientFactory.CreateClient() : this.HttpClientFactory.CreateClient(RunnerDefaults.HttpClients.NoRedirect); ;
136136
using var request = new HttpRequestMessage(new HttpMethod(this.Http.Method), uri) { Content = requestContent };
137-
using var response = await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
138-
if (!response.IsSuccessStatusCode) //todo: could be configurable on HTTP call?
137+
if (this.Http.Headers != null)
138+
{
139+
foreach(var header in this.Http.Headers)
140+
{
141+
var headerValue = header.Value;
142+
if (headerValue.IsRuntimeExpression()) headerValue = await this.Task.Workflow.Expressions.EvaluateAsync<string>(headerValue, this.Task.Input, this.GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false);
143+
request.Headers.TryAddWithoutValidation(header.Key, headerValue);
144+
}
145+
}
146+
using var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
147+
if (!response.IsSuccessStatusCode)
139148
{
140149
var detail = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
141150
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
185194
await this.SetResultAsync(result, this.Task.Definition.Then, cancellationToken).ConfigureAwait(false);
186195
}
187196

188-
/// <inheritdoc/>
189-
protected override async ValueTask DisposeAsync(bool disposing)
190-
{
191-
await base.DisposeAsync(disposing).ConfigureAwait(false);
192-
this.HttpClient.Dispose();
193-
}
194-
195-
/// <inheritdoc/>
196-
protected override void Dispose(bool disposing)
197-
{
198-
base.Dispose(disposing);
199-
this.HttpClient.Dispose();
200-
}
201-
202197
}

src/runner/Synapse.Runner/Services/Executors/OpenApiCallExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ protected override async Task DoExecuteAsync(CancellationToken cancellationToken
237237
request.Content.Headers.ContentType.MediaType = content.MediaType;
238238
}
239239
}
240-
using var httpClient = this.HttpClientFactory.CreateClient();
240+
using var httpClient = this.OpenApi.Redirect ? this.HttpClientFactory.CreateClient() : this.HttpClientFactory.CreateClient(RunnerDefaults.HttpClients.NoRedirect);
241241
await httpClient.ConfigureAuthenticationAsync(this.OpenApi.Authentication, this.ServiceProvider, this.Task.Workflow.Definition, cancellationToken).ConfigureAwait(false);
242242
using var response = await httpClient.SendAsync(request, cancellationToken);
243243
if (response.StatusCode == HttpStatusCode.ServiceUnavailable) continue;

0 commit comments

Comments
 (0)