1- from .slackrequest import SlackRequest
2- from requests .packages .urllib3 .util .url import parse_url
31from .channel import Channel
2+ from .exceptions import SlackClientError
3+ from .slackrequest import SlackRequest
44from .user import User
55from .util import SearchList , SearchDict
6- from .exceptions import SlackClientError
7- from ssl import SSLError
86
9- from websocket import create_connection
107import json
8+ import logging
9+ import time
10+ import random
11+
12+ from requests .packages .urllib3 .util .url import parse_url
13+ from ssl import SSLError
14+ from websocket import create_connection
15+ from websocket ._exceptions import WebSocketConnectionClosedException
1116
1217
1318class Server (object ):
@@ -17,18 +22,27 @@ class Server(object):
1722
1823 """
1924 def __init__ (self , token , connect = True , proxies = None ):
25+ # Slack client configs
2026 self .token = token
27+ self .proxies = proxies
28+ self .api_requester = SlackRequest (proxies = proxies )
29+
30+ # Workspace metadata
2131 self .username = None
2232 self .domain = None
2333 self .login_data = None
24- self .websocket = None
2534 self .users = SearchDict ()
2635 self .channels = SearchList ()
27- self .connected = False
36+
37+ # RTM configs
38+ self .websocket = None
2839 self .ws_url = None
29- self .proxies = proxies
30- self .api_requester = SlackRequest (proxies = proxies )
40+ self .connected = False
41+ self .last_connected_at = 0
42+ self .auto_reconnect = False
43+ self .reconnect_attempt = 0
3144
45+ # Connect to RTM on load
3246 if connect :
3347 self .rtm_connect ()
3448
@@ -68,8 +82,51 @@ def append_user_agent(self, name, version):
6882 self .api_requester .append_user_agent (name , version )
6983
7084 def rtm_connect (self , reconnect = False , timeout = None , use_rtm_start = True , ** kwargs ):
85+ """
86+ Connects to the RTM API - https://api.slack.com/rtm
87+
88+ If `auto_reconnect` is set to `True` then the SlackClient is initialized, this method
89+ will be used to reconnect on websocket read failures, which indicate disconnection
90+
91+ :Args:
92+ reconnect (boolean) Whether this method is being called to reconnect to RTM
93+ timeout (int): Timeout for Web API calls
94+ use_rtm_start (boolean): `True` to connect using `rtm.start` or
95+ `False` to connect using`rtm.connect`
96+ https://api.slack.com/rtm#connecting_with_rtm.connect_vs._rtm.start
97+
98+ :Returns:
99+ None
100+
101+ """
102+
71103 # rtm.start returns user and channel info, rtm.connect does not.
72104 connect_method = "rtm.start" if use_rtm_start else "rtm.connect"
105+
106+ # If the `auto_reconnect` param was passed, set the server's `auto_reconnect` attr
107+ if kwargs and kwargs ["auto_reconnect" ] is True :
108+ self .auto_reconnect = True
109+
110+ # If this is an auto reconnect, rate limit reconnect attempts
111+ if self .auto_reconnect and reconnect :
112+ # Raise a SlackConnectionError after 5 retries within 3 minutes
113+ recon_attempt = self .reconnect_attempt
114+ if recon_attempt == 5 :
115+ logging .error ("RTM connection failed, reached max reconnects." )
116+ raise SlackConnectionError ("RTM connection failed, reached max reconnects." )
117+ # Wait to reconnect if the last reconnect was more than 3 minutes ago
118+ if (time .time () - self .last_connected_at ) < 180 :
119+ if recon_attempt > 0 :
120+ # Back off after the the first attempt
121+ backoff_offset_multiplier = random .randint (1 , 4 )
122+ retry_timeout = (backoff_offset_multiplier * recon_attempt * recon_attempt )
123+ logging .debug ("Reconnecting in %d seconds" , retry_timeout )
124+
125+ time .sleep (retry_timeout )
126+ self .reconnect_attempt += 1
127+ else :
128+ self .reconnect_attempt = 0
129+
73130 reply = self .api_requester .do (self .token , connect_method , timeout = timeout , post_data = kwargs )
74131
75132 if reply .status_code != 200 :
@@ -111,8 +168,12 @@ def connect_slack_websocket(self, ws_url):
111168 http_proxy_host = proxy_host ,
112169 http_proxy_port = proxy_port ,
113170 http_proxy_auth = proxy_auth )
171+ self .connected = True
172+ self .last_connected_at = time .time ()
173+ logging .debug ("RTM connected" )
114174 self .websocket .sock .setblocking (0 )
115175 except Exception as e :
176+ self .connected = False
116177 raise SlackConnectionError (message = str (e ))
117178
118179 def parse_channel_data (self , channel_data ):
@@ -202,6 +263,13 @@ def websocket_safe_read(self):
202263 # SSLWantReadError
203264 return ''
204265 raise
266+ except WebSocketConnectionClosedException as e :
267+ logging .debug ("RTM disconnected" )
268+ self .connected = False
269+ if self .auto_reconnect :
270+ self .rtm_connect (reconnect = True )
271+ else :
272+ raise SlackConnectionError ("Unable to send due to closed RTM websocket" )
205273 return data .rstrip ()
206274
207275 def attach_user (self , name , user_id , real_name , tz , email ):
0 commit comments