Skip to content

Commit

Permalink
feat(server,connector): add possibility to ignore the connector response
Browse files Browse the repository at this point in the history
  • Loading branch information
thomashilzendegen authored and gingters committed Mar 19, 2024
1 parent 7f038c0 commit 3797392
Show file tree
Hide file tree
Showing 19 changed files with 383 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,22 @@ public static bool IsBodyContentOutsourced(this IClientRequest request)
/// <see cref="IClientRequest.RequestOriginId"/>.
/// </summary>
/// <param name="request">An <see cref="IClientRequest"/>.</param>
/// <param name="failureStatusCode">A <see cref="HttpStatusCode"/> which marks the target response as failed.</param>
/// <param name="httpStatusCode">A <see cref="HttpStatusCode"/>.</param>
/// <returns>The <see cref="TargetResponse"/>.</returns>
public static TargetResponse CreateResponse(this IClientRequest request, HttpStatusCode? failureStatusCode = null)
=> request.CreateResponse<TargetResponse>(failureStatusCode);
/// <remarks>If the <paramref name="httpStatusCode"/> is 4xx or 5xx, the request will be marked as failed.</remarks>
public static TargetResponse CreateResponse(this IClientRequest request, HttpStatusCode? httpStatusCode = null)
=> request.CreateResponse<TargetResponse>(httpStatusCode);

/// <summary>
/// Creates a pristine <see cref="ITargetResponse"/> prefilled with <see cref="IClientRequest.RequestId"/> and
/// <see cref="IClientRequest.RequestOriginId"/>.
/// </summary>
/// <param name="request">An <see cref="IClientRequest"/>.</param>
/// <param name="failureStatusCode">A <see cref="HttpStatusCode"/> which marks the target response as failed.</param>
/// <param name="httpStatusCode">A <see cref="HttpStatusCode"/>.</param>
/// <typeparam name="T">The type of response.</typeparam>
/// <returns>The <see cref="ITargetResponse"/>.</returns>
public static T CreateResponse<T>(this IClientRequest request, HttpStatusCode? failureStatusCode = null)
/// <remarks>If the <paramref name="httpStatusCode"/> is 4xx or 5xx, the request will be marked as failed.</remarks>
public static T CreateResponse<T>(this IClientRequest request, HttpStatusCode? httpStatusCode = null)
where T : ITargetResponse, new()
{
var response = new T()
Expand All @@ -47,11 +49,12 @@ public static T CreateResponse<T>(this IClientRequest request, HttpStatusCode? f
HttpHeaders = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase),
};

if (failureStatusCode == null) return response;
if (httpStatusCode == null || (int)httpStatusCode.GetValueOrDefault(HttpStatusCode.Continue) < 400)
return response;

response.OriginalBodySize = 0;
response.BodySize = 0;
response.HttpStatusCode = failureStatusCode.Value;
response.HttpStatusCode = httpStatusCode.Value;
response.RequestFailed = true;

return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ public class ClientRequest : IClientRequest

/// <inheritdoc />
public bool EnableTracing { get; set; }

/// <inheritdoc />
public bool DiscardConnectorResponse { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,10 @@ public interface IClientRequest
/// Enable tracing of this particular request.
/// </summary>
bool EnableTracing { get; set; }

/// <summary>
/// Enable discarding the connector response of this particular request.
/// </summary>
/// <remarks>Responses provided by an interceptor will still be returned to the client.</remarks>
bool DiscardConnectorResponse { get; set; }
}
37 changes: 31 additions & 6 deletions src/Thinktecture.Relay.Connector.Abstractions/Constants.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Net.Http;
using Thinktecture.Relay.Connector.Targets;

