Wat je moet weten om ons probleem te snappen
We werken aan een nieuwe website met veel nadruk op afbeeldingen. We hebben al www.333travel.nl, dat als reisproduct natuurlijk al zwaar leunt op afbeeldingen. Onze nieuwe website laadt zelfs afbeeldingen met een nog hogere resolutie, omdat de nadruk op het luxe gevoel ligt. Deze site is gericht op de luxe reiziger: www.travelplatinum.nl. Het project dat onze afbeeldingen serveert, static.333travel.nl, draait al een paar jaar zonder gemelde problemen voor de 333travel-website.
Het probleem
Het probleem werd gemeld door onze thuiswerkers. Zij merkten dat bij het werken vanuit huis veel afbeeldingen op TravelPlatinum soms niet laadden (en dat een refresh het meestal oploste). Ze gaven ook aan dat alles gewoon werkte vanuit kantoor. Dat was voor ons het startpunt van een zoektocht naar de boosdoener.

We zagen in de browsers die we gebruiken (Edge en Chrome) meldingen in de console van de mislukte afbeeldingen: ERR_HTTP2_PROTOCOL_ERROR. Met die melding in ons achterhoofd begonnen we in de logs op de server te zoeken. We draaien Nginx als webserver.
We vonden snel een spoor dat bij elke afbeelding opdook die volgens ons niet goed geladen was. We konden het probleem zelf nog niet reproduceren.
0.0.0.0 - - [06/Nov/2023:15:45:02 +0100] “GET /web-images/10/2023/653775730fc66/Jazan—Al-qahar-mountains-1200.webp HTTP/2.0” 206 183064 “https://www.travelplatinum.nl/” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0”
Let op de 206 hier. Dat betekent partial content.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206
Dat was vreemd, want onze webserver ondersteunde helemaal geen partial loading van afbeeldingen. We sturen nooit headers met iets als een range, terwijl dat nodig is om partial content te kunnen leveren.
Onze poging om het probleem op te lossen
Dus daar doken we als eerste in. Ik kwam erachter dat Nginx standaard een header meestuurt die Content-Type: bytes teruggeeft. Ik snap niet waarom ik zou willen dat onze image server met een ok-antwoord reageert op een 206-request. We hebben dit uitgezet met de volgende setting in ons server block.
server {
server_name static.333travel.nl;
max_ranges 0;
}
We vroegen de mensen die het probleem hadden om het te melden als het weer gebeurde. Helaas was dat snel het geval. De foutmelding in onze logging was wel veranderd. Edge en Chrome bleven dezelfde nutteloze ERR_HTTP2_PROTOCOL_ERROR in de console tab spuwen. In de timeline zagen we ook dat deze afbeeldingen op een bepaald punt stopten met laden.

0.0.0.0 - - [08/Nov/2023:14:22:27 +0100] “GET /web-images/06/2020/5ed7ad48d3d79/mexico-yucatan-chichen-itza-ik-kil-cenote.jpeg HTTP/2.0” 200 0 “https://vue-boss.333office.nl/” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0”
We krijgen nu een header met een lengte van 0 bytes en een response met http 200 (ok). Het wordt hier echt vreemd. De header geeft bij inspectie in de browser wél het juiste aantal bytes terug in de response header. Waarom zegt de log van Nginx dan dat hij met een size van 0 antwoordt? Hier werd het raar en we wisten eigenlijk niet wat we verder moesten doen. We moesten het probleem eerst zelf kunnen reproduceren voordat we echt konden troubleshooten.
Ik probeerde een fout te simuleren met ab (apache benchmark). Ik begon voorzichtig met 500 requests en 50 gelijktijdige requests. Het probleem werd bij mij nooit getriggerd. Daarna ging ik in kleine stapjes omhoog tot 10.000 requests met 700 gelijktijdige requests, zonder het gemelde probleem ook maar één keer te zien. De serverload leek alles ook prima aan te kunnen. Het ging om een afbeelding van zo’n 500kb die ik daar opvroeg.

