AspNetCore.Diagnostics.HealthChecks icon indicating copy to clipboard operation
AspNetCore.Diagnostics.HealthChecks copied to clipboard

An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.

Open Anaster666 opened this issue 2 years ago • 4 comments

Good morning!

Please, fill the following sections to help us fix the issue

What happened: In localhost I don't have any error but when I publish the service in ISS my healthcheckUI appears this message: An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set.

How to reproduce it (as minimally and precisely as possible): My configuration is:

  • ConfigureServices Method:
services.AddHealthChecksUI(s =>
{
   s.AddHealthCheckEndpoint("Validations", "/CheckPoints");
}).AddInMemoryStorage();  

services.AddHealthChecks();
  • Configure Method:
    {
        public static IEndpointRouteBuilder MapCustomHealthChecks(this IEndpointRouteBuilder builder, IWebHostEnvironment environment, IConfiguration configuration)
        {
            builder.MapHealthChecksUI(setupOptions: setup =>
            {
                setup.UIPath = "/CheckPointsUI";
            });
            builder.MapHealthChecks("/CheckPoints", new HealthCheckOptions()
            {
                Predicate = _ => true,
                ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
            });
            builder.MapHealthChecks("/Status");

            return builder;
        }

Environment:

  • .NET Core version 5.0

  • Others: My intention is simply get an endpoint to know that the service is healthy or not.

I send you the images of chekPointsUI in localhost/Production

Localhost: localhosy

Production: production

Thanks for all

Anaster666 avatar Oct 11 '21 09:10 Anaster666

If I change the uri in AddHealthCheckEndpoints to an absolute uri it works. But with this way, I need specify always the uri when I would like use a relativeUri and all the services would have the same properties.

Do I need specify any variable in appsettings for default? Right now I don't have any

Thanks!

Anaster666 avatar Oct 28 '21 10:10 Anaster666

I have the same issue with AddUrlGroup(): .AddUrlGroup(new Uri("/swagger", UriKind.Relative), "Swagger")

Edgaras91 avatar Nov 01 '21 11:11 Edgaras91

Hey @Anaster666 ,

I got the same issue and get it why HealthCheckReportCollector can't resolve the uri. It try to resolve relative uri with help of the IServerAddressesFeature which return all registered server address. On a IIS the feature will return the bindings of the application and in the most cases that bindings has a wildcard hostname *. The Uri and HttpClient can't resolve the wildcard host so you have to replace it before the the health check try to request the uri. I resolved it with a custom IServerAddressesFeature which replace the * with localhost and additionally I return at first the request uri if there exists one.

public class RequestOrLocalhostServerAddressesFeature
    : IServerAddressesFeature
{


    public ICollection<string> Addresses { get; }


    public bool PreferHostingUrls
    {
        get => OriginFeature.PreferHostingUrls;
        set => OriginFeature.PreferHostingUrls = value;
    }


    protected IServerAddressesFeature OriginFeature { get; }


    protected IHttpContextAccessor HttpContextAccessor { get; }


    public RequestOrLocalhostServerAddressesFeature(IServerAddressesFeature originFeature, IHttpContextAccessor httpContextAccessor)
    {
        OriginFeature = originFeature ?? throw new ArgumentNullException(nameof(originFeature));
        HttpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        Addresses = new AddressCollection(this);
    }


    class AddressCollection : ICollection<string>
    {


        private static readonly Regex _WildcardHostRegex =
            new Regex(@"^((?:[A-Za-z][A-Za-z+-.]*://)?)\*(.*)$");


        public int Count => _feature.OriginFeature.Addresses.Count + (_feature.HttpContextAccessor.HttpContext is null ? 0 : 1);

        public bool IsReadOnly => _feature.OriginFeature.Addresses.IsReadOnly;


        protected IEnumerable<string> Addresses
        {
            get
            {
                var context = _feature.HttpContextAccessor.HttpContext;
                if (context is not null)
                    yield return context.Request.GetBaseUrl();

                foreach (var address in _feature.OriginFeature.Addresses)
                    yield return _WildcardHostRegex.Replace(address, match => $"{match.Groups[1]}localhost{match.Groups[2]}");
            }
        }


        private readonly RequestOrLocalhostServerAddressesFeature _feature;


        public AddressCollection(RequestOrLocalhostServerAddressesFeature feature)
        {
            _feature = feature ?? throw new ArgumentNullException(nameof(feature));
        }


        public void Add(string item) =>
            _feature.OriginFeature.Addresses.Add(item);

        public bool Remove(string item) =>
            _feature.OriginFeature.Addresses.Remove(item);

        public void Clear() =>
            _feature.OriginFeature.Addresses.Clear();

        public bool Contains(string item) =>
            Addresses.Contains(item);


        public void CopyTo(string[] array, int arrayIndex) =>
            Addresses.ToArray().CopyTo(array, arrayIndex);


        public IEnumerator<string> GetEnumerator() =>
            Addresses.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() =>
            GetEnumerator();


    }
}

The registration is in a IStartupFilter

public class RequestOrLocalhostServerAddressesStartupFilter
    : IStartupFilter
{


    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => appBuilder =>
    {
        var origin = appBuilder.ServerFeatures.Get<IServerAddressesFeature>();
        appBuilder.ServerFeatures.Set<IServerAddressesFeature>(new RequestOrLocalhostServerAddressesFeature(origin,
            appBuilder.ApplicationServices.GetRequiredService<IHttpContextAccessor>()));

        next(appBuilder);
    };


}

And to make it more convenient add a extensions method to IHostBuilder and call it in Program

public static IHostBuilder UseRequestOrLocalhostServerAddresses(this IHostBuilder builder)
{
    if (builder is null)
        throw new ArgumentNullException(nameof(builder));

    return builder.ConfigureServices(services =>
        services.AddTransient<IStartupFilter, RequestOrLocalhostServerAddressesStartupFilter>());
}
Host.CreateDefaultBuilder(args)
  .UseRequestOrLocalhostServerAddresses()
  .ConfigureWebHostDefaults(webBuilder =>
  {
      webBuilder.UseStartup<Startup>();
  });

I hope they will include some checks for http request in ServerAddressesService like:

  • Check if server address has the schema http[s] otherwise try next
  • Replace wildcard host * like above if use the uri for a http request.

DavenaHack avatar Dec 15 '21 09:12 DavenaHack

嘿@Anaster666 ,

我遇到了同样的问题,并得到了为什么HealthCheckReportCollector无法解决uri的原因。它尝试在返回所有已注册服务器地址的帮助下解析相对uri。在 IIS 上,该功能将返回应用程序的绑定,并且在大多数情况下,绑定具有通配符主机名 。和 无法解析通配符主机,因此您必须在运行状况检查尝试请求 uri 之前替换它。我用一个习惯解决了这个问题,该习俗取代了,另外,如果存在一个,我首先返回请求uri。IServerAddressesFeature``*``Uri``HttpClient``IServerAddressesFeature``*``localhost

public class RequestOrLocalhostServerAddressesFeature
    : IServerAddressesFeature
{


    public ICollection<string> Addresses { get; }


    public bool PreferHostingUrls
    {
        get => OriginFeature.PreferHostingUrls;
        set => OriginFeature.PreferHostingUrls = value;
    }


    protected IServerAddressesFeature OriginFeature { get; }


    protected IHttpContextAccessor HttpContextAccessor { get; }


    public RequestOrLocalhostServerAddressesFeature(IServerAddressesFeature originFeature, IHttpContextAccessor httpContextAccessor)
    {
        OriginFeature = originFeature ?? throw new ArgumentNullException(nameof(originFeature));
        HttpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        Addresses = new AddressCollection(this);
    }


    class AddressCollection : ICollection<string>
    {


        private static readonly Regex _WildcardHostRegex =
            new Regex(@"^((?:[A-Za-z][A-Za-z+-.]*://)?)\*(.*)$");


        public int Count => _feature.OriginFeature.Addresses.Count + (_feature.HttpContextAccessor.HttpContext is null ? 0 : 1);

        public bool IsReadOnly => _feature.OriginFeature.Addresses.IsReadOnly;


        protected IEnumerable<string> Addresses
        {
            get
            {
                var context = _feature.HttpContextAccessor.HttpContext;
                if (context is not null)
                    yield return context.Request.GetBaseUrl();

                foreach (var address in _feature.OriginFeature.Addresses)
                    yield return _WildcardHostRegex.Replace(address, match => $"{match.Groups[1]}localhost{match.Groups[2]}");
            }
        }


        private readonly RequestOrLocalhostServerAddressesFeature _feature;


        public AddressCollection(RequestOrLocalhostServerAddressesFeature feature)
        {
            _feature = feature ?? throw new ArgumentNullException(nameof(feature));
        }


        public void Add(string item) =>
            _feature.OriginFeature.Addresses.Add(item);

        public bool Remove(string item) =>
            _feature.OriginFeature.Addresses.Remove(item);

        public void Clear() =>
            _feature.OriginFeature.Addresses.Clear();

        public bool Contains(string item) =>
            Addresses.Contains(item);


        public void CopyTo(string[] array, int arrayIndex) =>
            Addresses.ToArray().CopyTo(array, arrayIndex);


        public IEnumerator<string> GetEnumerator() =>
            Addresses.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() =>
            GetEnumerator();


    }
}

注册在IStartupFilter

public class RequestOrLocalhostServerAddressesStartupFilter
    : IStartupFilter
{


    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => appBuilder =>
    {
        var origin = appBuilder.ServerFeatures.Get<IServerAddressesFeature>();
        appBuilder.ServerFeatures.Set<IServerAddressesFeature>(new RequestOrLocalhostServerAddressesFeature(origin,
            appBuilder.ApplicationServices.GetRequiredService<IHttpContextAccessor>()));

        next(appBuilder);
    };


}

为了使其更方便,请添加一个扩展方法并将其调用IHostBuilder``Program

public static IHostBuilder UseRequestOrLocalhostServerAddresses(this IHostBuilder builder)
{
    if (builder is null)
        throw new ArgumentNullException(nameof(builder));

    return builder.ConfigureServices(services =>
        services.AddTransient<IStartupFilter, RequestOrLocalhostServerAddressesStartupFilter>());
}
Host.CreateDefaultBuilder(args)
  .UseRequestOrLocalhostServerAddresses()
  .ConfigureWebHostDefaults(webBuilder =>
  {
      webBuilder.UseStartup<Startup>();
  });

我希望它们能在ServerAddressesService中包含一些请求检查,例如:http

  • 检查服务器地址是否具有架构,否则请尝试下一步http[s]
  • 如果将 uri 用于 http 请求,请像上面这样替换通配符主机。*

thanks

tangjiegege avatar Apr 15 '22 09:04 tangjiegege