using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace ServerMonitorApp
{
/// Executes an HTTP request and checks the result or status code.
[DisplayName("HTTP check"), Description("Check the result of an HTTP request"), DisplayWeight(1)]
public class HttpCheck : Check
{
/// The URL to request.
public string Url { get; set; }
/// The HTTP method for the request.
/// Only HEAD and GET are supported.
public string Method { get; set; }
/// Whether the HTTP status code should be checked.
public bool CheckResponseCode { get; set; }
/// The required response code if CheckResponseCode is true.
public int ResponseCode { get; set; }
/// Whether the response lenth should be checked.
public bool CheckResponseLength { get; set; }
/// The required minimum response length if CheckResponseLength is true.
public string ResponseLengthMin { get; set; }
/// The required maximum response length if CheckResponseLength is true.
public string ResponseLengthMax { get; set; }
/// Whether the response body should be checked.
public bool CheckResponseBody { get; set; }
/// The method to use when checking the response content against the pattern.
public MatchType ResponseBodyMatchType { get; set; }
/// The string or pattern that the response content must match if CheckResponseBody is true.
public string ResponseBodyPattern { get; set; }
/// Whether the ResponseBodyPattern should be interpreted as a regular expression.
public bool ResponseBodyUseRegex { get; set; }
/// Executes the HTTP command on the server.
protected async override Task ExecuteCheckAsync(CancellationToken token)
{
try
{
// Disable auto redirect so we can get the true response code of the request.
using (HttpClientHandler handler = new HttpClientHandler() { AllowAutoRedirect = false })
using (HttpClient client = new HttpClient(handler))
using (HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(Method), new Uri(Url)))
{
token.Register(client.CancelPendingRequests);
HttpResponseMessage response = await client.SendAsync(request, token);
token.ThrowIfCancellationRequested();
List results = await ProcessResponse(response);
return MergeResults(results.ToArray());
}
}
catch (Exception e)
{
return Fail(e);
}
}
///// Processes an HTTP response and checks that it matches the expected values.
///// The HTTP response.
///// A list of check results according to user preferences.
protected async virtual Task> ProcessResponse(HttpResponseMessage response)
{
List results = new List();
// Check the actual response code against the expected response code if response code checking is enabled.
if (CheckResponseCode)
results.Add(GetIntResult(ResponseCode, (int)response.StatusCode, "Response code"));
// Check the actual response length against the expected response length if response length checking is enabled.
if (CheckResponseLength)
{
string length = null;
if (Method == "HEAD")
{
// Use the Content-Length header if a HEAD request.
if (response.Headers.TryGetValues("Content-Length", out IEnumerable values))
{
length = values.First();
}
}
else
{
// For a GET request, read the actual length
Stream stream = await response.Content.ReadAsStreamAsync();
length = stream.Length.ToString();
}
if (length != null)
{
results.Add(GetStringResult(MatchType.GreaterThan, (int.Parse(ResponseLengthMin) * 1024).ToString(), false, length, "Response length"));
results.Add(GetStringResult(MatchType.LessThan, (int.Parse(ResponseLengthMax) * 1024).ToString(), false, length, "Response length"));
}
else
{
results.Add(Fail("Could not get content length"));
}
}
// Check the actual response content against the expected response content if response content checking is enabled.
if (CheckResponseBody && Method != "HEAD")
{
string content = await response.Content.ReadAsStringAsync();
results.Add(GetStringResult(ResponseBodyMatchType, ResponseBodyPattern, ResponseBodyUseRegex, content, "Response body"));
}
return results;
}
/// Validates HTTP check options.
public override string Validate(bool saving = true)
{
string message = base.Validate();
if (Url.IsNullOrEmpty())
message += "URL cannot be blank." + Environment.NewLine;
if (!CheckResponseCode && !CheckResponseLength && !CheckResponseBody)
message += "At least one check must be enabled." + Environment.NewLine;
if (CheckResponseBody && ResponseBodyUseRegex)
{
try
{
Regex re = new Regex(ResponseBodyPattern);
}
catch (ArgumentException)
{
message += "Invalid regular expression for response body." + Environment.NewLine;
}
}
return message;
}
}
}