| File: FallbackToHttpMessageHandler.cs | Web Access |
| Project: src\sdk\src\Containers\Microsoft.NET.Build.Containers\Microsoft.NET.Build.Containers.csproj (Microsoft.NET.Build.Containers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net; using Microsoft.Extensions.Logging; using Microsoft.NET.Build.Containers.Resources; namespace Microsoft.NET.Build.Containers; /// <summary> /// A delegating handler that falls back from https to http for a specific hostname. /// </summary> internal sealed partial class FallbackToHttpMessageHandler : DelegatingHandler { private readonly string _registryName; private readonly string _host; private readonly int _port; private readonly ILogger _logger; private bool _fallbackToHttp; public FallbackToHttpMessageHandler(string registryName, string host, int port, HttpMessageHandler innerHandler, ILogger logger) : base(innerHandler) { _registryName = registryName; _host = host; _port = port; _logger = logger; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (request.RequestUri is null) { throw new ArgumentException(Resource.GetString(nameof(Strings.NoRequestUriSpecified)), nameof(request)); } bool canFallback = request.RequestUri.Host == _host && request.RequestUri.Port == _port && request.RequestUri.Scheme == "https"; do { try { if (canFallback && _fallbackToHttp) { FallbackToHttp(_registryName, request); canFallback = false; } return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); } catch (HttpRequestException re) when (canFallback && ShouldAttemptFallbackToHttp(re)) { string uri = request.RequestUri.ToString(); try { // Try falling back. _logger.LogTrace("Attempt to fall back to http for {uri}.", uri); FallbackToHttp(_registryName, request); HttpResponseMessage response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); // Fall back was successful. Use http for all new requests. _logger.LogTrace("Fall back to http for {uri} was successful.", uri); _fallbackToHttp = true; return response; } catch (Exception ex) { _logger.LogInformation(ex, "Fall back to http for {uri} failed with message \"{message}\".", uri, ex.Message); } // Falling back didn't work, throw original exception. throw; } } while (true); } internal static bool ShouldAttemptFallbackToHttp(HttpRequestException exception) { return exception.HttpRequestError == HttpRequestError.SecureConnectionError; } private static bool RegistryNameContainsPort(string registryName) { // use `container` scheme which does not have a default port. return new Uri($"container://{registryName}").Port != -1; } private static void FallbackToHttp(string registryName, HttpRequestMessage request) { var uriBuilder = new UriBuilder(request.RequestUri!); uriBuilder.Scheme = "http"; if (RegistryNameContainsPort(registryName) == false) { // registeryName does not contains port number, so reset the port number to -1, otherwise it will be https default port 443 uriBuilder.Port = -1; } request.RequestUri = uriBuilder.Uri; } }