Home > other >  How should I handle multiple requests for a system status page?
How should I handle multiple requests for a system status page?

Time:07-12

I created an ASP.NET application because I'm more familiar with it, and the only purpose is to show the status of multiple websites (22) from the same entity.

Because I didn't know how to create a single void that creates the HttpClient, created a void for every website.

public async Task CheckStatusWebsiteName1()
    {
        var handler = new HttpClientHandler()
        {
            ServerCertificateCustomValidationCallback = delegate { return true; },
        };
        var client = new HttpClient(handler);
        var response = await client.GetAsync(url-variable);
        var status = response.StatusCode;

        if (status == HttpStatusCode.OK)
        {
            ViewData["WebsiteName"] = "available";
        }
        else
        {
            ViewData["WebSiteName"] = "unavailable";
        }

        if (status == HttpStatusCode.ServiceUnavailable)
        {
            ViewData["WebsiteName"] = "under-maintance";
        }

        client.Dispose();
    }

available and unavailable are span classes that show a circle red/yellow/green based on the ViewData.

Passing all the voids in the Index will make the application load very slow:

public async Task<IActionResult> Index()
    {
        await CheckStatusWebsiteName1();
        await CheckStatusWebsiteName2();
        ...
        return View();
    }

How I should refactor it to make the first-page load faster, and get the status of the pages at the same time?

CodePudding user response:

Resue the HttpClient:

static HttpClient client = CreateClient();

private static HttpClient CreateClient()
{
    var header = new AuthenticationHeaderValue("Basic", ConfigurationManager.AppSettings["Authorization"]);
    var proxy = ConfigurationManager.AppSettings["Proxy"];
    if (!string.IsNullOrWhiteSpace(proxy))
    {
        return new HttpClient(new HttpClientHandler
        {
            Proxy = new WebProxy(proxy),
        })
        {
            DefaultRequestHeaders =
            {
                Authorization = header,
            }
        };
    }
    return new HttpClient()
    {
        DefaultRequestHeaders =
            {
                Authorization = header,
            }
    };
}

Only have one method that accepts a Url, you can call it from the BackEnd to populate the ViewData or you could call it from the front end and return the response and render the response on the status page.

There's loads of examples on GitHub.

[Route("api/{*websiteUrl}")]
[HttpGet]
public async Task CheckStatus(string websiteUrl)
{

    HttpResponseMessage response = new HttpResponseMessage();
    try
    {
        response = await client.GetAsync("https://"   websiteUrl);
    }
    catch() {}

    if (response.IsSuccessStatusCode)
    {
        ViewData["WebsiteName"] = "available";
    }
    else
    {
        ViewData["WebSiteName"] = "unavailable";
    }

    if (status == HttpStatusCode.ServiceUnavailable)
    {
        ViewData["WebsiteName"] = "under-maintance";
    }
}

CodePudding user response:

You can load an index page and then query the individual pages with javascript (then you only need one Task as an API endpoint for example CheckSite(string siteUrl) which returns its status.

CodePudding user response:

if the websites information is stored in application or database, then you can configure a service that will setup all websites information at application startup, along with their tasks. Then, you can use this service at your controller action to re-check the status.

here is how I would've done it :

  • Implement a class model that holds the site information (name, url, status, and last update time).
  • Implement a service that would be configured once at application startup with the websites information along with the tasks, so it can be reused and updated whenever needed.
  • since it's going to be asynchrony operation, then the underlying collections should thread-safe, hence, use System.Collections.Concurrent collections.

Note : validations were omitted for the sake of brevity.

first WebStatusModel :

public class WebsiteStatusModel
{
    public string Name { get; }
    public string Url { get; }
    public string Status { get; private set; }
    public DateTime LastCheck { get; private set; }

    public WebsiteStatusModel(string name, string url) : this(name, url, HttpStatusCode.Created) { }
    public WebsiteStatusModel(string name, string url, HttpStatusCode statusCode)
    {
        if(string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name));

        if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url));

        Name = name.ToLowerInvariant().Trim(); 

        Url = url.ToLowerInvariant().Trim();

        UpdateStatus(statusCode);
    }


    public void UpdateStatus(HttpStatusCode statusCode)
    {
        switch (statusCode)
        {
            case HttpStatusCode.OK:
                Status = "available";
                break;
            case HttpStatusCode.ServiceUnavailable:
                Status = "under-maintance";
                break;
            case HttpStatusCode.Created:
                Status = String.Empty;
                break;
            default:
                Status = "unavailable";
                break;
        }

        LastCheck = DateTime.UtcNow;
    }
}

implement the service :

public class WebsiteStatusService
{
    private static readonly HttpClient _client = new HttpClient(new HttpClientHandler
    {
        ServerCertificateCustomValidationCallback = delegate { return true; }
    });

    private static readonly ConcurrentDictionary<string, WebsiteStatusModel> _results = new ConcurrentDictionary<string, WebsiteStatusModel>(StringComparer.OrdinalIgnoreCase);

    private static readonly List<Task> _tasks = new List<Task>();

    static WebsiteStatusService() { }


    // Configure it at project startup
    public static void Configure()
    {
        // assuming you have them stored somewhere in the Database
        // or in the application, just pull'em here
        var sites = new List<WebsiteStatusModel>()
        {
            new WebsiteStatusModel("Site1", "Url")
        };

        // setup the tasks
        // the tasks are cached, for reuse.
        foreach (var site in sites)
        {
            _tasks.Add(CheckStatus(site));
        }

    }

    // keep it private
    private static async Task CheckStatus(WebsiteStatusModel website)
    {
        if(website == null) throw new ArgumentNullException(nameof(website));

        var response = await _client.GetAsync(website.Url);

        website.UpdateStatus(response.StatusCode);

        _results.AddOrUpdate(website.Name, website, (k, v) => website);
    }
    
    // call it within any controller
    public async Task<List<WebsiteStatusModel>> GetResults()
    {
        await Task.WhenAll(_tasks);

        var results = new List<WebsiteStatusModel>();

        foreach (var item in _results)
        {
            results.Add(item.Value);
        }

        return results;
    }


}

now we can do this inside the controller :

public class HomeController : Controller
{
    private static readonly WebsiteStatusService _websiteChecker = new WebsiteStatusService();

    public async Task<IActionResult> Index()
    {
        var results = await _websiteChecker.GetResults();

        //pass the results within a model
        // so you can iterate from the view.
        var model = new IndexViewModel();
        model.WebSitesStatus = results;
        return View(model);
    }
}

the rest is within your hands.

  • Related