| | | 1 | | // Copyright (c) ZeroC, Inc. |
| | | 2 | | |
| | | 3 | | using IceRpc.Extensions.DependencyInjection; |
| | | 4 | | using IceRpc.Features; |
| | | 5 | | using ZeroC.Slice; |
| | | 6 | | |
| | | 7 | | namespace IceRpc.Deadline; |
| | | 8 | | |
| | | 9 | | /// <summary>Represents a middleware that decodes deadline fields into deadline features. When the decoded deadline |
| | | 10 | | /// expires, this middleware cancels the dispatch and returns an <see cref="OutgoingResponse" /> with status code |
| | | 11 | | /// <see cref="StatusCode.DeadlineExceeded" />.</summary> |
| | | 12 | | /// <seealso cref="DeadlineRouterExtensions"/> |
| | | 13 | | /// <seealso cref="DeadlineDispatcherBuilderExtensions"/> |
| | | 14 | | public class DeadlineMiddleware : IDispatcher |
| | | 15 | | { |
| | | 16 | | private readonly IDispatcher _next; |
| | | 17 | | |
| | | 18 | | /// <summary>Constructs a deadline middleware.</summary> |
| | | 19 | | /// <param name="next">The next dispatcher in the dispatch pipeline.</param> |
| | 4 | 20 | | public DeadlineMiddleware(IDispatcher next) => _next = next; |
| | | 21 | | |
| | | 22 | | /// <inheritdoc/> |
| | | 23 | | public ValueTask<OutgoingResponse> DispatchAsync( |
| | | 24 | | IncomingRequest request, |
| | | 25 | | CancellationToken cancellationToken = default) |
| | 2 | 26 | | { |
| | 2 | 27 | | TimeSpan? timeout = null; |
| | | 28 | | |
| | | 29 | | // not found returns default == DateTime.MinValue. |
| | 2 | 30 | | DateTime deadline = request.Fields.DecodeValue( |
| | 2 | 31 | | RequestFieldKey.Deadline, |
| | 4 | 32 | | (ref SliceDecoder decoder) => decoder.DecodeTimeStamp()); |
| | | 33 | | |
| | 2 | 34 | | if (deadline != DateTime.MinValue) |
| | 2 | 35 | | { |
| | 2 | 36 | | timeout = deadline - DateTime.UtcNow; |
| | | 37 | | |
| | 2 | 38 | | if (timeout <= TimeSpan.Zero) |
| | 0 | 39 | | { |
| | 0 | 40 | | return new(new OutgoingResponse( |
| | 0 | 41 | | request, |
| | 0 | 42 | | StatusCode.DeadlineExceeded, |
| | 0 | 43 | | "The request deadline has expired.")); |
| | | 44 | | } |
| | | 45 | | |
| | 2 | 46 | | request.Features = request.Features.With<IDeadlineFeature>(new DeadlineFeature(deadline)); |
| | 2 | 47 | | } |
| | | 48 | | |
| | 2 | 49 | | return timeout is null ? _next.DispatchAsync(request, cancellationToken) : PerformDispatchAsync(timeout.Value); |
| | | 50 | | |
| | | 51 | | async ValueTask<OutgoingResponse> PerformDispatchAsync(TimeSpan timeout) |
| | 2 | 52 | | { |
| | 2 | 53 | | using var timeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); |
| | 2 | 54 | | timeoutTokenSource.CancelAfter(timeout); |
| | | 55 | | |
| | | 56 | | try |
| | 2 | 57 | | { |
| | 2 | 58 | | return await _next.DispatchAsync(request, timeoutTokenSource.Token).ConfigureAwait(false); |
| | | 59 | | } |
| | 1 | 60 | | catch (OperationCanceledException exception) when (exception.CancellationToken == timeoutTokenSource.Token) |
| | 1 | 61 | | { |
| | 1 | 62 | | cancellationToken.ThrowIfCancellationRequested(); |
| | 1 | 63 | | return new OutgoingResponse(request, StatusCode.DeadlineExceeded, "The request deadline has expired."); |
| | | 64 | | } |
| | 2 | 65 | | } |
| | 2 | 66 | | } |
| | | 67 | | } |