Een website heeft problemen met de performance, zet Fortiweb ervoor. Of duik toch wat dieper in de code.

Ingediend door Dirk Hornstra op 14-jul-2025 22:28

Een tijdje geleden kregen mijn collega Jeroen en ik een uitnodiging om een training te volgen bij CJ2 ( presentatie en doorlopen van hands-on modules in een soort online lab-omgeving ). Omdat er korte tijd daarvoor tussen TRES en CJ2 gesproken was over Fortiweb gingen we er vanuit dat dit daaraan gerelateerd was, maar dat was het niet (direct).

Dat gesprek over Fortiweb ontstond doordat er een web-applicatie was die zo nu en dan problemen had, doordat er flink wat requests op de site werden afgevuurd, werd de boel "unresponsive". Daar hebben we een automatisch "recycle-mechanisme" voor. Maar goed, dan moet de boel weer wat opstarten, als die "vloed van requests" binnen blijft komen, dan krijg je hetzelfde euvel. En als dat maar vaak genoeg achter elkaar gebeurt, dan zegt IIS na een aantal keer: nu stop ik gewoon de applicationpool. Einde oefening. Waarbij je dan zelf de boel handmatig weer op moest starten. Dan was de stroom van verzoeken ook gestopt en werkte de boel weer.

Bij dat overleg ben ik niet aanwezig geweest, toen ik dit later hoorde was ik op zich wel voor die Fortiweb, maar dat was voor mij niet de "oplossing". Als al die pagina's die nu opgevraagd werden allemaal "platte HTML-bestanden waren", zou het probleem dan ook optreden? Ik denk dat ik wel kan zeggen: nee, dan werkt het prima. Ooit volgens mij door mijn oud-collega Eric Limpens genoemd, zo is het maar net. Je code die acties uitvoert, database-connecties, mogelijk tig SQL-statements die allemaal over het netwerk verstuurd worden, als je al die dingen kunt uitsluiten dan is er vaak geen probleem.

Maar goed, dat heb je met een CMS, vaak zijn die pagina's niet (snel) statisch te maken en moet je werken met de code die je hebt. Vaak is het niet de code van het CMS, maar de "eigen code die gebouwd wordt" die de problemen veroorzaakt. 

 

Het team moet door met hun andere werk, ik ben in de code gedoken om te onderzoeken wat nu de "echte" oorzaak was. Het probleem met zulk soort storingen is dat je vaak niet weet waar je moet beginnen. Dus misschien heb je wat aan mijn uitleg hoe we uiteindelijk hier de oorzaak opgespoord hebben;

Ik ben eerst gaan kijken of ik APM kon toevoegen. Daarmee heb je extra monitoring op wat er binnen de web-applicatie gebeurt. Een tijd geleden ingesteld dat we dit ook binnen ons netwerk kunnen gebruiken, dus ik kon een lokale kopie van de omgeving draaien en daar deze tooling aan toevoegen. Helaas, daar was niets op te zien. Omdat de boel "hing" werd er ook geen data naar APM gestuurd. Dat kon ik dus afvinken.

Het "plat gooien" van mijn lokale site deed ik met een console-programma. Ik kon de sitemap opvragen, dan krijg je de URL's van de pagina's en binnen een loopje vroeg ik die allemaal achter elkaar op. Dat gaat zo snel dat het "bijna" gelijktijdig is. En omdat de boel meteen plat ging, wist ik dat mijn test representatief was, zou dat goed gaan, dan zou het in de live situatie ook goed moeten gaan.

Vervolgens ben ik in de "views" gedoken. Output (dus wat je op je scherm ziet) wordt in .CSHTML-bestanden opgebouwd. Daar moet (eigenlijk) niet teveel intelligentie inzitten. Die moet "de data binnen krijgen" en dit tonen. Vaak wordt dat niet gedaan, omdat er toch "even" wat extra's bij moet komen. Ik heb zo'n beetje alle code uitgeschakeld, zodat je alleen nog maar een witte pagina kreeg.

En zo heb ik stuk-voor-stuk de onderdelen weer actief gemaakt, tot ik weer op het punt kwam dat zaken weer traag / unresponsive werden. En zo kwamen we uit op het onderdeel wat je op veel pagina's bovenin ziet staan: klanten geven ons een xx.xx. Totaal aantal reacties: xx.
Die gegevens worden opgevraagd en in de cache gezet, daarna wordt het uit de cache gehaald. Dat HTTP-request duurt echter nogal lang. En als er dan heel veel requests op pagina's binnen komen waar deze component op staat, dan starten deze al die HTTP-requests op. 

Dit een write-once, read-many situatie. Een centrale functie zou die data moeten opvragen en in de cache zetten, de andere onderdelen moeten alleen die data lezen (en niet zelf proberen om het in de cache te zetten). Ik heb een voorzet gedaan en uitleg aan het team gegeven. Daar zijn nog wat optimalisaties doorgevoerd en is de fix uiteindelijk live gezet. We zien nog wel wat andere probleempjes, maar het volledig down gaan / unresponsive worden is hiermee wel opgelost.

