tpodlaski / copra

Ansyncronous REST and WebSocket Python clients for the Coinbase Pro virtual currency trading platform.
MIT License
49 stars 15 forks source link

Websocket Connections close after 1 minute for user channel #6

Closed pavs94 closed 5 years ago

pavs94 commented 5 years ago

Description

When connecting to the user channel, there is no data instantly, in that case, the websocket connection shuts down and reconnects after 1 minute and just continuously reconnects every 1 minute. Is there a keep-alive that can be implemented or heartbeat to ensure that the connection is open?

tpodlaski commented 5 years ago

This can definitely be done, and I will look into adding into the client initialization, but in the meantime, I believe the following should work. I have not tested it yet, so I could be wrong.

copra.websocket.client.Client extends autobahn.asyncio.websocket.WebSocketClientFactory. The autobahn base class allows for really fine grained control of the protocol that manages the connection. You should be able to use setProtocolOptions() to configure the ping. You would have to do this before the client is added to the event loop. As it stands the protocol is a singleton created when Client.add_as_task_to_loop() is run, and setProtocolOptions() must be run before the protocol is initialized. To ensure this, the client would need to be initialized with auto_connect = False.

The process would look something like:

import asyncio
from copra.websocket import Channel, Client

KEY = YOUR_API_KEY
SECRET = YOUR_API_SECRET
PASSPHRASE = YOUR_API_PASSPHRASE

loop = asyncio.get_event_loop()

channel = Channel('user', 'BTC-USD')

client = Client(loop, channel, auth=True, key=KEY, secret=SECRET, passphrase=PASSPHRASE, auto_connect=False)

client.setProtocolOptions(autoPingInterval=30)

client.add_as_task_to_loop()

try:
    loop.run_forever()
except KeyboardInterrupt:
    loop.run_until_complete(client.close())
    loop.close()

Again, I haven't yet tried this, but it should work. If you do try it, I would greatly appreciate your feedback. I will move forward with adding the option of configuring the ping during client initialization, but hopefully this will be a bandaid until I get it integrated.

tpodlaski commented 5 years ago

Update: I have tested the above solution and it seems to work.

An alternative to using a ping would be to subscribe to the heartbeat channel. It may be simpler to set it up this way, and it will keep your connection open, but it is also pushing data to you once per second that you may not need.

pavs94 commented 5 years ago

I tried something different which worked too. Just a ping to keep connection alive.

Added this to the ClientProtocol class:

def schedule_keepalive(self):
        loop=asyncio.get_event_loop()
        loop.call_later(30,self.sendPing)

def sendPing(self, payload = None):
        if self.state != WebSocketClientProtocol.STATE_OPEN:
            return
        if payload:
            l = len(payload)
            if l > 125:
                raise Exception("invalid payload for PING (payload length must be <= 125, was %d)" % l)
            self.sendFrame(opcode = 9, payload = payload)
            self.schedule_keepalive()

Changed the onOpen Function to this.

def onOpen(self):
        print("Websocket Open")
        self.schedule_keepalive()
        self.factory.on_open()
tpodlaski commented 5 years ago

Thank you for sharing your workaround. Any of these three methods should work to keep the connection alive. I am going to continue forward with implementing ping configuration in the client initialization since it makes the most sense to use the underlying functionality provided by autobahn. Since there already are multiple, if not convenient, ways to skin this cat, I am going to close this issue.