• 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/UnfuckedHttpHandler.cs
1
using System.Collections.Concurrent;
2
using System.Diagnostics.Contracts;
3
using System.Reflection;
4
using Unfucked.HTTP.Config;
5
using Unfucked.HTTP.Exceptions;
6
using Unfucked.HTTP.Filters;
7
using Unfucked.HTTP.Serialization;
8
#if NET8_0_OR_GREATER
9
using System.Diagnostics.Metrics;
10
#endif
11

12
namespace Unfucked.HTTP;
13

14
public interface IUnfuckedHttpHandler: Configurable<IUnfuckedHttpHandler> {
15

16
    /// <inheritdoc cref="DelegatingHandler.InnerHandler" />
17
    HttpMessageHandler? InnerHandler { get; }
18

19
    /// <summary>
20
    /// HTTP client configuration, including properties, request and response filters, and message body readers
21
    /// </summary>
22
    IClientConfig ClientConfig { get; }
23

24
    /// <summary>
25
    /// This is the method to mock/fake/stub/spy if you want to inspect HTTP requests and return fake responses instead of real ones.
26
    /// </summary>
27
    Task<HttpResponseMessage> TestableSendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
28

29
    /// <summary>
30
    /// This should just delegate to <c>UnfuckedHttpHandler.SendAsync</c>, it's only here because the method was originally only specified on a superclass, not an interface.
31
    /// </summary>
32
    /// <exception cref="ProcessingException"></exception>
33
    Task<HttpResponseMessage> FilterAndSendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
34

35
}
36

