< Summary - Jellyfin

Information
Class: Jellyfin.Networking.HappyEyeballs.HttpClientExtension
Assembly: Jellyfin.Networking
File(s): /srv/git/jellyfin/src/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 119
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%

File(s)

/srv/git/jellyfin/src/Jellyfin.Networking/HappyEyeballs/HttpClientExtension.cs

#LineLine coverage
 1/*
 2The MIT License (MIT)
 3
 4Copyright (c) .NET Foundation and Contributors
 5
 6All rights reserved.
 7
 8Permission is hereby granted, free of charge, to any person obtaining a copy
 9of this software and associated documentation files (the "Software"), to deal
 10in the Software without restriction, including without limitation the rights
 11to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12copies of the Software, and to permit persons to whom the Software is
 13furnished to do so, subject to the following conditions:
 14
 15The above copyright notice and this permission notice shall be included in all
 16copies or substantial portions of the Software.
 17
 18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 24SOFTWARE.
 25*/
 26
 27using System.IO;
 28using System.Net.Http;
 29using System.Net.Sockets;
 30using System.Threading;
 31using System.Threading.Tasks;
 32
 33namespace Jellyfin.Networking.HappyEyeballs;
 34
 35/// <summary>
 36/// Defines the <see cref="HttpClientExtension"/> class.
 37///
 38/// Implementation taken from https://github.com/ppy/osu-framework/pull/4191 .
 39/// </summary>
 40public static class HttpClientExtension
 41{
 42    /// <summary>
 43    /// Gets or sets a value indicating whether the client should use IPv6.
 44    /// </summary>
 345    public static bool UseIPv6 { get; set; } = true;
 46
 47    /// <summary>
 48    /// Implements the httpclient callback method.
 49    /// </summary>
 50    /// <param name="context">The <see cref="SocketsHttpConnectionContext"/> instance.</param>
 51    /// <param name="cancellationToken">The <see cref="CancellationToken"/> instance.</param>
 52    /// <returns>The http steam.</returns>
 53    public static async ValueTask<Stream> OnConnect(SocketsHttpConnectionContext context, CancellationToken cancellation
 54    {
 55        if (!UseIPv6)
 56        {
 57            return await AttemptConnection(AddressFamily.InterNetwork, context, cancellationToken).ConfigureAwait(false)
 58        }
 59
 60        using var cancelIPv6 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 61        var tryConnectAsyncIPv6 = AttemptConnection(AddressFamily.InterNetworkV6, context, cancelIPv6.Token);
 62
 63        // GetAwaiter().GetResult() is used instead of .Result as this results in improved exception handling.
 64        // The tasks have already been completed.
 65        // See https://github.com/dotnet/corefx/pull/29792/files#r189415885 for more details.
 66        if (await Task.WhenAny(tryConnectAsyncIPv6, Task.Delay(200, cancelIPv6.Token)).ConfigureAwait(false) == tryConne
 67        {
 68            await cancelIPv6.CancelAsync().ConfigureAwait(false);
 69            return tryConnectAsyncIPv6.GetAwaiter().GetResult();
 70        }
 71
 72        using var cancelIPv4 = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
 73        var tryConnectAsyncIPv4 = AttemptConnection(AddressFamily.InterNetwork, context, cancelIPv4.Token);
 74
 75        if (await Task.WhenAny(tryConnectAsyncIPv6, tryConnectAsyncIPv4).ConfigureAwait(false) == tryConnectAsyncIPv6)
 76        {
 77            if (tryConnectAsyncIPv6.IsCompletedSuccessfully)
 78            {
 79                await cancelIPv4.CancelAsync().ConfigureAwait(false);
 80                return tryConnectAsyncIPv6.GetAwaiter().GetResult();
 81            }
 82
 83            return tryConnectAsyncIPv4.GetAwaiter().GetResult();
 84        }
 85        else
 86        {
 87            if (tryConnectAsyncIPv4.IsCompletedSuccessfully)
 88            {
 89                await cancelIPv6.CancelAsync().ConfigureAwait(false);
 90                return tryConnectAsyncIPv4.GetAwaiter().GetResult();
 91            }
 92
 93            return tryConnectAsyncIPv6.GetAwaiter().GetResult();
 94        }
 95    }
 96
 97    private static async Task<Stream> AttemptConnection(AddressFamily addressFamily, SocketsHttpConnectionContext contex
 98    {
 99        // The following socket constructor will create a dual-mode socket on systems where IPV6 is available.
 100        var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp)
 101        {
 102            // Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
 103            NoDelay = true
 104        };
 105
 106        try
 107        {
 108            await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
 109            // The stream should take the ownership of the underlying socket,
 110            // closing it when it's disposed.
 111            return new NetworkStream(socket, ownsSocket: true);
 112        }
 113        catch
 114        {
 115            socket.Dispose();
 116            throw;
 117        }
 118    }
 119}

Methods/Properties

.cctor()