De feiten
- Een traag netwerkprofiel instellen in de network tab bij het inspecteren simuleerde het probleem voor ons niet.
- De fout werd nog steeds alleen gemeld door 2 gebruikers die vanuit huis werkten.
- Op kantoor hadden ze geen enkel probleem.
- De site 333travel.nl laden gaf helemaal geen probleem. Elke afbeelding werkte daar ook voor die 2 thuiswerkers.
- Testen via een 5g-verbinding op allerlei plekken die we konden bedenken simuleerde het probleem voor mij geen enkele keer.
- ab (apache benchmark) met 10.000 requests en 700 gelijktijdige requests triggerde het probleem nooit.
Er was nog één verschil, en dat was het domein dat de afbeeldingen serveerde. 333travel kreeg een Cross origin header van same_site terug, wat klopt, want het domein is hetzelfde. Dus ik voegde een header toe aan de static.
server {
server_name static.333travel.nl;
max_ranges 0;
add_header ‘Access-Control-Allow-Origin’ ‘*’;
}
Zo krijgt elke bron die afbeeldingen van onze static.333travel.nl wil gebruiken hetzelfde antwoord. Dit loste niets op van het gemelde probleem.
We hadden nog steeds moeite om het probleem zelf te simuleren. We waren er vrij zeker van dat het alleen getriggerd werd op een of andere slechte verbinding bij een bezoek aan TravelPlatinum. Een bezoek aan 333travel, dat de afbeeldingen van dezelfde static server serveert, liet het probleem dat we op TravelPlatinum ervaren nooit zien.
Een slechte wifi-verbinding simuleren
We wilden zo graag een slechte wifi-verbinding simuleren. Ik verplaatste mezelf naar een plek buiten ons kantoor waar de wifi slecht was, en eindelijk zag ik het probleem met eigen ogen. Eindelijk iets om mee te testen. Toch bleef het raar dat we een of andere trage/slechte wifi nodig hadden om dit te simuleren. Je zou zeggen dat je profiel op slow 3g zetten in de developer tools van de browser hetzelfde gedrag zou geven.
Nu konden we eindelijk gaan testen vanuit onze development-omgeving. We begonnen te vermoeden dat het project zelf iets raars deed, misschien iets met javascript dat de verbinding sloot of zo. Dus begonnen we een stripped-down versie van de TravelPlatinum-website te bouwen om zonder javascript te testen.
Het probleem dat afbeeldingen niet laadden was er nog steeds..
We besloten exact dezelfde html van 333travel.nl over te nemen om daarmee te testen en zo een gelijke omgeving te creëren. 333travel deed het immers bij elke refresh nog prima.
Het probleem dat afbeeldingen niet laadden was er nog steeds..
We wilden dit nog beter kunnen simuleren, want onze slechte wifi-plek was niet bepaald een bureau; we zaten op een trap het probleem te debuggen. Ik besloot een 2nd wifi-hotspot op te zetten in ons kantoor met een beperkte verbinding van 3mbit om te zien waar dat ons zou brengen. Dit was het! We konden nu bij elke refresh een heleboel kapotte afbeeldingen op TravelPlatinum simuleren! We komen in elk geval ergens. Bedenk wel dat de 333travel-website nog steeds elke afbeelding zonder problemen laadde.
We lieten de nginx-logs op dat moment voor wat ze waren, omdat we erachter waren gekomen dat we in die richting niets gingen oplossen. Nginx gaf nog steeds een ok terug op een header met 0 bytes. Als nginx dat ok vindt met de juiste headers op zijn plek, gaat dat bericht ons op dat moment niet helpen het probleem op te lossen.
Testen met Firefox
Op het moment dat ik deze wifi-hotspot aanmaakte, waren we net zover dat we afbeeldingen konden testen met alleen de html image tag en verder niets geladen in het project. Ook dat brak onze afbeeldingen. Ik stelde toen voor om Firefox er even bij te pakken, gewoon om te zien of die zich anders gedroeg, want ik vermoedde dat het browsergerelateerd was — hoe de browser reageert op wat er gebeurt. Of in elk geval zou hij misschien een andere foutmelding geven. Iets waar we onze zoektocht naar de oplossing op konden richten.
We zagen dat Firefox de afbeeldingen inderdaad regel voor regel laadde op de 3mbit-verbinding. Maar plotseling stopte het laden, met kapotte afbeeldingen als gevolg die voor zo’n 50% geladen waren of helemaal niet. Eindelijk zagen we eens iets anders! Edge/Chrome lieten nog steeds helemaal geen afbeelding zien en gaven me een nutteloze foutmelding. Nu drong eindelijk tot me door dat de static de afbeeldingen daadwerkelijk aan het versturen was en de browser ze ontving, maar dat de verbinding op de een of andere manier te vroeg gesloten werd.
De oplossing
En dat was het moment waarop ik het probleem eindelijk vond. Een header die ik al tijden geleden op globaal niveau in Nginx had gezet tijdens een performance-tuningsessie.
send_timeout 2;
Wat send timeout volgens mij doet, is de verbinding X seconden openhouden na het niet ontvangen van data. De standaard is 60 seconden en ik had hem op 2 seconden gezet zodat we geen 60 seconden lang open verbindingen zouden hebben die niets doen.
Waarom zou dat het probleem zijn, terwijl ik Firefox duidelijk de afbeelding regel voor regel zag laden? Er wordt dan toch constant data verstuurd? Waarom zou deze setting dan gerelateerd zijn aan het sluiten van de verbinding.
| Syntax: | send_timeout time; |
|---|---|
| Default: | send_timeout 60s; |
| Context: | http, server, location |
Sets a timeout for transmitting a response to the client. The timeout is set only between two successive write operations, not for the transmission of the whole response. If the client does not receive anything within this time, the connection is closed.
Nou, het blijkt dat dit op tragere verbindingen wél getriggerd wordt. Nginx interpreteert dit als een stalled request. En het sluit de hele verbinding naar in ons geval static.333travel.nl. De gedeeltelijk geladen afbeelding en elke afbeelding daarna die niet geladen werd, wordt niet opnieuw geladen omdat de verbinding met static gesloten is.
Waarom het simuleren van een trage verbinding in de developer tools dit probleem niet triggert, weet ik nog steeds niet zeker. Ik heb een paar theorieën: een slechte wifi-verbinding zorgt ook voor meer latency en verloren packets die opnieuw verstuurd moeten worden. Maar de echte reden ken ik niet. Ik weet niet hoe deze browsers een trage verbinding simuleren.
Deze send_timeout op 8 seconden zetten zoals hieronder loste ons probleem volledig op.
server {
server_name static.333travel.nl;
max_ranges 0;
add_header ‘Access-Control-Allow-Origin’ ‘*’;
send_timeout 8;
}
Conclusie
We hebben veel geleerd, en we hebben het probleem opgelost. Raar blijft wel dat dit al die tijd alleen op ons TravelPlatinum-project getriggerd werd. We balanceerden blijkbaar op de grens met de send_timeout-setting, omdat de afbeeldingen op TravelPlatinum voor meer load zorgen.
Nog iets belangrijks om te onthouden: een trage verbinding instellen in je developer tools is geen echte simulatie van een trage verbinding. Je kunt simuleren wat je wilt, maar je hebt echte praktijksituaties nodig om echt te meten of alles goed werkt.



