Nuestra API de WebSocket está destinada a proporcionar una fuente persistente de nuestros datos de mercado (tickers, libros de órdenes, tiempo y ventas, etc), pero a veces Cloudflare termina inesperadamente las conexiones WebSocket.
Las desconexiones iniciadas por Cloudflare generalmente producen errores de red, como el siguiente error 1006:
- •Error al leer desde websocket: cierre 1006 (cierre anormal): EOF inesperado
o lo siguiente, donde Python lanza una excepción al intentar leer un nuevo mensaje de datos de mercado desde una conexión ya cerrada:
- •websocket._exceptions.WebSocketConnectionClosedException: socket ya está cerrado
Si bien no hay una solución que evite este tipo de desconexiones inesperadas, existen algunas alternativas muy efectivas. En caso de que experimente desconexiones frecuentes de WebSocket, le recomendamos implementar una o más de los siguientes opciones:
Dirección IP alternativa
Las precauciones de seguridad de Cloudflare a menudo se aplican a direcciones IP específicas, grupos de direcciones IP similares (como un bloque de clase C), o ciertas ubicaciones geográficas, por lo que cambiar su dirección IP a veces es una solución simple pero efectiva.
Por ejemplo, alojar su software de API en una región diferente de Amazon Web Service (AWS), o usar una VPN en una ubicación geográfica diferente, cambiaría significativamente la dirección IP local y podría resolver el problema de desconexión inesperada.
Lógica de reconexión
El uso de una única conexión WebSocket que se monitorea en busca de desconexiones involuntarias y se vuelve a conectar y a suscribir, necesariamente, es otra solución simple pero efectiva. El siguiente es un ejemplo de código básico de Python que muestra cómo volver a conectar y a suscribir una conexión WebSocket cada vez que se produce una excepción de WebSocketConnectionClosed:
#!/usr/bin/env python3import sysfrom websocket import create_connectionws = create_connection('wss://ws.kraken.com/')print(ws.recv())ws.send('{"event":"subscribe", "subscription":{"name":"ticker"}, "pair":["XBT/USD"]}')print(ws.recv())for count in range(100):if count == 30 or count == 60:ws.close()try:print(ws.recv())except:ws = create_connection('wss://ws.kraken.com/')print(ws.recv())ws.send('{"event":"subscribe", "subscription":{"name":"ticker"}, "pair":["XBT/USD"]}')print(ws.recv())ws.close()sys.exit(0)
Como se muestra en el siguiente resultado de ejemplo, la conexión y suscripción de WebSocket se renuevan cada vez que la conexión termina inesperadamente (a 30 y 60 iteraciones del bucle):
{"connectionID":8986307948074364287,"event":"systemStatus","status":"online","version":"1.0.1"}{"channelID":274,"channelName":"ticker","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"ticker"}}[274,{"a":["9766.00000",16,"16.15314657"],"b":["9765.90000",16,"16.06793017"],"c":["9766.00000","0.15407543"],"v":["934.89117067","6431.35804307"],"p":["9793.82728","9736.03263"],"t":[3442,18738],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.80000"]},"ticker","XBT/USD"]{"event":"heartbeat"}...[274,{"a":["9766.00000",15,"15.80052495"],"b":["9765.90000",15,"15.09399036"],"c":["9766.00000","0.18958527"],"v":["935.24379229","6430.73154248"],"p":["9793.81679","9736.04572"],"t":[3444,18738],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"][274,{"a":["9766.00000",15,"15.59644495"],"b":["9765.90000",15,"15.09399036"],"c":["9766.00000","0.20408000"],"v":["935.44787229","6430.93562248"],"p":["9793.81072","9736.04667"],"t":[3445,18739],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"]{"event":"heartbeat"}[274,{"a":["9766.00000",11,"11.59644495"],"b":["9765.90000",20,"20.24943617"],"c":["9766.00000","4.00000000"],"v":["939.44787229","6434.93562248"],"p":["9793.69231","9736.06529"],"t":[3446,18740],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"]{"connectionID":14767302623274336124,"event":"systemStatus","status":"online","version":"1.0.1"}{"channelID":274,"channelName":"ticker","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"ticker"}}[274,{"a":["9766.00000",11,"11.59644495"],"b":["9765.90000",20,"20.24943617"],"c":["9766.00000","4.00000000"],"v":["939.44787229","6434.93562248"],"p":["9793.69231","9736.06529"],"t":[3446,18740],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"][274,{"a":["9766.00000",8,"8.49644495"],"b":["9765.90000",21,"21.50083276"],"c":["9766.00000","1.50355505"],"v":["942.54787229","6438.03562248"],"p":["9793.60123","9736.07971"],"t":[3448,18742],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"]{"event":"heartbeat"}{"event":"heartbeat"}[274,{"a":["9766.00000",4,"4.19644495"],"b":["9765.90000",22,"22.67116661"],"c":["9766.00000","4.30000000"],"v":["946.84787229","6442.33562248"],"p":["9793.47588","9736.09968"],"t":[3449,18743],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"]...{"event":"heartbeat"}{"connectionID":11355575731659611126,"event":"systemStatus","status":"online","version":"1.0.1"}{"channelID":274,"channelName":"ticker","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"ticker"}}[274,{"a":["9766.00000",4,"4.00000071"],"b":["9765.90000",21,"21.97845481"],"c":["9766.00000","0.19644424"],"v":["947.04431653","6442.53206672"],"p":["9793.47018","9736.10059"],"t":[3450,18744],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"][274,{"a":["9766.00000",3,"3.85443405"],"b":["9765.90000",19,"19.69673762"],"c":["9766.00000","0.14556666"],"v":["947.18988319","6442.67763338"],"p":["9793.46596","9736.10127"],"t":[3451,18745],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"]{"event":"heartbeat"}{"event":"heartbeat"}[274,{"a":["9766.00000",3,"3.51109438"],"b":["9765.90000",19,"19.69673762"],"c":["9766.00000","0.16647855"],"v":["947.53322286","6443.02097305"],"p":["9793.45601","9736.10286"],"t":[3453,18747],"l":["9731.30000","9465.00000"],"h":["9845.00000","9884.70000"],"o":["9792.10000","9660.70000"]},"ticker","XBT/USD"]...
Conexiones redundantes
El uso de múltiples (dos o más) conexiones redundantes de WebSocket suele ser la solución más efectiva para las desconexiones inesperadas de WebSocket, ya que permite que las fuentes de datos del mercado continúen sin interrupciones, independientemente de la frecuencia con la que se terminan las conexiones subyacentes.
El siguiente ejemplo de código básico de Python muestra cómo implementar múltiples conexiones WebSocket con conmutación por error automática:
#!/usr/bin/env python3import sysfrom websocket import create_connectionws = [ None, None ]data = [ "", "" ]ws[0] = create_connection("wss://ws.kraken.com/")print("WebSocket (Primary): %s" % ws[0].recv())ws[1] = create_connection("wss://ws.kraken.com/")print("WebSocket (Backup): %s" % ws[1].recv())ws[0].send('{"event":"subscribe", "subscription":{"name":"spread"}, "pair":["XBT/USD"]}')print("WebSocket (Primary): %s" % ws[0].recv())ws[1].send('{"event":"subscribe", "subscription":{"name":"spread"}, "pair":["XBT/USD"]}')print("WebSocket (Backup): %s" % ws[1].recv())source = 0for count in range(100):try:try:data[0] = ws[0].recv()except Exception:source = 1try:data[1] = ws[1].recv()except Exception:source = 0except KeyboardInterrupt:ws[0].close()print("WebSocket (%(source)s): %(data)s" % {"source":"Primary" if source == 0 else "Backup", "data":data[source]})ws[source].close()sys.exit(1)
El código anterior crea dos conexiones WebSocket y las suscribe a ambas a la misma fuente de datos de mercado. La conexión primaria se utiliza para dar salida a los datos del mercado, hasta que se genera una interrupción del teclado (a través de Ctrl + C, por ejemplo), que cierra la conexión primaria. Las lecturas posteriores a la conexión primaria generan una excepción, que se detecta y se utiliza para cambiar la fuente de datos del mercado a la conexión de respaldo, que luego se utiliza para dar salida a los datos del mercado.
El siguiente resultado de ejemplo muestra cómo la fuente de datos del mercado cambia de la conexión primaria a la conexión de respaldo sin interrumpir la fuente de datos del mercado:
WebSocket (Primario):{"connectionID":6894434610526943167,"event":"systemStatus","status":"online","version":"1.0.1"}WebSocket (Backup):{"connectionID":11520162761468018366,"event":"systemStatus","status":"online","version":"1.0.1"}WebSocket (Primary):{"channelID":275,"channelName":"spread","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"spread"}}WebSocket (Backup):{"channelID":275,"channelName":"spread","event":"subscriptionStatus","pair":"XBT/USD","status":"subscribed","subscription":{"name":"spread"}}WebSocket (Primary): [275,["9674.90000","9675.00000","1591440118.120752","10.88476562","3.47935600"],"spread","XBT/USD"]WebSocket (Primary): {"event":"heartbeat"}WebSocket (Primary): {"event":"heartbeat"}WebSocket (Primary): [275,["9674.90000","9675.00000","1591440127.108830","10.98812533","3.47935600"],"spread","XBT/USD"]WebSocket (Primary): {"event":"heartbeat"}WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.406073","1.87412533","7.47935600"],"spread","XBT/USD"]WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.505372","0.77412533","7.47935600"],"spread","XBT/USD"]WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.572658","0.10335971","7.47935600"],"spread","XBT/USD"]WebSocket (Primary): [275,["9674.90000","9675.00000","1591440129.572658","0.10335971","7.47935600"],"spread","XBT/USD"]^CWebSocket (Backup): [275,["9674.90000","9675.00000","1591440130.156840","0.10335971","8.27935600"],"spread","XBT/USD"]WebSocket (Backup): [275,["9674.90000","9675.00000","1591440130.195899","0.10335971","8.47435600"],"spread","XBT/USD"]WebSocket (Backup): [275,["9673.80000","9675.00000","1591440130.459388","0.10335971","8.47435600"],"spread","XBT/USD"]WebSocket (Backup): [275,["9672.60000","9675.00000","1591440130.195899","0.00516924","8.47435600"],"spread","XBT/USD"]WebSocket (Backup): [275,["9673.80000","9675.00000","1591440130.764856","0.00400000","8.47435600"],"spread","XBT/USD"]WebSocket (Backup): [275,["9673.80000","9674.90000","1591440130.780514","0.00400000","0.25000000"],"spread","XBT/USD"]WebSocket (Backup): {"event":"heartbeat"}WebSocket (Backup): [275,["9673.80000","9674.90000","1591440132.433940","4.00400000","0.25000000"],"spread","XBT/USD"]WebSocket (Backup): [275,["9673.80000","9674.90000","1591440132.519509","4.00000000","0.25000000"],"spread","XBT/USD"]WebSocket (Backup): [275,["9673.90000","9674.90000","1591440132.527887","0.10336612","0.25000000"],"spread","XBT/USD"]WebSocket (Backup): [275,["9673.90000","9675.00000","1591440132.527887","0.10336612","4.27935600"],"spread","XBT/USD"]
Tenga en cuenta que las conexiones WebSocket redundantes es la solución más compleja de las tres, aunque es la más óptima en términos de seguridad.