view ServerMonitor/Objects/Checks/HttpCheck.cs @ 18:b713b9db4c82

HTTP checks.
author Brad Greco <brad@bgreco.net>
date Mon, 27 May 2019 15:40:44 -0400
parents 68d7834dc28e
children 7645122aa7a9
line wrap: on
line source

´╗┐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
{
    /// <summary>Executes an HTTP request and checks the result or status code.</summary>
    [DisplayName("HTTP check"), Description("Check the result of an HTTP request"), DisplayWeight(1)]
    public class HttpCheck : Check
    {
        /// <summary>The URL to request.</summary>
        public string Url { get; set; }

        /// <summary>The HTTP method for the request.</summary>
        /// <remarks>Only HEAD and GET are supported.</remarks>
        public string Method { get; set; }

        /// <summary>Whether the HTTP status code should be checked.</summary>
        public bool CheckResponseCode { get; set; }

        /// <summary>The required response code if CheckResponseCode is true.</summary>
        public int ResponseCode { get; set; }

        /// <summary>Whether the response lenth should be checked.</summary>
        public bool CheckResponseLength { get; set; }

        /// <summary>The required minimum response length if CheckResponseLength is true.</summary>
        public string ResponseLengthMin { get; set; }

        /// <summary>The required maximum response length if CheckResponseLength is true.</summary>
        public string ResponseLengthMax { get; set; }

        /// <summary>Whether the response body should be checked.</summary>
        public bool CheckResponseBody { get; set; }

        /// <summary>The method to use when checking the response content against the pattern.</summary>
        public MatchType ResponseBodyMatchType { get; set; }

        /// <summary>The string or pattern that the response content must match if CheckResponseBody is true.</summary>
        public string ResponseBodyPattern { get; set; }

        /// <summary>Whether the ResponseBodyPattern should be interpreted as a regular expression.</summary>
        public bool ResponseBodyUseRegex { get; set; }

        /// <summary>Executes the HTTP command on the server.</summary>
        protected async override Task<CheckResult> 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<CheckResult> results = await ProcessResponse(response);
                    return MergeResults(results.ToArray());
                }
            }
            catch (Exception e)
            {
                return Fail(e);
            }
        }

        ///// <summary>Processes an HTTP response and checks that it matches the expected values.</summary>
        ///// <param name="response">The HTTP response.</param>
        ///// <returns>A list of check results according to user preferences.</returns>
        protected async virtual Task<List<CheckResult>> ProcessResponse(HttpResponseMessage response)
        {
            List<CheckResult> results = new List<CheckResult>();

            // 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<string> 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;
        }

        /// <summary>Validates HTTP check options.</summary>
        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;
        }

    }
}