Hierbij nog even de code die ik in mijn console-applicatie gebruikte om de boel te "bestoken":

 


// could be improved, but did what had to be done ;)

using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.XPath;
using System.Collections.Generic;


namespace Runner
{

    public class Program
    {
        static void Main(string[] args)
        {
            Downloader.FetchJobs();
            Console.WriteLine("Downloader will run!!");
            Console.ReadKey();

        }
    }


    public class Downloader
    {
        private static Stack<System.Threading.Thread> _threads = new Stack<System.Threading.Thread>();
        private static Stack<string> _stack = new Stack<string>();
        internal async static Task FetchJobs()
        {
            var uri = "https://localhost:44320/sitemap-jobs.xml?locale=nl-nl";
            using (var client = new HttpClient())
            {
                var sitemap = await client.GetAsync(uri);
                var document = System.Xml.Linq.XDocument.Parse(await sitemap.Content.ReadAsStringAsync());
                var urls = document.XPathSelectElements("//*").ToList();
                foreach (var url in urls)
                {
                    if (url.Name != "{http://www.sitemaps.org/schemas/sitemap/0.9}loc")
                    {
                        continue;
                    }
                    _stack.Push(url.Value);
                    var t = new System.Threading.Thread(async rec =>
                    {
                        if (_stack.TryPop(out var result))
                        {
                            await FetchJobPage(result);
                            Console.WriteLine("ok!");
                        }
                    });
                    _threads.Push(t);
                }
            }
            _threads.ToList().ForEach(rec => rec.Start());
            while (true)
            {
                Thread.Sleep(500);
                try
                {
                    _threads.First(rec => rec.ThreadState != ThreadState.Aborted).Abort();
                }
                catch (Exception x)
                {
                    /* skip */
                    Console.WriteLine(x.Message);
                    Console.WriteLine("=== stopped! ===");
                    return;
                }
            }
        }

        internal static async Task FetchJobPage(string url)
        {
            Console.WriteLine(url);
            using (var innerClient = new HttpClient())
            {
                var data = await innerClient.GetAsync(url);
                var html = await data.Content.ReadAsStringAsync();
                Console.WriteLine(html.Length > 100 ? html.Substring(0, 100) : html);
            }
        }
    }
}

 

Maar goed, die Fortiweb. Dat is een Web Application Firewall, die websites en API's beschermt tegen aanvallen. En daar is heel veel mee mogelijk. We doen natuurlijk al bepaalde zaken qua monitoring en beveiliging, maar dat is vaak "per site". Dus als je ziet dat een "script-kiddy" site A probeert binnen te komen, dan gooi je dat IP-adres in de blacklist van die website. Daarmee vangt IIS het verzoek af voordat het jouw web-applicatie bereikt. Maar ja, als die script-kiddy denkt, dan ga ik site B wel proberen, dan moet je daar hetzelfde trucje uithalen. Als je Fortiweb ervoor hebt en direct de eerste keer het IP-adres kunt blacklisten, dan komt ie op geen enkele server van jou meer binnen. Now we are talking!

Omdat het voor je web-applicatie zit kun je zaken laten loggen. Je kunt afdwingen dat je cookies "secure" zijn. Je kunt in je web-applicaties authenticatie inregelen, maar mocht dat niet werken/lukken, dan kun je dat ook via Fortiweb afhandelen. Je kunt in Fortiweb HTTPS afdwingen. Dat wordt nu vaak via redirects in de web-applicatie gedaan, maar mocht je bijvoorbeeld een legacy-applicatie van een andere partij overnemen en je kunt dat niet werkend krijgen, ook hier kan Fortiweb je dan redden. Met Machine Learning kun je Fortiweb trainen. Dus als een veld altijd een numerieke waarde van 5 cijfers heeft, maar iemand probeert daar nu heel wat anders in te voeren: detectie. Je kunt bepaalde botjes uitsluiten om jouw website te bezoeken. Ook de OWASP top 10 wordt meegenomen in het verdedigingsmechanisme. En "gelekte usernames/passwords" zijn ook beschikbaar en kunnen gedetecteerd worden als ze worden aangeboden. 

Ik ga niet over het budget (en ik weet zo ook niet wat dit kost), maar zoals uit dit korte overzicht blijkt (hoop ik), is dat dit een hele goede toevoeging is om te zorgen dat "ons netwerk veiliger wordt" en "onze websites minder bull-shit requests hoeven te verwerken", omdat Fortiweb dat al voor je afvangt.

 

Een goede presentatie door Cock Prins van Arrow.com, een uitstekende labs-training. Met die training heb je namelijk online omgevingen beschikbaar (zoals een Kali Linux omgeving om vulnerabilities te testen) en een online Fortiweb-omgeving, zoals je die ook "in het echt hebt". En natuurlijk leuk om de servers in de rekken te zien staan, het zijn er nogal wat. En het blaast daar flink. Hopelijk later ook nog eens een kijkje op "locatie Smilde"!