Elke exchange publiceert rate limits, en elke exchange handhaaft ze. Binance beantwoordt een REST-aanroep die de limiet overschrijdt met een HTTP 429 en een Retry-After-header die je wordt geacht te respecteren, en sluit een WebSocket-verbinding die er meer dan 5 berichten per seconde naartoe stuurt. Twee klantmeldingen kwamen binnen een paar dagen na elkaar binnen, elk over een van beide helften, en samen legden ze drie problemen bloot die het waard waren om goed op te lossen.
Alle drie zijn nu opgelost. Eén ervan bleek veel meer dan alleen Binance te raken.
Een mislukt verzoek gooide zijn headers weg
Wanneer een verzoek via een van de kant-en-klare API-clients mislukte met een HTTP-foutstatus, wierp de component een exception op met de statuscode, de statustekst en de body. De responseheaders waren verdwenen. Niet verborgen, verdwenen. Het HTTP-object werd vernietigd terwijl de exception nog omhoog door de stack reisde, en de headers gingen mee.
Daardoor is een correcte Binance-backoff onmogelijk bovenop de component te bouwen. Retry-After vertelt je precies hoe lang je moet wachten, en het negeren ervan leidt van een 429 naar een HTTP 418 en een tijdelijke IP-ban. De rate-limit-tellers die Binance teruggeeft, zoals X-MBX-USED-WEIGHT-1M, waren net zo onbereikbaar, dus een applicatie kon haar eigen tempo evenmin bijstellen voordat ze tegen de muur liep.
De exception die nu wordt opgeworpen is EsgcHTTPAPIProtocolException, en die draagt de responseheaders met zich mee. Hij erft van de exception die voorheen werd opgeworpen, dus bestaande handlers blijven hem opvangen en er hoeft niets te veranderen, tenzij je de nieuwe informatie wilt gebruiken.
try
vResponse := oBinance.GetAggregateTrades('BTCUSDT');
except
on E: EsgcHTTPAPIProtocolException do
begin
if E.ErrorCode = 429 then
Sleep(E.RetryAfterMs);
vWeight := E.GetHeader('X-MBX-USED-WEIGHT-1M');
// E.ResponseHeaders holds the complete list
end;
end;
RetryAfterMs geeft de wachttijd in milliseconden terug en begrijpt beide vormen die de header mag aannemen, een gewoon aantal seconden en een HTTP-datum. Hij geeft -1 terug wanneer de header ontbreekt. De fix geldt voor alle acht request-methodes, niet alleen Get, en de parameter ResponseHeaders die Post en Query al accepteerden wordt nu gevuld wanneer een verzoek mislukt, precies op het moment dat je hem nodig hebt.
Goed om hierbij te weten: sinds 2026.7.0 kan de HTTP-client het wachten ook voor je doen.
oBinance.RetryOptions.Enabled := True;
oBinance.RetryOptions.MaxRetries := 3;
// HonorRetryAfter is already True by default
Eén frame in plaats van twaalf
Het Binance WebSocket-protocol accepteert een lijst met streams in één enkel SUBSCRIBE-frame. De component gebruikte dat niet. Elke Subscribe*-helper schreef onmiddellijk één frame met precies één stream, dus een gewone watchlist van zes symbolen verdeeld over twee kanalen verstuurde twaalf frames binnen een paar milliseconden. Binance sloot de verbinding, precies zoals gedocumenteerd.
Er zijn nu batchmethodes die de hele lijst als één frame versturen:
oBinance.SubscribeStreams(['btcusdt@aggTrade', 'btcusdt@depth',
'ethusdt@aggTrade', 'ethusdt@depth']);
Een frame blijft ver onder de limiet van 8 KB per verzoek, zelfs met honderden streams, dus in de praktijk is een watchlist altijd één frame. UnsubscribeStreams is het spiegelbeeld.
Het deel waar niemand omheen kon werken
Dit is het probleem dat het zwaarst woog, en geen van beide meldingen had het volledig door.
Elke exchange-API bewaart de kanalen waarop je je hebt geabonneerd, zodat die na een herverbinding hersteld kunnen worden. Die replay verstuurde één frame per stream, direct achter elkaar, zonder enige tempobeperking, over een verbinding die een paar milliseconden oud was. Bij een watchlist van enige realistische omvang werd de verse verbinding onmiddellijk gesloten. De WatchDog maakte vervolgens opnieuw verbinding, herhaalde dezelfde burst, en werd opnieuw afgesloten. Eén tijdelijke verbreking veranderde zo in een permanente herverbindingslus.
Omdat die burst binnen de component wordt gegenereerd, kon een applicatie hem niet vermijden. Zelf je frames schrijven in plaats van de helpers gebruiken hielp niet, want de replay is niets wat je zelf aanroept.
Een controle van de rest van de bibliotheek liet zien dat dezelfde replaylus was gekopieerd naar 17 exchange-API's: Binance, Bitfinex, Bitget, Bitmex, Bitstamp, Bybit, Cex, CexPlus, Coinbase, CryptoCom, Deribit, GateIO, Huobi, Kraken, Kucoin, MEXC en OKX. Elk daarvan tegen de limiet van zijn eigen exchange.
Er is nu een Throttle-optie op de WebSocket API-clients:
oBinance.Throttle.Enabled := True; // paces your own Subscribe calls
oBinance.Throttle.MaxMessages := 4; // default, leaves room for PING/PONG
oBinance.Throttle.IntervalMs := 1000; // default
De standaardinstelling verdient uitleg, want die is bewust asymmetrisch. Throttle.Enabled staat op False, dus de timing van de aanroepen die je applicatie doet verandert niet, tenzij je daarom vraagt. Maar Throttle.PaceResubscribe staat op True, dus de replay na een herverbinding wordt standaard getemporiseerd.
De redenering is eenvoudig. Je hebt zelf de controle over je subscribe-lus en kunt die batchen, dus daar is het tempo jouw beslissing. Over de replay heb je geen controle, dus die wordt voor je getemporiseerd. Daarmee is de herverbindingslus opgelost voor elke bestaande applicatie, zonder ook maar één codewijziging. Op Binance wordt de replay bovendien als gecombineerde frames verstuurd, dus een watchlist van twaalf streams wordt nu in één frame herhaald in plaats van in twaalf. Zet PaceResubscribe op False als je het vorige gedrag wilt.
Eén detail als je meerdere exchanges gebruikt: het standaardbudget van 4 berichten per seconde past bij Binance, met een gedocumenteerde limiet van 5. OKX documenteert er 3, dus de OKX-client staat standaard op 3. Controleer de limiet van je exchange voordat je MaxMessages verhoogt.
Upgraden
Drop-in. De nieuwe exception erft van de exception die voorheen werd opgeworpen, de batchmethodes zijn toevoegingen, en Throttle staat standaard uit voor alles behalve de replay die herverbindingen kapotmaakte. Download de nieuwste versie van de sgcWebSockets downloadpagina.
Beide meldingen kwamen van klanten die de tijd namen om de broncode te lezen, het probleem te reproduceren en het nauwkeurig te beschrijven. Zo is de derde en ergste bug gevonden. Als iets in de componenten zich gedraagt op een manier die je niet kunt verklaren, laat het ons weten.
Vragen, feedback of hulp bij de migratie? Neem contact op, je krijgt antwoord van de mensen die de code hebben geschreven.