Expand All @@ -8,13 +9,15 @@ namespace Thinktecture.Relay.Connector;
/// </summary>
public static class Constants
{
public static readonly string AssemblyVersion = typeof(Constants).GetAssemblyVersion();

/// <summary>
/// The identity scopes.
/// </summary>
public const string RelayServerScopes = "connector";

/// <summary>
/// The id for the catch-all <see cref="IRelayTarget{TRequest,TResponse}"/>.
/// The id for the catch-all <see cref="IRelayTarget"/>.
/// </summary>
public const string RelayTargetCatchAllId = "** CATCH-ALL **";

Expand All @@ -39,30 +42,52 @@ public static class Constants
public static class HeaderNames
{
/// <summary>
/// The url to use for acknowledging a request by issuing as POST with an empty body to it.
/// Contains the url to use for acknowledging a request by issuing as POST with an empty body to it.
/// </summary>
/// <remarks>This will only be present when manual acknowledgement is needed.</remarks>
public const string AcknowledgeUrl = "RelayServer-AcknowledgeUrl";

/// <summary>
/// The unique id of the request.
/// Contains the unique id of the request.
/// </summary>
/// <remarks>This will only be present when tracing is enabled.</remarks>
public const string RequestId = "RelayServer-RequestId";

/// <summary>
/// The unique id of the origin receiving the request.
/// </summary>
public const string OriginId = "RelayServer-OriginId";
/// <remarks>This will only be present when tracing is enabled.</remarks>
public const string RequestOriginId = "RelayServer-RequestOriginId";

/// <summary>
/// The machine name of the connector handling the request.
/// Contains the machine name of the connector handling the request.
/// </summary>
/// <remarks>This will only be present when tracing is enabled.</remarks>
public const string ConnectorMachineName = "RelayServer-Connector-MachineName";

/// <summary>
/// The version of the connector handling the request.
/// Contains the version of the connector handling the request.
/// </summary>
/// <remarks>This will only be present when tracing is enabled.</remarks>
public const string ConnectorVersion = "RelayServer-Connector-Version";

/// <summary>
/// Contains the unique id of the origin to use for acknowledgement.
/// </summary>
/// <remarks>This will only be present when tracing is enabled.</remarks>
public const string AcknowledgeOriginId = "RelayServer-AcknowledgeOriginId";

/// <summary>
/// Contains the start timestamp of the target handling the request.
/// </summary>
/// <remarks>This will only be present when tracing is enabled.</remarks>
public const string TargetStart = "RelayServer-TargetStart";

/// <summary>
/// Contains the duration of the target handling the request.
/// </summary>
/// <remarks>This will only be present when tracing is enabled.</remarks>
public const string TargetDuration = "RelayServer-TargetDuration";
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class RelayConnectorBuilderExtensions
{
/// <summary>
/// Adds an <see cref="IRelayTarget{ClientRequest,TargetResponse}"/>.
/// Adds an <see cref="IRelayTarget"/>.
/// </summary>
/// <param name="builder">The <see cref="IRelayConnectorBuilder{ClientRequest,TargetResponse,AcknowledgeRequest}"/>.</param>
/// <param name="id">The unique id of the target.</param>
Expand All @@ -24,13 +24,12 @@ public static class RelayConnectorBuilderExtensions
/// <returns>The <see cref="IRelayConnectorBuilder{ClientRequest,TargetResponse,AcknowledgeRequest}"/>.</returns>
public static IRelayConnectorBuilder<ClientRequest, TargetResponse, AcknowledgeRequest> AddTarget<TTarget>(
this IRelayConnectorBuilder<ClientRequest, TargetResponse, AcknowledgeRequest> builder, string id,
TimeSpan? timeout = null,
params object[] parameters)
where TTarget : IRelayTarget<ClientRequest, TargetResponse>
TimeSpan? timeout = null, params object[] parameters)
where TTarget : IRelayTarget
=> builder.AddTarget<ClientRequest, TargetResponse, AcknowledgeRequest, TTarget>(id, timeout, parameters);

/// <summary>
/// Adds an <see cref="IRelayTarget{TRequest,TResponse}"/>.
/// Adds an <see cref="IRelayTarget"/>.
/// </summary>
/// <param name="builder">The <see cref="IRelayConnectorBuilder{TRequest,TResponse,TAcknowledge}"/>.</param>
/// <param name="id">The unique id of the target.</param>
Expand All @@ -41,13 +40,13 @@ public static IRelayConnectorBuilder<ClientRequest, TargetResponse, AcknowledgeR
/// <typeparam name="TTarget">The type of target.</typeparam>
/// <typeparam name="TAcknowledge">The type of acknowledge.</typeparam>
/// <returns>The <see cref="IRelayConnectorBuilder{TRequest,TResponse,TAcknowledge}"/>.</returns>
public static IRelayConnectorBuilder<TRequest, TResponse, TAcknowledge> AddTarget<TRequest, TResponse, TAcknowledge,
TTarget>(
this IRelayConnectorBuilder<TRequest, TResponse, TAcknowledge> builder, string id, TimeSpan? timeout = null,
params object[] parameters)
public static IRelayConnectorBuilder<TRequest, TResponse, TAcknowledge>
AddTarget<TRequest, TResponse, TAcknowledge, TTarget>(
this IRelayConnectorBuilder<TRequest, TResponse, TAcknowledge> builder, string id, TimeSpan? timeout = null,
params object[] parameters)
where TRequest : IClientRequest
where TResponse : ITargetResponse
where TTarget : IRelayTarget<TRequest, TResponse>
where TTarget : IRelayTarget
where TAcknowledge : IAcknowledgeRequest
{
builder.Services.Configure<RelayTargetOptions>(options => options.Targets.Add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,43 @@
namespace Thinktecture.Relay.Connector.Targets;

/// <summary>
/// An implementation of a target requesting the necessary information for a connector.
/// An implementation of a target.
/// </summary>
/// <remarks>This is just a marker interface.</remarks>
public interface IRelayTarget
{
}

/// <summary>
/// An implementation of a target executing logic triggered by a request.
/// </summary>
/// <typeparam name="TRequest">The type of request.</typeparam>
public interface IRelayTargetAction<in TRequest> : IRelayTarget
where TRequest : IClientRequest
{
/// <summary>
/// Called when the target should be executed.
/// </summary>
/// <param name="request">The client request.</param>
/// <param name="cancellationToken">
/// The token to monitor for cancellation requests. The default value is
/// <see cref="CancellationToken.None"/>.
/// </param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task HandleAsync(TRequest request, CancellationToken cancellationToken = default);
}

/// <inheritdoc />
public interface IRelayTargetAction : IRelayTargetAction<ClientRequest>
{
}

/// <summary>
/// An implementation of a target providing the necessary response information for a request.
/// </summary>
/// <typeparam name="TRequest">The type of request.</typeparam>
/// <typeparam name="TResponse">The type of response.</typeparam>
public interface IRelayTarget<in TRequest, TResponse>
public interface IRelayTargetFunc<in TRequest, TResponse> : IRelayTarget
where TRequest : IClientRequest
where TResponse : ITargetResponse
{
Expand All @@ -24,3 +56,8 @@ public interface IRelayTarget<in TRequest, TResponse>
/// <returns>A <see cref="Task"/> representing the asynchronous operation, which wraps the target response.</returns>
Task<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken = default);
}

/// <inheritdoc />
public interface IRelayTargetFunc : IRelayTargetFunc<ClientRequest, TargetResponse>
{
}
2 changes: 0 additions & 2 deletions src/Thinktecture.Relay.Connector/RelayConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ namespace Thinktecture.Relay.Connector;
/// <remarks>This is just a convenient class for holding the transient <see cref="IConnectorConnection"/>.</remarks>
public class RelayConnector
{
internal static readonly string AssemblyVersion = typeof(RelayConnector).GetAssemblyVersion();

private readonly IConnectorConnection _connection;

/// <summary>
Expand Down
43 changes: 36 additions & 7 deletions src/Thinktecture.Relay.Connector/Targets/ClientRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,22 @@ public async Task HandleAsync(TRequest request, CancellationToken cancellationTo
{
if (QueueWorker(request, cancellationToken)) return;

// we could not queue the work item

if (request.AcknowledgeMode == AcknowledgeMode.ConnectorReceived)
{
await AcknowledgeRequestAsync(request, false);
}

await DeliverResponseAsync(request.CreateResponse<TResponse>(HttpStatusCode.ServiceUnavailable),
request.EnableTracing);
await DeliverResponseAsync(request, request.CreateResponse<TResponse>(HttpStatusCode.ServiceUnavailable));
}

[LoggerMessage(10400, LogLevel.Debug, "Acknowledging request {RelayRequestId} on origin {OriginId}")]
partial void LogAcknowledgeRequest(Guid relayRequestId, Guid? originId);

[LoggerMessage(10403, LogLevel.Debug, "Discarding response for request {RelayRequestId}")]
partial void LogDiscardResponse(Guid relayRequestId);

/// <inheritdoc />
public async Task AcknowledgeRequestAsync(TRequest request, bool removeRequestBodyContent)
{
Expand All @@ -92,13 +96,19 @@ private async Task WorkerCallAsync(TRequest request, CancellationToken cancellat
if (request.EnableTracing)
{
request.HttpHeaders[Constants.HeaderNames.RequestId] = new[] { request.RequestId.ToString() };
request.HttpHeaders[Constants.HeaderNames.OriginId] = new[] { request.RequestOriginId.ToString() };
request.HttpHeaders[Constants.HeaderNames.RequestOriginId] = new[] { request.RequestOriginId.ToString() };
}

try
{
var response = await worker.HandleAsync(request, cancellationToken);
await DeliverResponseAsync(response, request.EnableTracing);
if (request.DiscardConnectorResponse)
{
LogDiscardResponse(request.RequestId);
return;
}

await DeliverResponseAsync(request, response);
}
catch (OperationCanceledException)
{
Expand All @@ -113,15 +123,34 @@ private async Task WorkerCallAsync(TRequest request, CancellationToken cancellat
[LoggerMessage(10402, LogLevel.Debug, "Delivering response for request {RelayRequestId}")]
partial void LogDeliverResponse(Guid relayRequestId);

private async Task DeliverResponseAsync(TResponse response, bool enableTracing)
private async Task DeliverResponseAsync(TRequest request, TResponse response)
{
LogDeliverResponse(response.RequestId);

if (enableTracing)
if (request.EnableTracing)
{
response.HttpHeaders ??= new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);

response.HttpHeaders[Constants.HeaderNames.ConnectorMachineName] = new[] { Environment.MachineName };
response.HttpHeaders[Constants.HeaderNames.ConnectorVersion] = new[] { RelayConnector.AssemblyVersion };
response.HttpHeaders[Constants.HeaderNames.ConnectorVersion] = new[] { Constants.AssemblyVersion };
response.HttpHeaders[Constants.HeaderNames.RequestId] = new[] { request.RequestId.ToString() };
response.HttpHeaders[Constants.HeaderNames.RequestOriginId] = new[] { request.RequestOriginId.ToString() };

if (request.AcknowledgeOriginId.HasValue)
{
response.HttpHeaders[Constants.HeaderNames.AcknowledgeOriginId] =
new[] { request.AcknowledgeOriginId.Value.ToString() };
}
if (response.RequestStart.HasValue)
{
response.HttpHeaders[Constants.HeaderNames.TargetStart] =
new[] { response.RequestStart.Value.ToString("R") };
}
if (response.RequestDuration.HasValue)
{
response.HttpHeaders[Constants.HeaderNames.TargetDuration] =
new[] { response.RequestDuration.Value.ToString("g") };
}
}

await _responseTransport.TransportAsync(response);
Expand Down
Loading

0 comments on commit 3797392

Please sign in to comment.