AspNetCore3.1_Middleware源码解析_3_HttpsRedirection

概述

上文提到3.1版本默认没有使用Hsts,但是使用了HttpsRedirection这个中间件。看名字就很好理解,https跳转,顾名思义,就是跳转到https地址。

使用场景:当用户使用http访问网站时,自动跳转到https地址。这样更加安全,也更加方便,因为不需要用户特意输入https://。

具体怎么实现的我们来看看。

  app.UseHttpsRedirection();

使用方法

跟Hsts一样,HttpsRedirection默认是不需要注入的,除非你需要修改默认配置。

services.AddHttpsRedirection(config =>
  {
      //https地址的端口号,默认null
      config.HttpsPort = 12345;

      //跳转响应的状态码,默认307
      config.RedirectStatusCode = 302;
  });

直接使用中间件即可

 app.UseHttpsRedirection();

源码解析

源代码很简单,只有两个类:HttpsRedirectionOptions配置类,HttpsRedirectionMiddleware中间件

HttpsRedirectionOptions就只有两个配置项

  /// <summary>
  /// Options for the HttpsRedirection middleware
  /// </summary>
  public class HttpsRedirectionOptions
  {
      /// <summary>
      /// The status code used for the redirect response. The default is 307.
      /// </summary>
      public int RedirectStatusCode { get; set; } = StatusCodes.Status307TemporaryRedirect;

      /// <summary>
      /// The HTTPS port to be added to the redirected URL.
      /// </summary>
      /// <remarks>
      /// If the HttpsPort is not set, we will try to get the HttpsPort from the following:
      /// 1. HTTPS_PORT environment variable
      /// 2. IServerAddressesFeature
      /// If that fails then the middleware will log a warning and turn off.
      /// </remarks>
      public int? HttpsPort { get; set; }
  }

重点看下中间件做了些什么。代码量很少,大体是这些逻辑。

注:3.1同时支持HTTPS_PORT和ANCM_HTTPS_PORT这两个环境变量。 image

https://docs.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.0

点击查看HttpsRedirectionMiddleware源代码
public class HttpsRedirectionMiddleware
    {
        private const int PortNotFound = -1;

        private readonly RequestDelegate _next;
        private readonly Lazy<int> _httpsPort;
        private readonly int _statusCode;

        private readonly IServerAddressesFeature _serverAddressesFeature;
        private readonly IConfiguration _config;
        private readonly ILogger _logger;

        /// <summary>
        /// Initializes the HttpsRedirectionMiddleware
        /// </summary>
        /// <param name="next"></param>
        /// <param name="options"></param>
        /// <param name="config"></param>
        /// <param name="loggerFactory"></param>
        public HttpsRedirectionMiddleware(RequestDelegate next, IOptions<HttpsRedirectionOptions> options, IConfiguration config, ILoggerFactory loggerFactory)

        {
            _next = next ?? throw new ArgumentNullException(nameof(next));
            _config = config ?? throw new ArgumentNullException(nameof(config));

            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            var httpsRedirectionOptions = options.Value;
            if (httpsRedirectionOptions.HttpsPort.HasValue)
            {
                _httpsPort = new Lazy<int>(() => httpsRedirectionOptions.HttpsPort.Value);
            }
            else
            {
                _httpsPort = new Lazy<int>(TryGetHttpsPort);
            }
            _statusCode = httpsRedirectionOptions.RedirectStatusCode;
            _logger = loggerFactory.CreateLogger<HttpsRedirectionMiddleware>();
        }

        /// <summary>
        /// Initializes the HttpsRedirectionMiddleware
        /// </summary>
        /// <param name="next"></param>
        /// <param name="options"></param>
        /// <param name="config"></param>
        /// <param name="loggerFactory"></param>
        /// <param name="serverAddressesFeature">The</param>
        public HttpsRedirectionMiddleware(RequestDelegate next, IOptions<HttpsRedirectionOptions> options, IConfiguration config, ILoggerFactory loggerFactory,
            IServerAddressesFeature serverAddressesFeature)
            : this(next, options, config, loggerFactory)
        {
            _serverAddressesFeature = serverAddressesFeature ?? throw new ArgumentNullException(nameof(serverAddressesFeature));
        }

        /// <summary>
        /// Invokes the HttpsRedirectionMiddleware
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public Task Invoke(HttpContext context)
        {
            if (context.Request.IsHttps)
            {
                return _next(context);
            }

            var port = _httpsPort.Value;
            if (port == PortNotFound)
            {
                return _next(context);
            }

            var host = context.Request.Host;
            if (port != 443)
            {
                host = new HostString(host.Host, port);
            }
            else
            {
                host = new HostString(host.Host);
            }

            var request = context.Request;
            var redirectUrl = UriHelper.BuildAbsolute(
                "https", 
                host,
                request.PathBase,
                request.Path,
                request.QueryString);

            context.Response.StatusCode = _statusCode;
            context.Response.Headers[HeaderNames.Location] = redirectUrl;

            _logger.RedirectingToHttps(redirectUrl);

            return Task.CompletedTask;
        }

        //  Returns PortNotFound (-1) if we were unable to determine the port.
        private int TryGetHttpsPort()
        {
            // The IServerAddressesFeature will not be ready until the middleware is Invoked,
            // Order for finding the HTTPS port:
            // 1. Set in the HttpsRedirectionOptions
            // 2. HTTPS_PORT environment variable
            // 3. IServerAddressesFeature
            // 4. Fail if not sets

            var nullablePort = _config.GetValue<int?>("HTTPS_PORT") ?? _config.GetValue<int?>("ANCM_HTTPS_PORT");
            if (nullablePort.HasValue)
            {
                var port = nullablePort.Value;
                _logger.PortLoadedFromConfig(port);
                return port;
            }

            if (_serverAddressesFeature == null)
            {
                _logger.FailedToDeterminePort();
                return PortNotFound;
            }

            foreach (var address in _serverAddressesFeature.Addresses)
            {
                var bindingAddress = BindingAddress.Parse(address);
                if (bindingAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))
                {
                    // If we find multiple different https ports specified, throw
                    if (nullablePort.HasValue && nullablePort != bindingAddress.Port)
                    {
                        _logger.FailedMultiplePorts();
                        return PortNotFound;
                    }
                    else
                    {
                        nullablePort = bindingAddress.Port;
                    }
                }
            }

            if (nullablePort.HasValue)
            {
                var port = nullablePort.Value;
                _logger.PortFromServer(port);
                return port;
            }

            _logger.FailedToDeterminePort();
            return PortNotFound;
        }
    }

OK,完成了。