| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | namespace IceRpc; |
| | 4 | |
|
| | 5 | | /// <summary>A pipeline is an invoker created from zero or more interceptors installed by calling |
| | 6 | | /// <see cref="Use(Func{IInvoker, IInvoker})"/>, and a final invoker installed by calling <see cref="Into(IInvoker)"/>. |
| | 7 | | /// Requests using this pipeline flow through the interceptors into the final invoker. The final invoker then sends the |
| | 8 | | /// request over a connection.</summary> |
| | 9 | | /// <example> |
| | 10 | | /// The following example demonstrates how an application would typically create the pipeline and use it as the invoker |
| | 11 | | /// for a Slice proxy or Protobuf client. |
| | 12 | | /// <code source="../../docfx/examples/IceRpc.Examples/PipelineExamples.cs" region="CreatingThePipeline" lang="csharp" / |
| | 13 | | /// Create a Slice proxy that uses the pipeline as its invoker. |
| | 14 | | /// <code source="../../docfx/examples/IceRpc.Examples/PipelineExamples.cs" region="CreateSliceProxy" lang="csharp" /> |
| | 15 | | /// Create a Protobuf client that uses the pipeline as its invoker. |
| | 16 | | /// <code source="../../docfx/examples/IceRpc.Examples/PipelineExamples.cs" region="CreateProtobufClient" lang="csharp" |
| | 17 | | /// You can easily create your own interceptor and add it to the pipeline. The next example shows how you can create an |
| | 18 | | /// interceptor using an <see cref="InlineInvoker"/> and add it to the pipeline with |
| | 19 | | /// <see cref="Use(Func{IInvoker, IInvoker})"/>. |
| | 20 | | /// <code source="../../docfx/examples/IceRpc.Examples/PipelineExamples.cs" region="UseWithInlineInterceptor" lang="csha |
| | 21 | | /// </example> |
| | 22 | | /// <seealso cref="Router"/> |
| | 23 | | /// <seealso href="https://docs.icerpc.dev/icerpc/invocation/invocation-pipeline"/> |
| | 24 | | public sealed class Pipeline : IInvoker |
| | 25 | | { |
| 35 | 26 | | private readonly Stack<Func<IInvoker, IInvoker>> _interceptorStack = new(); |
| | 27 | | private readonly Lazy<IInvoker> _invoker; |
| | 28 | | private IInvoker? _lastInvoker; |
| | 29 | |
|
| | 30 | | /// <summary>Constructs a pipeline.</summary> |
| 70 | 31 | | public Pipeline() => _invoker = new Lazy<IInvoker>(CreateInvokerPipeline); |
| | 32 | |
|
| | 33 | | /// <inheritdoc/> |
| | 34 | | public Task<IncomingResponse> InvokeAsync(OutgoingRequest request, CancellationToken cancellationToken = default) => |
| 114 | 35 | | _invoker.Value.InvokeAsync(request, cancellationToken); |
| | 36 | |
|
| | 37 | | /// <summary>Sets the last invoker of this pipeline. The pipeline flows into this invoker.</summary> |
| | 38 | | /// <param name="lastInvoker">The last invoker.</param> |
| | 39 | | /// <returns>This pipeline.</returns> |
| | 40 | | /// <exception cref="InvalidOperationException">Thrown if this method is called after the first call to |
| | 41 | | /// <see cref="InvokeAsync" />.</exception> |
| | 42 | | public Pipeline Into(IInvoker lastInvoker) |
| 36 | 43 | | { |
| 36 | 44 | | if (_invoker.IsValueCreated) |
| 0 | 45 | | { |
| 0 | 46 | | throw new InvalidOperationException($"{nameof(Into)} must be called before {nameof(InvokeAsync)}."); |
| | 47 | | } |
| | 48 | |
|
| 36 | 49 | | _lastInvoker = lastInvoker; |
| 36 | 50 | | return this; |
| 36 | 51 | | } |
| | 52 | |
|
| | 53 | | /// <summary>Installs an interceptor at the end of the pipeline.</summary> |
| | 54 | | /// <param name="interceptor">The interceptor to install.</param> |
| | 55 | | /// <returns>This pipeline.</returns> |
| | 56 | | /// <exception cref="InvalidOperationException">Thrown if this method is called after the first call to |
| | 57 | | /// <see cref="InvokeAsync" />.</exception> |
| | 58 | | public Pipeline Use(Func<IInvoker, IInvoker> interceptor) |
| 46 | 59 | | { |
| 46 | 60 | | if (_invoker.IsValueCreated) |
| 2 | 61 | | { |
| 2 | 62 | | throw new InvalidOperationException( |
| 2 | 63 | | $"The interceptors must be installed before the first call to {nameof(InvokeAsync)}."); |
| | 64 | | } |
| 44 | 65 | | _interceptorStack.Push(interceptor); |
| 44 | 66 | | return this; |
| 44 | 67 | | } |
| | 68 | |
|
| | 69 | | /// <summary>Creates a pipeline of invokers by starting with the last invoker installed. This method is called |
| | 70 | | /// by the first call to <see cref="InvokeAsync" />.</summary> |
| | 71 | | /// <returns>The pipeline of invokers.</returns> |
| | 72 | | private IInvoker CreateInvokerPipeline() |
| 34 | 73 | | { |
| 34 | 74 | | if (_lastInvoker is null) |
| 0 | 75 | | { |
| 0 | 76 | | throw new InvalidOperationException( |
| 0 | 77 | | $"{nameof(Into)} must be called before calling {nameof(InvokeAsync)} on a Pipeline."); |
| | 78 | | } |
| | 79 | |
|
| 34 | 80 | | IInvoker pipeline = _lastInvoker; |
| | 81 | |
|
| 190 | 82 | | foreach (Func<IInvoker, IInvoker> interceptor in _interceptorStack) |
| 44 | 83 | | { |
| 44 | 84 | | pipeline = interceptor(pipeline); |
| 44 | 85 | | } |
| 34 | 86 | | _interceptorStack.Clear(); // we no longer need these functions |
| 34 | 87 | | return pipeline; |
| 34 | 88 | | } |
| | 89 | | } |