| | | 1 | | namespace Kestrun.Callback; |
| | | 2 | | /// <summary> |
| | | 3 | | /// Sender for performing callback requests. |
| | | 4 | | /// </summary> |
| | | 5 | | /// <remarks> |
| | | 6 | | /// Initializes a new instance of the <see cref="HttpCallbackSender"/> class. |
| | | 7 | | /// </remarks> |
| | | 8 | | /// <param name="http"> The HTTP client to use for sending requests.</param> |
| | | 9 | | /// <param name="signer"> The optional callback signer for signing requests.</param> |
| | 5 | 10 | | public sealed class HttpCallbackSender(HttpClient http, ICallbackSigner? signer = null) : ICallbackSender |
| | | 11 | | { |
| | 5 | 12 | | private readonly HttpClient _http = http; |
| | 5 | 13 | | private readonly ICallbackSigner? _signer = signer; // optional |
| | | 14 | | |
| | | 15 | | /// <summary> |
| | | 16 | | /// Sends a callback request via HTTP. |
| | | 17 | | /// </summary> |
| | | 18 | | /// <param name="r"> The callback request to send.</param> |
| | | 19 | | /// <param name="ct"> The cancellation token.</param> |
| | | 20 | | /// <returns> A task that represents the asynchronous operation, containing the callback result.</returns> |
| | | 21 | | public async Task<CallbackResult> SendAsync(CallbackRequest r, CancellationToken ct) |
| | | 22 | | { |
| | 5 | 23 | | using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); |
| | 5 | 24 | | cts.CancelAfter(r.Timeout); |
| | | 25 | | |
| | 5 | 26 | | using var msg = new HttpRequestMessage(new HttpMethod(r.HttpMethod), r.TargetUrl); |
| | | 27 | | |
| | 12 | 28 | | foreach (var kv in r.Headers) |
| | | 29 | | { |
| | 1 | 30 | | _ = msg.Headers.TryAddWithoutValidation(kv.Key, kv.Value); |
| | | 31 | | } |
| | | 32 | | |
| | 5 | 33 | | _ = msg.Headers.TryAddWithoutValidation("X-Correlation-Id", r.CorrelationId); |
| | 5 | 34 | | _ = msg.Headers.TryAddWithoutValidation("Idempotency-Key", r.IdempotencyKey); |
| | | 35 | | |
| | 5 | 36 | | if (r.Body != null) |
| | | 37 | | { |
| | 2 | 38 | | msg.Content = new ByteArrayContent(r.Body); |
| | 2 | 39 | | msg.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(r.ContentType); |
| | | 40 | | } |
| | 5 | 41 | | _signer?.Sign(msg, r); // HMAC signature etc. |
| | | 42 | | |
| | | 43 | | try |
| | | 44 | | { |
| | 5 | 45 | | using var resp = await _http.SendAsync(msg, HttpCompletionOption.ResponseHeadersRead, cts.Token); |
| | 3 | 46 | | var code = (int)resp.StatusCode; |
| | | 47 | | |
| | | 48 | | // treat 2xx as success |
| | 3 | 49 | | if (code is >= 200 and <= 299) |
| | | 50 | | { |
| | 2 | 51 | | return new CallbackResult(true, code, null, null, DateTimeOffset.UtcNow); |
| | | 52 | | } |
| | | 53 | | |
| | | 54 | | // read a small snippet for diagnostics (cap size) |
| | 1 | 55 | | var err = resp.ReasonPhrase; |
| | 1 | 56 | | return new CallbackResult(false, code, "HttpError", err, DateTimeOffset.UtcNow); |
| | | 57 | | } |
| | 1 | 58 | | catch (OperationCanceledException oce) when (!ct.IsCancellationRequested) |
| | | 59 | | { |
| | 1 | 60 | | return new CallbackResult(false, null, "Timeout", oce.Message, DateTimeOffset.UtcNow); |
| | | 61 | | } |
| | 1 | 62 | | catch (HttpRequestException hre) |
| | | 63 | | { |
| | 1 | 64 | | return new CallbackResult(false, null, "HttpRequestException", hre.Message, DateTimeOffset.UtcNow); |
| | | 65 | | } |
| | 5 | 66 | | } |
| | | 67 | | } |