| | 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 | | } |