comparison 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
comparison
equal deleted inserted replaced
17:68d7834dc28e 18:b713b9db4c82
1 using System; 1 using System;
2 using System.Collections.Generic; 2 using System.Collections.Generic;
3 using System.ComponentModel; 3 using System.ComponentModel;
4 using System.IO;
4 using System.Linq; 5 using System.Linq;
5 using System.Text; 6 using System.Net.Http;
6 using System.Text.RegularExpressions; 7 using System.Text.RegularExpressions;
7 using System.Threading; 8 using System.Threading;
8 using System.Threading.Tasks; 9 using System.Threading.Tasks;
9 10
10 namespace ServerMonitorApp 11 namespace ServerMonitorApp
11 { 12 {
13 /// <summary>Executes an HTTP request and checks the result or status code.</summary>
12 [DisplayName("HTTP check"), Description("Check the result of an HTTP request"), DisplayWeight(1)] 14 [DisplayName("HTTP check"), Description("Check the result of an HTTP request"), DisplayWeight(1)]
13 public class HttpCheck : Check 15 public class HttpCheck : Check
14 { 16 {
17 /// <summary>The URL to request.</summary>
15 public string Url { get; set; } 18 public string Url { get; set; }
16 19
20 /// <summary>The HTTP method for the request.</summary>
21 /// <remarks>Only HEAD and GET are supported.</remarks>
22 public string Method { get; set; }
23
24 /// <summary>Whether the HTTP status code should be checked.</summary>
17 public bool CheckResponseCode { get; set; } 25 public bool CheckResponseCode { get; set; }
18 26
27 /// <summary>The required response code if CheckResponseCode is true.</summary>
19 public int ResponseCode { get; set; } 28 public int ResponseCode { get; set; }
20 29
30 /// <summary>Whether the response lenth should be checked.</summary>
21 public bool CheckResponseLength { get; set; } 31 public bool CheckResponseLength { get; set; }
22 32
33 /// <summary>The required minimum response length if CheckResponseLength is true.</summary>
23 public string ResponseLengthMin { get; set; } 34 public string ResponseLengthMin { get; set; }
24 35
36 /// <summary>The required maximum response length if CheckResponseLength is true.</summary>
25 public string ResponseLengthMax { get; set; } 37 public string ResponseLengthMax { get; set; }
26 38
39 /// <summary>Whether the response body should be checked.</summary>
27 public bool CheckResponseBody { get; set; } 40 public bool CheckResponseBody { get; set; }
28 41
42 /// <summary>The method to use when checking the response content against the pattern.</summary>
29 public MatchType ResponseBodyMatchType { get; set; } 43 public MatchType ResponseBodyMatchType { get; set; }
30 44
45 /// <summary>The string or pattern that the response content must match if CheckResponseBody is true.</summary>
31 public string ResponseBodyPattern { get; set; } 46 public string ResponseBodyPattern { get; set; }
32 47
48 /// <summary>Whether the ResponseBodyPattern should be interpreted as a regular expression.</summary>
33 public bool ResponseBodyUseRegex { get; set; } 49 public bool ResponseBodyUseRegex { get; set; }
34 50
35 protected override Task<CheckResult> ExecuteCheckAsync(CancellationToken token) 51 /// <summary>Executes the HTTP command on the server.</summary>
52 protected async override Task<CheckResult> ExecuteCheckAsync(CancellationToken token)
36 { 53 {
37 throw new NotImplementedException(); 54 try
38 55 {
39 56 // Disable auto redirect so we can get the true response code of the request.
40 57 using (HttpClientHandler handler = new HttpClientHandler() { AllowAutoRedirect = false })
41 58 using (HttpClient client = new HttpClient(handler))
42 // Cancellable version that doesn't actulaly work 59 using (HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(Method), new Uri(Url)))
43 // might be useful as a TaskCompletionSource example though 60 {
44 // 61 token.Register(client.CancelPendingRequests);
45 //TaskCompletionSource<CheckResult> tcs = new TaskCompletionSource<CheckResult 62 HttpResponseMessage response = await client.SendAsync(request, token);
46 // 63 token.ThrowIfCancellationRequested();
47 //using (Ping ping = new Ping()) 64 List<CheckResult> results = await ProcessResponse(response);
48 //{ 65 return MergeResults(results.ToArray());
49 // token.Register(ping.SendAsyncCancel); 66 }
50 // ping.PingCompleted += (sender, e) => 67 }
51 // { 68 catch (Exception e)
52 // if (e.Error != null) 69 {
53 // tcs.SetResult(Fail("Ping failed: " + e.Error.GetBaseException().Message)); 70 return Fail(e);
54 // else if (e.Reply.Status != IPStatus.Success) 71 }
55 // tcs.SetResult(Fail("Ping failed: " + e.Reply.Status.ToString()));
56 // else
57 // tcs.SetResult(Pass("Ping completed in " + e.Reply.RoundtripTime + "ms"));
58 // };
59 // ping.SendAsync(Server.Host, Timeout, null);
60 //}
61
62 //return tcs.Task;
63
64 } 72 }
65 73
74 ///// <summary>Processes an HTTP response and checks that it matches the expected values.</summary>
75 ///// <param name="response">The HTTP response.</param>
76 ///// <returns>A list of check results according to user preferences.</returns>
77 protected async virtual Task<List<CheckResult>> ProcessResponse(HttpResponseMessage response)
78 {
79 List<CheckResult> results = new List<CheckResult>();
80
81 // Check the actual response code against the expected response code if response code checking is enabled.
82 if (CheckResponseCode)
83 results.Add(GetIntResult(ResponseCode, (int)response.StatusCode, "Response code"));
84
85 // Check the actual response length against the expected response length if response length checking is enabled.
86 if (CheckResponseLength)
87 {
88 string length = null;
89 if (Method == "HEAD")
90 {
91 // Use the Content-Length header if a HEAD request.
92 if (response.Headers.TryGetValues("Content-Length", out IEnumerable<string> values))
93 {
94 length = values.First();
95 }
96 }
97 else
98 {
99 // For a GET request, read the actual length
100 Stream stream = await response.Content.ReadAsStreamAsync();
101 length = stream.Length.ToString();
102 }
103 if (length != null)
104 {
105 results.Add(GetStringResult(MatchType.GreaterThan, (int.Parse(ResponseLengthMin) * 1024).ToString(), false, length, "Response length"));
106 results.Add(GetStringResult(MatchType.LessThan, (int.Parse(ResponseLengthMax) * 1024).ToString(), false, length, "Response length"));
107 }
108 else
109 {
110 results.Add(Fail("Could not get content length"));
111 }
112 }
113
114 // Check the actual response content against the expected response content if response content checking is enabled.
115 if (CheckResponseBody && Method != "HEAD")
116 {
117 string content = await response.Content.ReadAsStringAsync();
118 results.Add(GetStringResult(ResponseBodyMatchType, ResponseBodyPattern, ResponseBodyUseRegex, content, "Response body"));
119 }
120
121 return results;
122 }
123
124 /// <summary>Validates HTTP check options.</summary>
66 public override string Validate(bool saving = true) 125 public override string Validate(bool saving = true)
67 { 126 {
68 string message = base.Validate(); 127 string message = base.Validate();
69 if (Url.IsNullOrEmpty()) 128 if (Url.IsNullOrEmpty())
70 message += "URL cannot be blank." + Environment.NewLine; 129 message += "URL cannot be blank." + Environment.NewLine;
82 } 141 }
83 } 142 }
84 return message; 143 return message;
85 } 144 }
86 145
87 //protected override CheckResult GetIntResult(int expectedValue, int resultValue, string description)
88 //{
89 // CheckResult result = base.GetIntResult(expectedValue, resultValue, description);
90
91 //}
92
93 /*
94 100 Continue[RFC7231, Section 6.2.1]
95 101 Switching Protocols[RFC7231, Section 6.2.2]
96 102 Processing[RFC2518]
97 103 Early Hints[RFC8297]
98 200 OK[RFC7231, Section 6.3.1]
99 201 Created[RFC7231, Section 6.3.2]
100 202 Accepted[RFC7231, Section 6.3.3]
101 203 Non-Authoritative Information[RFC7231, Section 6.3.4]
102 204 No Content[RFC7231, Section 6.3.5]
103 205 Reset Content[RFC7231, Section 6.3.6]
104 206 Partial Content[RFC7233, Section 4.1]
105 207 Multi-Status[RFC4918]
106 208 Already Reported[RFC5842]
107 226 IM Used[RFC3229]
108 300 Multiple Choices[RFC7231, Section 6.4.1]
109 301 Moved Permanently[RFC7231, Section 6.4.2]
110 302 Found[RFC7231, Section 6.4.3]
111 303 See Other[RFC7231, Section 6.4.4]
112 304 Not Modified[RFC7232, Section 4.1]
113 305 Use Proxy[RFC7231, Section 6.4.5]
114 306 (Unused)[RFC7231, Section 6.4.6]
115 307 Temporary Redirect[RFC7231, Section 6.4.7]
116 308 Permanent Redirect[RFC7538]
117 400 Bad Request[RFC7231, Section 6.5.1]
118 401 Unauthorized[RFC7235, Section 3.1]
119 402 Payment Required[RFC7231, Section 6.5.2]
120 403 Forbidden[RFC7231, Section 6.5.3]
121 404 Not Found[RFC7231, Section 6.5.4]
122 405 Method Not Allowed[RFC7231, Section 6.5.5]
123 406 Not Acceptable[RFC7231, Section 6.5.6]
124 407 Proxy Authentication Required[RFC7235, Section 3.2]
125 408 Request Timeout[RFC7231, Section 6.5.7]
126 409 Conflict[RFC7231, Section 6.5.8]
127 410 Gone[RFC7231, Section 6.5.9]
128 411 Length Required[RFC7231, Section 6.5.10]
129 412 Precondition Failed[RFC7232, Section 4.2][RFC8144, Section 3.2]
130 413 Payload Too Large[RFC7231, Section 6.5.11]
131 414 URI Too Long[RFC7231, Section 6.5.12]
132 415 Unsupported Media Type[RFC7231, Section 6.5.13][RFC7694, Section 3]
133 416 Range Not Satisfiable[RFC7233, Section 4.4]
134 417 Expectation Failed[RFC7231, Section 6.5.14]
135 421 Misdirected Request[RFC7540, Section 9.1.2]
136 422 Unprocessable Entity[RFC4918]
137 423 Locked[RFC4918]
138 424 Failed Dependency[RFC4918]
139 425 Too Early[RFC8470]
140 426 Upgrade Required[RFC7231, Section 6.5.15]
141 427 Unassigned
142 428 Precondition Required[RFC6585]
143 429 Too Many Requests[RFC6585]
144 430 Unassigned
145 431 Request Header Fields Too Large[RFC6585]
146 451 Unavailable For Legal Reasons[RFC7725]
147 500 Internal Server Error[RFC7231, Section 6.6.1]
148 501 Not Implemented[RFC7231, Section 6.6.2]
149 502 Bad Gateway[RFC7231, Section 6.6.3]
150 503 Service Unavailable[RFC7231, Section 6.6.4]
151 504 Gateway Timeout[RFC7231, Section 6.6.5]
152 505 HTTP Version Not Supported[RFC7231, Section 6.6.6]
153 506 Variant Also Negotiates[RFC2295]
154 507 Insufficient Storage[RFC4918]
155 508 Loop Detected[RFC5842]
156 509 Unassigned
157 510 Not Extended[RFC2774]
158 511 Network Authentication Required[RFC6585]
159 */
160 } 146 }
161 } 147 }