• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Aldaviva / Unfucked / 19128584806

06 Nov 2025 07:47AM UTC coverage: 0.396% (-39.5%) from 39.923%
19128584806

push

github

Aldaviva
DateTime: added IsBefore and IsAfter for ZonedDateTime, not just OffsetDateTime. DI: Allow super registration for keyed services; fixed super registration of a hosted service causing an infinite recursion during injection. STUN: updated fallback server list, most of which have gone offline (including Google, confusingly); made HttpClient optional.

2 of 1605 branches covered (0.12%)

0 of 55 new or added lines in 2 files covered. (0.0%)

945 existing lines in 35 files now uncovered.

9 of 2272 relevant lines covered (0.4%)

0.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/HTTP/UnfuckedHttpClient.cs
1
using System.Net.Http.Headers;
2
using System.Reflection;
3
using Unfucked.HTTP.Config;
4
using Unfucked.HTTP.Exceptions;
5
#if NET8_0_OR_GREATER
6
using Unfucked.HTTP.Filters;
7
#endif
8

9
namespace Unfucked.HTTP;
10

11
/// <summary>
12
/// Interface for <see cref="UnfuckedHttpClient"/>, an improved subclass of <see cref="HttpClient"/>.
13
/// </summary>
14
public interface IHttpClient: IDisposable {
15

16
    /// <summary>
17
    /// The HTTP message handler, such as an <see cref="UnfuckedHttpHandler"/>.
18
    /// </summary>
19
    IUnfuckedHttpHandler? Handler { get; }
20

21
    /// <summary>
22
    /// <para>Send an HTTP request using a nice parameterized options struct.</para>
23
    /// <para>Generally, this is called internally by the <see cref="IWebTarget"/> builder, which is more fluent (<c>HttpClient.Target(url).Get&lt;string&gt;()</c>, for example).</para>
24
    /// </summary>
25
    /// <param name="request">the HTTP verb, URL, headers, and optional body to send</param>
26
    /// <param name="cancellationToken">cancel the request</param>
27
    /// <returns>HTTP response, after the response headers only are read</returns>
28
    /// <exception cref="ProcessingException">the HTTP request timed out (<see cref="TimeoutException"/>) or threw an <see cref="HttpRequestException"/></exception>
29
    Task<HttpResponseMessage> SendAsync(HttpRequest request, CancellationToken cancellationToken = default);
30

31
}
32

33
/// <summary>
34
/// <para>An improved subclass of <see cref="HttpClient"/>.</para>
35
/// <para>Usage:</para>
36
/// <para><c>using HttpClient client = new UnfuckedHttpClient();
37
/// MyObject response = await client.Target(url).Get&lt;MyObject&gt;();</c></para>
38
/// </summary>
39
public class UnfuckedHttpClient: HttpClient, IHttpClient {
40

UNCOV
41
    private static readonly TimeSpan DEFAULT_TIMEOUT = new(0, 0, 30);
×
42

43
    /// <inheritdoc />
UNCOV
44
    public IUnfuckedHttpHandler? Handler { get; }
×
45

UNCOV
46
    public UnfuckedHttpClient(): this(new UnfuckedHttpHandler()) { }
×
47

48
    // These factory methods are no longer constructors so DI gets less confused by the arguments, even though many are optional, to prevent it trying to inject a real HttpMessageHandler in a symmetric dependency. Microsoft.Extensions.DependencyInjection always picks the constructor overload with the most injectable arguments, but I want it to pick the no-arg constructor.
49
    public static UnfuckedHttpClient Create(HttpMessageHandler? handler = null, bool disposeHandler = true, IClientConfig? configuration = null) =>
50
        new(handler as UnfuckedHttpHandler ?? new UnfuckedHttpHandler(handler, configuration), disposeHandler);
×
51

52
    public static UnfuckedHttpClient Create(IUnfuckedHttpHandler unfuckedHandler, bool disposeHandler = true) => new(unfuckedHandler, disposeHandler);
×
53

UNCOV
54
    protected UnfuckedHttpClient(IUnfuckedHttpHandler unfuckedHandler, bool disposeHandler = true): base(unfuckedHandler as HttpMessageHandler ?? new IUnfuckedHttpHandlerWrapper(unfuckedHandler),
×
UNCOV
55
        disposeHandler) {
×
UNCOV
56
        Handler = unfuckedHandler;
×
UNCOV
57
        Timeout = DEFAULT_TIMEOUT;
×
UNCOV
58
        if (Assembly.GetEntryAssembly()?.GetName() is { Name: { } programName, Version: { } programVersion }) {
×
UNCOV
59
            DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(programName, programVersion.ToString(4, true)));
×
60
        }
UNCOV
61
        UnfuckedHttpHandler.CacheClientHandler(this, unfuckedHandler);
×
UNCOV
62
    }
×
63

64
    // ExceptionAdjustment: M:System.Net.Http.Headers.HttpHeaders.Add(System.String,System.Collections.Generic.IEnumerable{System.String}) -T:System.FormatException
