|
| 1 | +// Copyright (c) 2026 dexpace and Omar Aljarrah. |
| 2 | +// Licensed under the MIT License. See LICENSE in the repository root for details. |
| 3 | + |
| 4 | +using Dexpace.Sdk.Core.Client; |
| 5 | +using Dexpace.Sdk.Core.Configuration; |
| 6 | +using Dexpace.Sdk.Core.Errors; |
| 7 | +using Dexpace.Sdk.Core.Http.Request; |
| 8 | +using Dexpace.Sdk.Core.Http.Response; |
| 9 | + |
| 10 | +namespace Dexpace.Sdk.Core.Pipeline; |
| 11 | + |
| 12 | +/// <summary> |
| 13 | +/// The entry point for sending an HTTP request through the configured policy chain. |
| 14 | +/// </summary> |
| 15 | +/// <remarks> |
| 16 | +/// <para> |
| 17 | +/// Instances are created exclusively by <see cref="PipelineBuilder.Build"/>. The pipeline is |
| 18 | +/// immutable after construction: the sorted policy array and transport are captured at build time. |
| 19 | +/// </para> |
| 20 | +/// <para> |
| 21 | +/// <b>Sync bridge.</b> <see cref="Send"/> blocks the calling thread by driving the async chain |
| 22 | +/// synchronously via <c>GetAwaiter().GetResult()</c>. Callers on a thread pool should prefer |
| 23 | +/// <see cref="SendAsync"/> to avoid thread starvation. |
| 24 | +/// </para> |
| 25 | +/// </remarks> |
| 26 | +public sealed class HttpPipeline |
| 27 | +{ |
| 28 | + private readonly HttpPipelinePolicy[] _policies; |
| 29 | + private readonly IAsyncHttpClient _transport; |
| 30 | + |
| 31 | + internal HttpPipeline(HttpPipelinePolicy[] policies, IAsyncHttpClient transport) |
| 32 | + { |
| 33 | + _policies = policies; |
| 34 | + _transport = transport; |
| 35 | + } |
| 36 | + |
| 37 | + /// <summary> |
| 38 | + /// Asynchronously sends <paramref name="request"/> through the pipeline and returns the |
| 39 | + /// response produced by the terminal transport. |
| 40 | + /// </summary> |
| 41 | + /// <param name="request">The request to send.</param> |
| 42 | + /// <param name="options">Client options that apply to this call.</param> |
| 43 | + /// <param name="cancellationToken">An optional token to cancel the call.</param> |
| 44 | + /// <returns> |
| 45 | + /// A <see cref="ValueTask{TResult}"/> that completes with the <see cref="Response"/> once |
| 46 | + /// the pipeline chain has finished. |
| 47 | + /// </returns> |
| 48 | + /// <exception cref="PipelineAbortedException"> |
| 49 | + /// No policy or the transport produced a <see cref="Response"/> by the time the chain |
| 50 | + /// completed (i.e. the pipeline was short-circuited without setting a response). |
| 51 | + /// </exception> |
| 52 | + public async ValueTask<Response> SendAsync( |
| 53 | + Request request, |
| 54 | + DexpaceClientOptions options, |
| 55 | + CancellationToken cancellationToken = default) |
| 56 | + { |
| 57 | + ArgumentNullException.ThrowIfNull(request); |
| 58 | + ArgumentNullException.ThrowIfNull(options); |
| 59 | + |
| 60 | + var context = new PipelineContext(request, options, cancellationToken); |
| 61 | + await new PipelineRunner(_policies, 0, _transport).RunAsync(context).ConfigureAwait(false); |
| 62 | + |
| 63 | + return context.Response |
| 64 | + ?? throw new PipelineAbortedException( |
| 65 | + "The pipeline completed without producing a response."); |
| 66 | + } |
| 67 | + |
| 68 | + /// <summary> |
| 69 | + /// Synchronously sends <paramref name="request"/> through the pipeline and returns the |
| 70 | + /// response. Blocks the calling thread until the async chain completes. |
| 71 | + /// </summary> |
| 72 | + /// <param name="request">The request to send.</param> |
| 73 | + /// <param name="options">Client options that apply to this call.</param> |
| 74 | + /// <param name="cancellationToken">An optional token to cancel the call.</param> |
| 75 | + /// <returns>The <see cref="Response"/> produced by the pipeline.</returns> |
| 76 | + /// <exception cref="PipelineAbortedException"> |
| 77 | + /// The pipeline completed without producing a response. |
| 78 | + /// </exception> |
| 79 | + public Response Send( |
| 80 | + Request request, |
| 81 | + DexpaceClientOptions options, |
| 82 | + CancellationToken cancellationToken = default) => |
| 83 | + SendAsync(request, options, cancellationToken).AsTask().GetAwaiter().GetResult(); |
| 84 | +} |
0 commit comments