/*
Technitium DNS Server
Copyright (C) 2024 Shreyas Zare (shreyas@technitium.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using TechnitiumLibrary;
using TechnitiumLibrary.Net.Dns.ResourceRecords;
using TechnitiumLibrary.Net.Http.Client;
using TechnitiumLibrary.Net.Proxy;
namespace Failover
{
class WebHook : IDisposable
{
#region variables
readonly HealthService _service;
readonly string _name;
bool _enabled;
Uri[] _urls;
SocketsHttpHandler _httpHandler;
HttpClientNetworkHandler _httpCustomResolverHandler;
HttpClient _httpClient;
#endregion
#region constructor
public WebHook(HealthService service, JsonElement jsonWebHook)
{
_service = service;
_name = jsonWebHook.GetPropertyValue("name", "default");
Reload(jsonWebHook);
}
#endregion
#region IDisposable
bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
if (_httpClient != null)
{
_httpClient.Dispose();
_httpClient = null;
}
if (_httpHandler != null)
{
_httpHandler.Dispose();
_httpHandler = null;
}
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region private
private void ConditionalHttpReload()
{
bool handlerChanged = false;
NetProxy proxy = _service.DnsServer.Proxy;
if (_httpHandler is null)
{
SocketsHttpHandler httpHandler = new SocketsHttpHandler();
httpHandler.Proxy = proxy;
httpHandler.UseProxy = proxy is not null;
httpHandler.AllowAutoRedirect = true;
httpHandler.MaxAutomaticRedirections = 10;
_httpHandler = httpHandler;
handlerChanged = true;
}
else
{
if (_httpHandler.Proxy != proxy)
{
SocketsHttpHandler httpHandler = new SocketsHttpHandler();
httpHandler.Proxy = proxy;
httpHandler.UseProxy = proxy is not null;
httpHandler.AllowAutoRedirect = true;
httpHandler.MaxAutomaticRedirections = 10;
SocketsHttpHandler oldHttpHandler = _httpHandler;
_httpHandler = httpHandler;
handlerChanged = true;
oldHttpHandler.Dispose();
}
}
if ((_httpCustomResolverHandler is null) || handlerChanged)
_httpCustomResolverHandler = new HttpClientNetworkHandler(_httpHandler, _service.DnsServer.PreferIPv6 ? HttpClientNetworkType.PreferIPv6 : HttpClientNetworkType.Default, _service.DnsServer);
if (_httpClient is null)
{
HttpClient httpClient = new HttpClient(_httpCustomResolverHandler);
_httpClient = httpClient;
}
else
{
if (handlerChanged)
{
HttpClient httpClient = new HttpClient(_httpCustomResolverHandler);
HttpClient oldHttpClient = _httpClient;
_httpClient = httpClient;
oldHttpClient.Dispose();
}
}
}
private async Task CallAsync(HttpContent content)
{
ConditionalHttpReload();
async Task CallWebHook(Uri url)
{
try
{
HttpResponseMessage response = await _httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
_service.DnsServer.WriteLog("Webhook call failed for URL: " + url.AbsoluteUri + "\r\n" + ex.ToString());
}
}
List tasks = new List();
foreach (Uri url in _urls)
tasks.Add(CallWebHook(url));
await Task.WhenAll(tasks);
}
#endregion
#region public
public void Reload(JsonElement jsonWebHook)
{
_enabled = jsonWebHook.GetPropertyValue("enabled", false);
if (jsonWebHook.TryReadArray("urls", delegate (string uri) { return new Uri(uri); }, out Uri[] urls))
_urls = urls;
else
_urls = null;
ConditionalHttpReload();
}
public Task CallAsync(IPAddress address, string healthCheck, HealthCheckResponse healthCheckResponse)
{
if (!_enabled)
return Task.CompletedTask;
HttpContent content;
{
using (MemoryStream mS = new MemoryStream())
{
Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
jsonWriter.WriteString("address", address.ToString());
jsonWriter.WriteString("healthCheck", healthCheck);
jsonWriter.WriteString("status", healthCheckResponse.Status.ToString());
if (healthCheckResponse.Status == HealthStatus.Failed)
jsonWriter.WriteString("failureReason", healthCheckResponse.FailureReason);
jsonWriter.WriteString("dateTime", healthCheckResponse.DateTime);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
content = new ByteArrayContent(mS.ToArray());
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
return CallAsync(content);
}
public Task CallAsync(IPAddress address, string healthCheck, Exception ex)
{
if (!_enabled)
return Task.CompletedTask;
HttpContent content;
{
using (MemoryStream mS = new MemoryStream())
{
Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
jsonWriter.WriteString("address", address.ToString());
jsonWriter.WriteString("healthCheck", healthCheck);
jsonWriter.WriteString("status", "Error");
jsonWriter.WriteString("failureReason", ex.ToString());
jsonWriter.WriteString("dateTime", DateTime.UtcNow);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
content = new ByteArrayContent(mS.ToArray());
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
return CallAsync(content);
}
public Task CallAsync(string domain, DnsResourceRecordType type, string healthCheck, HealthCheckResponse healthCheckResponse)
{
if (!_enabled)
return Task.CompletedTask;
HttpContent content;
{
using (MemoryStream mS = new MemoryStream())
{
Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
jsonWriter.WriteString("domain", domain);
jsonWriter.WriteString("recordType", type.ToString());
jsonWriter.WriteString("healthCheck", healthCheck);
jsonWriter.WriteString("status", healthCheckResponse.Status.ToString());
if (healthCheckResponse.Status == HealthStatus.Failed)
jsonWriter.WriteString("failureReason", healthCheckResponse.FailureReason);
jsonWriter.WriteString("dateTime", healthCheckResponse.DateTime);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
content = new ByteArrayContent(mS.ToArray());
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
return CallAsync(content);
}
public Task CallAsync(string domain, DnsResourceRecordType type, string healthCheck, Exception ex)
{
if (!_enabled)
return Task.CompletedTask;
HttpContent content;
{
using (MemoryStream mS = new MemoryStream())
{
Utf8JsonWriter jsonWriter = new Utf8JsonWriter(mS);
jsonWriter.WriteStartObject();
jsonWriter.WriteString("domain", domain);
jsonWriter.WriteString("recordType", type.ToString());
jsonWriter.WriteString("healthCheck", healthCheck);
jsonWriter.WriteString("status", "Error");
jsonWriter.WriteString("failureReason", ex.ToString());
jsonWriter.WriteString("dateTime", DateTime.UtcNow);
jsonWriter.WriteEndObject();
jsonWriter.Flush();
content = new ByteArrayContent(mS.ToArray());
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
return CallAsync(content);
}
#endregion
#region properties
public string Name
{ get { return _name; } }
public bool Enabled
{ get { return _enabled; } }
public Uri[] Urls
{ get { return _urls; } }
#endregion
}
}