Before starting the fundamental
about Owin Middleware, Let me start with Owin (Open web interface) in .net.
Owin come in picture when Asp.net decoupled web app and web
servers.It defines a standard way for middleware to be used in a pipeline to
handle requests and associated responses .
Owin Middleware :- This is a component that could be use to
control request and response in Asp.net.
A
good takeaway from the above is that the Next property is optional, meaning we
can stop the pipeline ourselves by simply not invoking the next OwinMiddleware.
To
implement OwinMiddleware we will need a constructor that calls the
base constructor and an implementation of the Invoke method. Let's spin
up an example.
Install-Package Microsoft.Owin.Host.SystemWeb
The abstract OwinMiddleware
class looks like this:
/// <summary>
/// An abstract base class for a standard middleware pattern.
/// </summary>
public abstract class OwinMiddleware {
/// <summary>
/// Instantiates the middleware with an optional pointer to the next component.
/// </summary>
/// <param name="next"></param>
protected OwinMiddleware(OwinMiddleware next) {
Next = next;
}
/// <summary>
/// The optional next component.
/// </summary>
protected OwinMiddleware Next { get; set; }
/// <summary>
/// Process an individual request.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public abstract Task Invoke(IOwinContext context);
}
This is then registered like in StartUp class
app.Use(typeof(MyMiddlewareClass));
Here is the example of Custom Health Check Owin middleware in Asp.net :-
We will take two different type of health check middleware pipeline example
public static class AppBuilderExtensions
{
public static IAppBuilder UseHealthCheck(this IAppBuilder app, string route, HealthCheckConfiguration config = null)
{
config = config ?? new HealthCheckConfiguration();
return app.Map(route, x => x.Use<HealthCheckMiddleware>(config));
}
}
public class HealthCheckConfiguration
{
public TimeSpan Timeout { get; set; }
public IList<ICheckHealth> HealthChecks { get; set; }
public HealthCheckConfiguration(TimeSpan timeout = default(TimeSpan), IList<ICheckHealth> healthchecks = null)
{
Timeout = timeout;
HealthChecks = healthchecks;
}
}
public interface ICheckHealth
{
string Name { get; }
Task<HealthCheckStatus> Check();
}
public sealed class HealthCheckStatus
{
public string Message { get; set; }
public bool HasFailed { get; set; }
public HealthCheckStatus(string message, bool hasFailed)
{
Message = message;
HasFailed = hasFailed;
}
public static HealthCheckStatus Failed(string message = null)
{
return new HealthCheckStatus(message ?? "Failed", true);
}
public static HealthCheckStatus Passed(string message = null)
{
return new HealthCheckStatus(message ?? "Success", false);
}
}
public class HealthCheckMiddleware : OwinMiddleware
{
private IList<ICheckHealth> _healthChecks;
private readonly TimeSpan _timeout;
public HealthCheckMiddleware(OwinMiddleware next, HealthCheckConfiguration config) : base(next)
{
_healthChecks = (config.HealthChecks ?? Enumerable.Empty<ICheckHealth>()).ToArray(); ;
_timeout = config.Timeout;
}
public override async Task Invoke(IOwinContext context)
{
if (!string.IsNullOrEmpty(context.Request.Path.Value))
{
await Next.Invoke(context);
return;
}
//var queryParameters = context.Request.GetQueryParameters();
//var debug = false;
//if (queryParameters.ContainsKey("debug"))
// bool.TryParse(queryParameters["debug"], out debug);
var checkTasks = _healthChecks.Select(async x =>
{
try
{
return new
{
Name = x.Name,
Status = await x.Check().ConfigureAwait(false)
};
}
catch (Exception e)
{
return new
{
Name = x.Name,
Status = HealthCheckStatus.Failed(e.Message)
};
}
});
var allTasks = Task.WhenAll(checkTasks.ToArray());
if (allTasks != await Task.WhenAny(allTasks, Task.Delay(_timeout)).ConfigureAwait(false))
{
context.Response.StatusCode = (int)HttpStatusCode.GatewayTimeout;
}
else
{
var results = allTasks.Result;
var hasFailed = results.Any(x => x.Status.HasFailed);
context.Response.StatusCode = hasFailed ? (int)HttpStatusCode.ServiceUnavailable : (int)HttpStatusCode.OK;
var sb = new StringBuilder();
foreach (var r in results)
sb.AppendLine(r.Name + ": " + r.Status.Message);
await context.Response.WriteAsync(sb.ToString());
}
}
}
1. HttpHealthCheck :- This pipeline will use to check the http client health check in Asp.net pipeline
public class HttpHealthCheck : BaseHealthCheck
{
private readonly Uri _uri;
private readonly ICredentials _credentials;
public HttpHealthCheck(string name, Uri uri, ICredentials credentials = null)
{
Name = name;
_uri = uri;
_credentials = credentials;
}
public override async Task<HealthCheckStatus> Check()
{
var request = (HttpWebRequest)WebRequest.Create(_uri);
request.UserAgent = "Owin.HealthCheck";
request.Method = "GET";
request.Credentials = _credentials;
using (var response = (HttpWebResponse)await request.GetResponseAsync().ConfigureAwait(false))
{
var statusCode = (int)response.StatusCode;
if (statusCode >= 200 && statusCode < 300)
return HealthCheckStatus.Passed(response.StatusDescription);
else
return HealthCheckStatus.Failed(response.StatusDescription);
}
}
}
2. Ping Health Check Pipeline:- This pipeline is used to check the server health while process the request
public class PingHealthCheck : BaseHealthCheck
{
private readonly string _host;
private readonly TimeSpan _timeout;
public PingHealthCheck(string name, string host, TimeSpan timeout)
{
if (string.IsNullOrEmpty(host))
throw new ArgumentException(string.Format("host cannot be empty {0}", name));
Name = name;
_host = host;
_timeout = timeout;
}
public override async Task<HealthCheckStatus> Check()
{
var ping = new Ping();
var result = await ping.SendPingAsync(_host, (int)_timeout.TotalMilliseconds).ConfigureAwait(false);
return new HealthCheckStatus(result.Status.ToString(), result.Status != IPStatus.Success);
}
}
To register the middleware you need to pass the type
app.UseHealthCheck("/healthcheck", new HealthCheckConfiguration
{
Timeout = TimeSpan.FromSeconds(20),
HealthChecks = new List<ICheckHealth>()
{
new HttpHealthCheck("Google Check", new Uri("https://www.google.com")),
new PingHealthCheck("Local Ping", "localhost", TimeSpan.FromSeconds(10))
}
});
No comments:
Post a Comment