From 21444ef9ced11487f99d2c4cb60613eef5069c5d Mon Sep 17 00:00:00 2001 From: Shreyas Zare Date: Sat, 14 Aug 2021 12:13:26 +0530 Subject: [PATCH] FailoverApp: added feature to auto generate health check url based of the app record domain name. added support for `https` type for auto generating health check url with expected scheme. --- Apps/FailoverApp/Address.cs | 12 +++- Apps/FailoverApp/CNAME.cs | 5 +- Apps/FailoverApp/HealthCheck.cs | 120 ++++++++++++++++++-------------- Apps/FailoverApp/dnsApp.config | 16 ++++- 4 files changed, 94 insertions(+), 59 deletions(-) diff --git a/Apps/FailoverApp/Address.cs b/Apps/FailoverApp/Address.cs index c798ae7e..84ac6f59 100644 --- a/Apps/FailoverApp/Address.cs +++ b/Apps/FailoverApp/Address.cs @@ -159,6 +159,9 @@ namespace Failover if (jsonAppRecordData.healthCheckUrl != null) healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value); + if (healthCheckUrl is null) + healthCheckUrl = new Uri("http://" + question.Name); + List answers = new List(); GetAnswers(jsonAppRecordData.primary, question, appRecordTtl, healthCheck, healthCheckUrl, answers); @@ -199,6 +202,9 @@ namespace Failover if (jsonAppRecordData.healthCheckUrl != null) healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value); + if (healthCheckUrl is null) + healthCheckUrl = new Uri("http://" + question.Name); + List answers = new List(); GetStatusAnswers(jsonAppRecordData.primary, FailoverType.Primary, question, 30, healthCheck, healthCheckUrl, answers); @@ -218,7 +224,7 @@ namespace Failover #region properties public string Description - { get { return "Returns A or AAAA records from primary set of addresses with a continous health check as configured in the app config. When none of the primary addresses are healthy, the app returns healthy addresses from the secondary set of addresses. When none of the primary and secondary addresses are healthy, the app returns healthy addresses from the server down set of addresses. The server down feature is expected to be used for showing a service status page and not to serve the actual content.\n\nSet 'allowTxtStatus' to 'true' in your APP record data to allow checking health status by querying for TXT record."; } } + { get { return "Returns A or AAAA records from primary set of addresses with a continous health check as configured in the app config. When none of the primary addresses are healthy, the app returns healthy addresses from the secondary set of addresses. When none of the primary and secondary addresses are healthy, the app returns healthy addresses from the server down set of addresses. The server down feature is expected to be used for showing a service status page and not to serve the actual content.\n\nWhen an URL is not provided in 'healthCheckUrl' parameter for 'http' or 'https' type health check, the domain name of the APP record will be used to auto generate an URL.\n\nSet 'allowTxtStatus' parameter to 'true' in your APP record data to allow checking health status by querying for TXT record."; } } public string ApplicationRecordDataTemplate { @@ -236,8 +242,8 @@ namespace Failover ""serverDown"": [ ""3.3.3.3"" ], - ""healthCheck"": ""http"", - ""healthCheckUrl"": ""https://www.example.com"", + ""healthCheck"": ""https"", + ""healthCheckUrl"": null, ""allowTxtStatus"": false }"; } diff --git a/Apps/FailoverApp/CNAME.cs b/Apps/FailoverApp/CNAME.cs index 89007c0b..4d7283ac 100644 --- a/Apps/FailoverApp/CNAME.cs +++ b/Apps/FailoverApp/CNAME.cs @@ -135,6 +135,9 @@ namespace Failover if (jsonAppRecordData.healthCheckUrl != null) healthCheckUrl = new Uri(jsonAppRecordData.healthCheckUrl.Value); + if (healthCheckUrl is null) + healthCheckUrl = new Uri("http://" + question.Name); + IReadOnlyList answers; if (question.Type == DnsResourceRecordType.TXT) @@ -192,7 +195,7 @@ namespace Failover #region properties public string Description - { get { return "Returns CNAME record for primary domain name with a continous health check as configured in the app config. When the primary domain name is unhealthy, the app returns one of the secondary domain names in the given order of preference that is healthy. When none of the primary and secondary domain names are healthy, the app returns the server down domain name. The server down feature is expected to be used for showing a service status page and not to serve the actual content. Note that the app will return ANAME record for an APP record at zone apex.\n\nSet 'allowTxtStatus' to 'true' in your APP record data to allow checking health status by querying for TXT record."; } } + { get { return "Returns CNAME record for primary domain name with a continous health check as configured in the app config. When the primary domain name is unhealthy, the app returns one of the secondary domain names in the given order of preference that is healthy. When none of the primary and secondary domain names are healthy, the app returns the server down domain name. The server down feature is expected to be used for showing a service status page and not to serve the actual content. Note that the app will return ANAME record for an APP record at zone apex.\n\nWhen an URL is not provided in 'healthCheckUrl' parameter for 'http' or 'https' type health check, the domain name of the APP record will be used to auto generate an URL.\n\nSet 'allowTxtStatus' parameter to 'true' in your APP record data to allow checking health status by querying for TXT record."; } } public string ApplicationRecordDataTemplate { diff --git a/Apps/FailoverApp/HealthCheck.cs b/Apps/FailoverApp/HealthCheck.cs index 208665d9..ab178a2c 100644 --- a/Apps/FailoverApp/HealthCheck.cs +++ b/Apps/FailoverApp/HealthCheck.cs @@ -35,7 +35,8 @@ namespace Failover Unknown = 0, Ping = 1, Tcp = 2, - Http = 3 + Http = 3, + Https = 4 } class HealthCheck : IDisposable @@ -111,25 +112,14 @@ namespace Failover private void ConditionalHttpReload() { - if (_type == HealthCheckType.Http) + switch (_type) { - bool handlerChanged = false; - NetProxy proxy = _service.DnsServer.Proxy; + case HealthCheckType.Http: + case HealthCheckType.Https: + bool handlerChanged = false; + NetProxy proxy = _service.DnsServer.Proxy; - if (_httpHandler is null) - { - SocketsHttpHandler httpHandler = new SocketsHttpHandler(); - httpHandler.ConnectTimeout = TimeSpan.FromMilliseconds(_timeout); - httpHandler.Proxy = proxy; - httpHandler.AllowAutoRedirect = true; - httpHandler.MaxAutomaticRedirections = 10; - - _httpHandler = httpHandler; - handlerChanged = true; - } - else - { - if ((_httpHandler.ConnectTimeout.TotalMilliseconds != _timeout) || (_httpHandler.Proxy != proxy)) + if (_httpHandler is null) { SocketsHttpHandler httpHandler = new SocketsHttpHandler(); httpHandler.ConnectTimeout = TimeSpan.FromMilliseconds(_timeout); @@ -137,50 +127,64 @@ namespace Failover httpHandler.AllowAutoRedirect = true; httpHandler.MaxAutomaticRedirections = 10; - SocketsHttpHandler oldHttpHandler = _httpHandler; _httpHandler = httpHandler; handlerChanged = true; - - oldHttpHandler.Dispose(); } - } + else + { + if ((_httpHandler.ConnectTimeout.TotalMilliseconds != _timeout) || (_httpHandler.Proxy != proxy)) + { + SocketsHttpHandler httpHandler = new SocketsHttpHandler(); + httpHandler.ConnectTimeout = TimeSpan.FromMilliseconds(_timeout); + httpHandler.Proxy = proxy; + httpHandler.AllowAutoRedirect = true; + httpHandler.MaxAutomaticRedirections = 10; - if (_httpClient is null) - { - HttpClient httpClient = new HttpClient(_httpHandler); - httpClient.Timeout = TimeSpan.FromMilliseconds(_timeout); - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(HTTP_HEALTH_CHECK_USER_AGENT); + SocketsHttpHandler oldHttpHandler = _httpHandler; + _httpHandler = httpHandler; + handlerChanged = true; - _httpClient = httpClient; - } - else - { - if (handlerChanged || (_httpClient.Timeout.TotalMilliseconds != _timeout)) + oldHttpHandler.Dispose(); + } + } + + if (_httpClient is null) { HttpClient httpClient = new HttpClient(_httpHandler); httpClient.Timeout = TimeSpan.FromMilliseconds(_timeout); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(HTTP_HEALTH_CHECK_USER_AGENT); - HttpClient oldHttpClient = _httpClient; _httpClient = httpClient; - - oldHttpClient.Dispose(); } - } - } - else - { - if (_httpClient != null) - { - _httpClient.Dispose(); - _httpClient = null; - } + else + { + if (handlerChanged || (_httpClient.Timeout.TotalMilliseconds != _timeout)) + { + HttpClient httpClient = new HttpClient(_httpHandler); + httpClient.Timeout = TimeSpan.FromMilliseconds(_timeout); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(HTTP_HEALTH_CHECK_USER_AGENT); - if (_httpHandler != null) - { - _httpHandler.Dispose(); - _httpHandler = null; - } + HttpClient oldHttpClient = _httpClient; + _httpClient = httpClient; + + oldHttpClient.Dispose(); + } + } + break; + + default: + if (_httpClient != null) + { + _httpClient.Dispose(); + _httpClient = null; + } + + if (_httpHandler != null) + { + _httpHandler.Dispose(); + _httpHandler = null; + } + break; } } @@ -386,6 +390,7 @@ namespace Failover } case HealthCheckType.Http: + case HealthCheckType.Https: { ConditionalHttpReload(); @@ -397,12 +402,23 @@ namespace Failover try { Uri url = healthCheckUrl; - if (url is null) + { url = _url; + if (url is null) + return new HealthCheckStatus(false, "Missing health check URL in APP record as well as in app config."); + } - if (url is null) - return new HealthCheckStatus(false, "Missing health check URL in APP record as well as in app config."); + if (_type == HealthCheckType.Http) + { + if (url.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) + url = new Uri("http://" + url.Host + (url.IsDefaultPort ? "" : ":" + url.Port) + url.PathAndQuery); + } + else + { + if (url.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase)) + url = new Uri("https://" + url.Host + (url.IsDefaultPort ? "" : ":" + url.Port) + url.PathAndQuery); + } IPEndPoint ep = new IPEndPoint(address, url.Port); Uri queryUri = new Uri(url.Scheme + "://" + ep.ToString() + url.PathAndQuery); diff --git a/Apps/FailoverApp/dnsApp.config b/Apps/FailoverApp/dnsApp.config index eb06be48..bb81faf2 100644 --- a/Apps/FailoverApp/dnsApp.config +++ b/Apps/FailoverApp/dnsApp.config @@ -30,13 +30,23 @@ "interval": 60, "retries": 3, "timeout": 10, + "url": null, "emailAlert": "default", - "webHook": "default", - "comments": "The APP record data must contain 'healthCheckUrl' property with an url when no url is configured for a 'http' type health check." + "webHook": "default" + }, + { + "name": "https", + "type": "https", + "interval": 60, + "retries": 3, + "timeout": 10, + "url": null, + "emailAlert": "default", + "webHook": "default" }, { "name": "www.example.com", - "type": "http", + "type": "https", "interval": 60, "retries": 3, "timeout": 10,