37
public class UnfuckedHttpHandler: DelegatingHandler, IUnfuckedHttpHandler {
38

UNCOV
39
    private static readonly ConcurrentDictionary<int, WeakReference<IUnfuckedHttpHandler>?> HTTP_CLIENT_HANDLER_CACHE = new();
×
40

UNCOV
41
    private static readonly Lazy<FieldInfo> HTTP_CLIENT_HANDLER_FIELD = new(() => typeof(HttpMessageInvoker).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
×
UNCOV
42
        .First(field => field.FieldType == typeof(HttpMessageHandler)), LazyThreadSafetyMode.PublicationOnly);
×
43

44
    private readonly FilterContext baseFilterContext;
45

46
#if NET8_0_OR_GREATER
47
    private readonly IMeterFactory? wireLoggingMeterFactory;
48
#endif
49

50
    private bool disposed;
51

52
    /// <inheritdoc />
UNCOV
53
    public IClientConfig ClientConfig { get; private set; }
×
54

55
    /// <inheritdoc />
56
    [Pure]
57
    public IReadOnlyList<ClientRequestFilter> RequestFilters => ClientConfig.RequestFilters;
×
58

59
    /// <inheritdoc />
60
    [Pure]
61
    public IReadOnlyList<ClientResponseFilter> ResponseFilters => ClientConfig.ResponseFilters;
×
62

63
    /// <inheritdoc />
64
    [Pure]
65
    public IEnumerable<MessageBodyReader> MessageBodyReaders => ClientConfig.MessageBodyReaders;
×
66

67
    /*
68
     * No-argument constructor overload lets FakeItEasy call this real constructor, which makes ClientConfig not a fake so registering JSON options aren't ignored, which would cause confusing errors
69
     * at test runtime. Default values for other constructor below wouldn't have been called by FakeItEasy. This avoids having to remember to call
70
     * options.WithArgumentsForConstructor(() => new UnfuckedHttpHandler(null, null)) when creating the fake.
71
     */
UNCOV
72
    public UnfuckedHttpHandler(): this((HttpMessageHandler?) null) { }
×
73

74
    // HttpClientHandler automatically uses SocketsHttpHandler on .NET Core ≥ 2.1, or HttpClientHandler otherwise
UNCOV
75
    public UnfuckedHttpHandler(HttpMessageHandler? innerHandler = null, IClientConfig? configuration = null): base(innerHandler ??
×
UNCOV
76
#if NETCOREAPP2_1_OR_GREATER
×
UNCOV
77
        new SocketsHttpHandler {
×
UNCOV
78
            PooledConnectionLifetime = TimeSpan.FromHours(1),
×
UNCOV
79
            ConnectTimeout = TimeSpan.FromSeconds(10),
×
UNCOV
80
            // MaxConnectionsPerServer defaults to MAX_INT, so we don't need to increase it here
×
UNCOV
81
#if NET8_0_OR_GREATER
×
UNCOV
82
            MeterFactory = new WireLogFilter.WireLoggingMeterFactory()
×
UNCOV
83
#endif
×
UNCOV
84
        }
×
UNCOV
85
#else
×
UNCOV
86
        new HttpClientHandler()
×
UNCOV
87
#endif
×
UNCOV
88
    ) {
×
UNCOV
89
        ClientConfig      = configuration ?? new ClientConfig();
×
UNCOV
90
        baseFilterContext = new FilterContext(this, ClientConfig);
×
91

92
#if NET8_0_OR_GREATER
93
        if (innerHandler == null) {
×
94
            wireLoggingMeterFactory = ((SocketsHttpHandler) InnerHandler!).MeterFactory;
95
        }
96
#endif
UNCOV
97
    }
×
98

99
    public UnfuckedHttpHandler(HttpClient toClone, IClientConfig? configuration = null): this((HttpMessageHandler) HTTP_CLIENT_HANDLER_FIELD.Value.GetValue(toClone)!, configuration) { }
×
100

101
    [Pure]
102
    public static HttpClient CreateClient(HttpMessageHandler? innerHandler = null) => UnfuckedHttpClient.Create(innerHandler);
×
103

104
    internal static IUnfuckedHttpHandler? FindHandler(HttpClient httpClient) {
105
        if (httpClient is IHttpClient client) {
×
106
            return client.Handler;
×
107
        }
108

109
        IUnfuckedHttpHandler? handler = null;
×
110
        if (!HTTP_CLIENT_HANDLER_CACHE.TryGetValue(httpClient.GetHashCode(), out WeakReference<IUnfuckedHttpHandler>? handlerHolder) || !(handlerHolder?.TryGetTarget(out handler) ?? true)) {
×
111
            handler = findDescendantUnfuckedHandler((HttpMessageHandler?) HTTP_CLIENT_HANDLER_FIELD.Value.GetValue(httpClient));
×
112
            CacheClientHandler(httpClient, handler);
×
113
        }
114

115
        return handler;
×
116

117
        static UnfuckedHttpHandler? findDescendantUnfuckedHandler(HttpMessageHandler? parent) => parent switch {
×
118
            UnfuckedHttpHandler f => f,
×
119
            DelegatingHandler d   => findDescendantUnfuckedHandler(d.InnerHandler),
×
120
            _                     => null
×
121
        };
×
122
    }
123

124
    internal static void CacheClientHandler(HttpClient client, IUnfuckedHttpHandler? handler) =>
UNCOV
125
        HTTP_CLIENT_HANDLER_CACHE[client.GetHashCode()] = handler is null ? null : new WeakReference<IUnfuckedHttpHandler>(handler);
×
126

127
    /// <inheritdoc />
128
    public Task<HttpResponseMessage> FilterAndSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => SendAsync(request, cancellationToken);
×
129

130
    /// <inheritdoc />
131
    /// <exception cref="ProcessingException">filter error</exception>
132
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
133
        IClientConfig? config        = (request as UnfuckedHttpRequestMessage)?.Config;
×
134
        FilterContext  filterContext = baseFilterContext with { Configuration = config ?? baseFilterContext.Configuration };
×
135

136
        try {
137
            foreach (ClientRequestFilter requestFilter in config?.RequestFilters ?? RequestFilters) {
×
138
                HttpRequestMessage newRequest = await requestFilter.Filter(request, filterContext, cancellationToken).ConfigureAwait(false);
×
139
                if (request != newRequest) {
×
140
                    request.Dispose();
×
141
                    request = newRequest;
×
142
                }
143
            }
144
        } catch (ProcessingException) {
×
145
            request.Dispose();
×
146
            throw;
×
147
        }
148

149
        HttpResponseMessage response = await TestableSendAsync(request, cancellationToken).ConfigureAwait(false);
×
150

151
        try {
152
            foreach (ClientResponseFilter responseFilter in config?.ResponseFilters ?? ResponseFilters) {
×
153
                HttpResponseMessage newResponse = await responseFilter.Filter(response, filterContext, cancellationToken).ConfigureAwait(false);
×
154
                if (response != newResponse) {
×
155
                    response.Dispose();
×
156
                    response = newResponse;
×
157
                }
158
            }
159
        } catch (ProcessingException) {
×
160
            request.Dispose();
×
161
            response.Dispose();
×
162
            throw;
×
163
        }
164

165
        return response;
×
166
    }
