Skip to content

Instantly share code, notes, and snippets.

@IDisposable
Created February 1, 2018 04:43
Show Gist options
  • Save IDisposable/15742e92aa0391d07a8d5f0741750d9e to your computer and use it in GitHub Desktop.
Save IDisposable/15742e92aa0391d07a8d5f0741750d9e to your computer and use it in GitHub Desktop.
A once-in-a-while pinger of websites
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Cache;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Configuration;
using Alerts.Data.API;
using Alerts.Data.API.DB;
using Alerts.Helpers;
namespace Alerts.API
{
public static class TaskHelpers
{
//Fire-and-forget task, essentially the same as what is in Microsoft.VisualStudio.Threading do we want to include that instead?
public static void Forget(this Task task) { }
}
public static partial class ElmahExtensions
{
public class PingingException : Exception
{
public IClientSiteStatus Context { get; set; }
public PingingException(IClientSiteStatus context, Exception innerException)
: base(BuildMessageFromContext(context), innerException)
{
Context = context;
}
private static string BuildMessageFromContext(IClientSiteStatus context)
{
if (context == null)
return "Pinging Exception:";
else
return "Pinging Exception:\n\t" + context.ToJson();
}
}
public static Exception LogException(this Exception ex, IClientSiteStatus context)
{
var wrappedEx = new PingingException(context, ex);
if (HttpContext.Current != null)//website is logging the error
{
var elmahCon = Elmah.ErrorSignal.FromCurrentContext();
elmahCon.Raise(wrappedEx);
}
else//non website, probably an agent
{
var elmahCon = Elmah.ErrorLog.GetDefault(null);
elmahCon.Log(new Elmah.Error(wrappedEx));
}
return ex;
}
}
public class Pinger
{
private static readonly Lazy<int> s_SleepTime = new Lazy<int>(() =>
{
int value;
var valueSetting = WebConfigurationManager.AppSettings["sleepTime"];
if (valueSetting.IsNullOrWhiteSpace() || !int.TryParse(valueSetting, out value))
value = 60;
return value;
});
public static int SleepTime
{
get
{
return s_SleepTime.Value;
}
}
private static readonly Lazy<string> s_DefaultUserAgent = new Lazy<string>(() =>
{
var val = WebConfigurationManager.AppSettings["userAgent"];
if (val.IsNullOrWhiteSpace())
{
// default it
val = "Mozilla/5.0 (Windows NT 6.3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.0 (compatible) Safari/537.36 (compatible) AlertWireBot/1.0 (https://www.alertwire.com/bot.html)";
}
return val;
});
public static string DefaultUserAgent
{
get
{
return s_DefaultUserAgent.Value;
}
}
private CancellationTokenSource tokenSource;
static Pinger()
{
// allow 100's
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
}
public void Start()
{
if (tokenSource == null)
tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Task.Factory.StartNew(
async () =>
{
await CircularRunner(token);
},
token,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
TaskScheduler.Default)
.Forget();
}
public void Stop()
{
if (tokenSource != null)
{
tokenSource.Cancel();
tokenSource = null;
}
}
private async Task CircularRunner(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var sites = Repositories.Instance.ClientSite.GetMonitoringDue();
await PingAll(sites, token);
// now wait until the next chance to run
await Task.Delay(SleepTime * 1000, token);
}
}
private async Task PingAll(IEnumerable<IClientSiteAudit> sites, CancellationToken token)
{
foreach (var site in sites)
{
var currentStatus = Repositories.Instance.ClientSiteStatus.GetStatus(site.ClientId, site.SiteId);
var success = await PingOne(site, currentStatus, token);
currentStatus = Repositories.Instance.ClientSiteStatus.NotePingResult(site.ClientId, site.SiteId, success, currentStatus.LastStatusCode, currentStatus.LastMessage);
// we send notifications based on CURRENT status and last notification with no override message,
Repositories.Instance.ClientSiteStatus.MakeNotifications(currentStatus, null, null, true);
}
}
private async Task<bool> PingOne(IClientSiteAudit site, IClientSiteStatus currentStatus, CancellationToken masterToken)
{
var request = CreateRequest(site.UrlLive);
try
{
//Method from http://stackoverflow.com/questions/19211972/getresponseasync-does-not-accept-cancellationtoken
var requestTokenSource = CancellationTokenSource.CreateLinkedTokenSource(masterToken);
requestTokenSource.CancelAfter(request.Timeout);
var requestToken = requestTokenSource.Token;
using (requestToken.Register(() => request.Abort(), useSynchronizationContext: false))
{
requestToken.ThrowIfCancellationRequested();
using (var response = await request.GetResponseAsync() as HttpWebResponse)
{
requestToken.ThrowIfCancellationRequested();
if (response != null)
{
// any 2xx is success!
if ((int)response.StatusCode / 100 == 2)
{
currentStatus.LastStatusCode = (int)response.StatusCode;
currentStatus.LastOkayPing = DateTime.UtcNow;
return true;
}
else
{
currentStatus.LastStatusCode = (int)response.StatusCode;
currentStatus.LastFailedPing = DateTime.UtcNow;
currentStatus.LastMessage = response.StatusDescription;
}
}
}
}
}
catch (AggregateException aex)
{
aex.Handle((ex) =>
{
return HandleAndLogException(currentStatus, ex);
});
}
catch (Exception ex)
{
HandleAndLogException(currentStatus, ex);
}
return false;
}
private bool HandleAndLogException(IClientSiteStatus status, Exception ex)
{
var odex = ex as ObjectDisposedException;
if (odex != null)
return true; // we don't care
ex.LogException(status);
status.LastFailedPing = DateTime.UtcNow;
var wex = ex as WebException;
if (wex != null)
{
var response = wex.Response as HttpWebResponse;
status.LastStatusCode = (int)(response == null ? HttpStatusCode.ExpectationFailed : response.StatusCode);
status.LastMessage = (response == null ? wex.Message : response.StatusDescription);
}
else
{
var hex = ex as HttpException;
if (hex != null)
{
status.LastStatusCode = hex.GetHttpCode();
status.LastMessage = hex.Message;
}
else
{
var tex = ex as TimeoutException;
if (tex != null)
{
status.LastStatusCode = (int)HttpStatusCode.RequestTimeout;
status.LastMessage = tex.Message;
}
else
{
status.LastStatusCode = (int)HttpStatusCode.ExpectationFailed;
status.LastMessage = ex.Message;
}
}
}
return true;
}
private static readonly RequestCachePolicy s_BypassCaching = new RequestCachePolicy(RequestCacheLevel.BypassCache);
private HttpWebRequest CreateRequest(string url)
{
var request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "GET";
request.CookieContainer = new CookieContainer();
request.UserAgent = DefaultUserAgent;
request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
request.Headers.Add("Accept-Encoding: gzip,deflate");
request.Headers.Add("Accept-Language: en-US,en;q=0.8,la;q=0.6");
request.Timeout = 30 * 1000; // we don't want to wait too long...
request.ReadWriteTimeout = 30 * 1000;
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.AllowAutoRedirect = true;
request.AuthenticationLevel = AuthenticationLevel.None;
request.CachePolicy = s_BypassCaching;
return request;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment