Mercurial > servermonitor
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 } |