×
167

168
    /// <inheritdoc />
169
    public virtual Task<HttpResponseMessage> TestableSendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => base.SendAsync(request, cancellationToken);
×
170

171
    private UnfuckedHttpHandler With(IClientConfig newConfig) {
172
        ClientConfig = newConfig;
×
173
        return this;
×
174
    }
175

176
    public IUnfuckedHttpHandler Register(Registrable registrable) => With(ClientConfig.Register(registrable));
×
177

178
    public IUnfuckedHttpHandler Register<Option>(Registrable<Option> registrable, Option registrationOption) => With(ClientConfig.Register(registrable, registrationOption));
×
179

180
    public IUnfuckedHttpHandler Property<T>(PropertyKey<T> key, T? value) where T: notnull => With(ClientConfig.Property(key, value));
×
181

182
    [Pure]
183
    public bool Property<T>(PropertyKey<T> key,
184
#if !NETSTANDARD2_0
185
                            [NotNullWhen(true)]
186
#endif
187
                            out T? existingValue) where T: notnull => ClientConfig.Property(key, out existingValue);
×
188

189
    [Pure]
190
    IUnfuckedHttpHandler Configurable<IUnfuckedHttpHandler>.Property<T>(PropertyKey<T> key, T? newValue) where T: default => Property(key, newValue);
×
191

192
    protected override void Dispose(bool disposing) {
193
        if (!disposed) {
×
194
            disposed = true;
×
195
            if (disposing) {
×
196
                List<KeyValuePair<int, WeakReference<IUnfuckedHttpHandler>?>> evictions =
×
197
                    HTTP_CLIENT_HANDLER_CACHE.Where(pair => pair.Value != null && (!pair.Value.TryGetTarget(out IUnfuckedHttpHandler? handler) || handler == this)).ToList();
×
198
                foreach (KeyValuePair<int, WeakReference<IUnfuckedHttpHandler>?> eviction in evictions) {
×
199
#if NET5_0_OR_GREATER
200
                    HTTP_CLIENT_HANDLER_CACHE.TryRemove(eviction);
201
#else
202
                    HTTP_CLIENT_HANDLER_CACHE.TryRemove(eviction.Key, out _);
203
#endif
204
                }
205

206
#if NET8_0_OR_GREATER
207
                wireLoggingMeterFactory?.Dispose();
×
208
#endif
209
            }
210
        }
211
        base.Dispose(disposing);
×
212
    }
×
213

214
}
215

216
/// <summary>
217
/// This is used when a consumer passes an IUnfuckedHttpHandler to an UnfuckedHttpClient constructor. Just because it implements IUnfuckedHttpHandler doesn't mean it's a subclass of HttpMessageHandler, and Microsoft stupidly decided to never use interfaces for anything. This class is an adapter that actually has the superclass needed to be used as an HttpMessageHandler.
218
/// </summary>
219
internal class IUnfuckedHttpHandlerWrapper(IUnfuckedHttpHandler realHandler): HttpMessageHandler {
×
220

221
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => realHandler.FilterAndSendAsync(request, cancellationToken);
×
222

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