AspNetCore.Diagnostics.HealthChecks
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.
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:
Production:
Thanks for all
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!
I have the same issue with AddUrlGroup()
:
.AddUrlGroup(new Uri("/swagger", UriKind.Relative), "Swagger")
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.
嘿@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