Created
February 1, 2018 04:43
-
-
Save IDisposable/15742e92aa0391d07a8d5f0741750d9e to your computer and use it in GitHub Desktop.
A once-in-a-while pinger of websites
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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