65
    public static UnfuckedHttpClient Create(HttpClient toClone, bool disposeHandler = true, IClientConfig? configuration = null) {
66
        UnfuckedHttpClient newClient = new(toClone is UnfuckedHttpClient { Handler: { } h } ? h : new UnfuckedHttpHandler(toClone, configuration), disposeHandler) {
×
67
            BaseAddress                  = toClone.BaseAddress,
×
68
            Timeout                      = toClone.Timeout,
×
69
            MaxResponseContentBufferSize = toClone.MaxResponseContentBufferSize,
×
70
#if NETCOREAPP3_0_OR_GREATER
×
71
            DefaultRequestVersion = toClone.DefaultRequestVersion,
×
72
            DefaultVersionPolicy = toClone.DefaultVersionPolicy
×
73
#endif
×
74
        };
×
75

76
        foreach (KeyValuePair<string, IEnumerable<string>> wrappedDefaultHeader in toClone.DefaultRequestHeaders) {
×
77
            newClient.DefaultRequestHeaders.Add(wrappedDefaultHeader.Key, wrappedDefaultHeader.Value);
×
78
        }
79

80
        return newClient;
×
81
    }
82

83
    /// <inheritdoc />
84
    public virtual Task<HttpResponseMessage> SendAsync(HttpRequest request, CancellationToken cancellationToken = default) {
85
#if NET8_0_OR_GREATER
86
        WireLogFilter.ASYNC_STATE.Value = new WireLogFilter.WireAsyncState();
87
#endif
88

89
        UnfuckedHttpRequestMessage req = new(request.Verb, request.Uri) {
×
90
            Content = request.Body,
×
91
            Config  = request.ClientConfig
×
92
        };
×
93
        try {
94
            foreach (KeyValuePair<string, string> header in request.Headers) {
×
95
                req.Headers.Add(header.Key, header.Value);
×
96
            }
97
        } catch (FormatException e) {
×
98
            throw new ProcessingException(e, HttpExceptionParams.FromRequest(req));
×
99
        }
100

101
        // Set wire logging AsyncLocal outside of this async method so it is available higher in the await chain when the response finishes
102
        return SendAsync(this, req, cancellationToken);
×
103
    }
104

105
    /// <exception cref="ProcessingException"></exception>
106
    internal static async Task<HttpResponseMessage> SendAsync(HttpClient client, UnfuckedHttpRequestMessage request, CancellationToken cancellationToken) {
107
        try {
108
            return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
×
109
        } catch (OperationCanceledException e) {
110
            // Official documentation is wrong: .NET Framework throws a TaskCanceledException for an HTTP request timeout, not an HttpRequestException (https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync)
111
            TimeoutException cause = e.InnerException as TimeoutException ??
×
112
                new TimeoutException($"The request was canceled due to the configured {nameof(HttpClient)}.{nameof(Timeout)} of {client.Timeout.TotalSeconds} seconds elapsing.");
×
113
            throw new ProcessingException(cause, HttpExceptionParams.FromRequest(request));
×
114
        } catch (HttpRequestException e) {
×
115
            throw new ProcessingException(e.InnerException ?? e, HttpExceptionParams.FromRequest(request));
×
116
        } finally {
117
            request.Dispose();
×
118
        }
119
    }
×
120

121
}
122

123
internal class HttpClientWrapper: IHttpClient {
124

125
    private readonly HttpClient realClient;
126

127
    public IUnfuckedHttpHandler? Handler { get; }
×
128

129
    private HttpClientWrapper(HttpClient realClient) {
×
130
        this.realClient = realClient;
×
131
        Handler         = UnfuckedHttpHandler.FindHandler(realClient);
×
132
    }
×
133

134
    public static IHttpClient Wrap(IHttpClient client) => client is HttpClient httpClient and not UnfuckedHttpClient ? new HttpClientWrapper(httpClient) : client;
×
135
    public static IHttpClient Wrap(HttpClient client) => client as UnfuckedHttpClient as IHttpClient ?? new HttpClientWrapper(client);
×
136

137
    /// <exception cref="ProcessingException"></exception>
138
    public Task<HttpResponseMessage> SendAsync(HttpRequest request, CancellationToken cancellationToken = default) {
139
        using UnfuckedHttpRequestMessage req = new(request);
×
140

141
        try {
142
            foreach (IGrouping<string, string> header in request.Headers.GroupBy(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase)) {
×
143
                req.Headers.Add(header.Key, header);
×
144
            }
145
        } catch (FormatException e) {
×
146
            throw new ProcessingException(e, HttpExceptionParams.FromRequest(req));
×
147
        }
148
        return UnfuckedHttpClient.SendAsync(realClient, new UnfuckedHttpRequestMessage(request), cancellationToken);
×
149
    }
×
150

151
    public void Dispose() {
152
        GC.SuppressFinalize(this);
×
153
    }
×
154

155
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc