signalrcore 0.9.5__py3-none-any.whl → 0.9.6a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of signalrcore might be problematic. Click here for more details.
- signalrcore/helpers.py +61 -0
- signalrcore/hub/base_hub_connection.py +70 -56
- signalrcore/hub/errors.py +2 -0
- signalrcore/hub/handlers.py +2 -2
- signalrcore/hub_connection_builder.py +27 -5
- signalrcore/protocol/base_hub_protocol.py +10 -6
- signalrcore/protocol/messagepack_protocol.py +26 -9
- signalrcore/transport/base_transport.py +11 -9
- signalrcore/transport/websockets/websocket_client.py +263 -0
- signalrcore/transport/websockets/websocket_transport.py +49 -58
- {signalrcore-0.9.5.dist-info → signalrcore-0.9.6a2.dist-info}/METADATA +16 -22
- {signalrcore-0.9.5.dist-info → signalrcore-0.9.6a2.dist-info}/RECORD +25 -23
- {signalrcore-0.9.5.dist-info → signalrcore-0.9.6a2.dist-info}/WHEEL +1 -1
- test/base_test_case.py +9 -4
- test/client_streaming_test.py +10 -14
- test/configuration_test.py +28 -26
- test/open_close_test.py +38 -29
- test/reconnection_test.py +46 -32
- test/send_auth_errors_test.py +18 -18
- test/send_auth_test.py +21 -18
- test/send_test.py +61 -36
- test/streaming_test.py +17 -18
- test/subscribe_test.py +18 -0
- {signalrcore-0.9.5.dist-info → signalrcore-0.9.6a2.dist-info}/LICENSE +0 -0
- {signalrcore-0.9.5.dist-info → signalrcore-0.9.6a2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import ssl
|
|
3
|
+
import base64
|
|
4
|
+
import threading
|
|
5
|
+
import os
|
|
6
|
+
import struct
|
|
7
|
+
import urllib.parse as parse
|
|
8
|
+
from typing import Optional, Callable, Union
|
|
9
|
+
from signalrcore.helpers import Helpers
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
THREAD_NAME = "Signalrcore websocket client"
|
|
13
|
+
WINDOW_SIZE = 1024
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NoHeaderException(Exception):
|
|
17
|
+
"""Error reading messages from socket, empty content
|
|
18
|
+
"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SocketHandshakeError(Exception):
|
|
23
|
+
"""Error during connection
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
msg (str): message
|
|
27
|
+
"""
|
|
28
|
+
def __init__(self, msg: str):
|
|
29
|
+
super().__init__(msg)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WebSocketClient(object):
|
|
33
|
+
"""Minimal websocket client
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
url (str): Websocket url
|
|
37
|
+
headers (Optional[dict]): additional headers
|
|
38
|
+
verify_ssl (bool): Verify SSL y/n
|
|
39
|
+
on_message (callable): on message callback
|
|
40
|
+
on_error (callable): on error callback
|
|
41
|
+
on_open (callable): on open callback
|
|
42
|
+
on_close (callable): on close callback
|
|
43
|
+
"""
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
url: str,
|
|
47
|
+
is_binary: bool = False,
|
|
48
|
+
headers: Optional[dict] = None,
|
|
49
|
+
proxies: dict = {},
|
|
50
|
+
verify_ssl: bool = True,
|
|
51
|
+
enable_trace: bool = False,
|
|
52
|
+
on_message: Callable = None,
|
|
53
|
+
on_open: Callable = None,
|
|
54
|
+
on_error: Callable = None,
|
|
55
|
+
on_close: Callable = None):
|
|
56
|
+
self.is_trace_enabled = enable_trace
|
|
57
|
+
self.proxies = proxies
|
|
58
|
+
self.url = url
|
|
59
|
+
self.is_binary = is_binary
|
|
60
|
+
self.headers = headers or {}
|
|
61
|
+
self.verify_ssl = verify_ssl
|
|
62
|
+
self.sock = None
|
|
63
|
+
self.ssl_context = ssl.create_default_context()\
|
|
64
|
+
if verify_ssl else\
|
|
65
|
+
ssl._create_unverified_context()
|
|
66
|
+
self.logger = Helpers.get_logger()
|
|
67
|
+
self.recv_thread = None
|
|
68
|
+
self.on_message = on_message
|
|
69
|
+
self.on_close = on_close
|
|
70
|
+
self.on_error = on_error
|
|
71
|
+
self.on_open = on_open
|
|
72
|
+
self.running = False
|
|
73
|
+
self.is_closing = False
|
|
74
|
+
|
|
75
|
+
def connect(self):
|
|
76
|
+
# ToDo URL PARSE
|
|
77
|
+
parsed_url = parse.urlparse(self.url)
|
|
78
|
+
is_secure_connection = parsed_url.scheme == "wss"\
|
|
79
|
+
or parsed_url.scheme == "https"
|
|
80
|
+
|
|
81
|
+
proxy_info = None
|
|
82
|
+
if is_secure_connection\
|
|
83
|
+
and self.proxies.get("https", None) is not None:
|
|
84
|
+
proxy_info = parse.urlparse(self.proxies.get("https"))
|
|
85
|
+
|
|
86
|
+
if not is_secure_connection\
|
|
87
|
+
and self.proxies.get("http", None) is not None:
|
|
88
|
+
proxy_info = parse.urlparse(self.proxies.get("http"))
|
|
89
|
+
|
|
90
|
+
host, port = parsed_url.hostname, parsed_url.port
|
|
91
|
+
|
|
92
|
+
if proxy_info is not None:
|
|
93
|
+
host = proxy_info.hostname,
|
|
94
|
+
port = proxy_info.port
|
|
95
|
+
|
|
96
|
+
raw_sock = socket.create_connection((host, port))
|
|
97
|
+
|
|
98
|
+
if is_secure_connection:
|
|
99
|
+
raw_sock = self.ssl_context.wrap_socket(
|
|
100
|
+
raw_sock,
|
|
101
|
+
server_hostname=host)
|
|
102
|
+
|
|
103
|
+
self.sock = raw_sock
|
|
104
|
+
|
|
105
|
+
# Perform the WebSocket handshake
|
|
106
|
+
key = base64.b64encode(os.urandom(16)).decode("utf-8")
|
|
107
|
+
request_headers = [
|
|
108
|
+
f"GET {parsed_url.path} HTTP/1.1",
|
|
109
|
+
f"Host: {parsed_url.hostname}",
|
|
110
|
+
"Upgrade: websocket",
|
|
111
|
+
"Connection: Upgrade",
|
|
112
|
+
f"Sec-WebSocket-Key: {key}",
|
|
113
|
+
"Sec-WebSocket-Version: 13"
|
|
114
|
+
]
|
|
115
|
+
for k, v in self.headers.items():
|
|
116
|
+
request_headers.append(f"{k}: {v}")
|
|
117
|
+
|
|
118
|
+
request = "\r\n".join(request_headers) + "\r\n\r\n"
|
|
119
|
+
req = request.encode("utf-8")
|
|
120
|
+
|
|
121
|
+
if self.is_trace_enabled:
|
|
122
|
+
self.logger.debug(req)
|
|
123
|
+
|
|
124
|
+
self.sock.sendall(req)
|
|
125
|
+
|
|
126
|
+
# Read handshake response
|
|
127
|
+
response = b""
|
|
128
|
+
while b"\r\n\r\n" not in response:
|
|
129
|
+
chunk = self.sock.recv(WINDOW_SIZE)
|
|
130
|
+
if self.is_trace_enabled:
|
|
131
|
+
self.logger.debug(chunk)
|
|
132
|
+
|
|
133
|
+
if not chunk:
|
|
134
|
+
raise SocketHandshakeError(
|
|
135
|
+
"Connection closed during handshake")
|
|
136
|
+
|
|
137
|
+
response += chunk
|
|
138
|
+
|
|
139
|
+
if b"101" not in response:
|
|
140
|
+
raise SocketHandshakeError(
|
|
141
|
+
f"Handshake failed: {response.decode()}")
|
|
142
|
+
|
|
143
|
+
self.running = True
|
|
144
|
+
self.recv_thread = threading.Thread(
|
|
145
|
+
target=self._recv_loop,
|
|
146
|
+
name=THREAD_NAME)
|
|
147
|
+
self.recv_thread.daemon = True
|
|
148
|
+
self.recv_thread.start()
|
|
149
|
+
|
|
150
|
+
def _recv_loop(self):
|
|
151
|
+
self.on_open()
|
|
152
|
+
try:
|
|
153
|
+
while self.running:
|
|
154
|
+
message = self._recv_frame()
|
|
155
|
+
|
|
156
|
+
if self.on_message:
|
|
157
|
+
self.on_message(self, message)
|
|
158
|
+
except Exception as e:
|
|
159
|
+
self.running = False
|
|
160
|
+
|
|
161
|
+
# is closing and no header indicates
|
|
162
|
+
# that socket has not received anything
|
|
163
|
+
has_no_content = type(e) is NoHeaderException
|
|
164
|
+
|
|
165
|
+
# is closing and errno indicates
|
|
166
|
+
# that file descriptor points to a closed file
|
|
167
|
+
has_closed_fd = type(e) is OSError and e.errno == 9
|
|
168
|
+
|
|
169
|
+
if (has_no_content or has_closed_fd) and self.is_closing:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
if self.logger:
|
|
173
|
+
self.logger.error(f"Receive error: {e}")
|
|
174
|
+
self.on_error(e)
|
|
175
|
+
|
|
176
|
+
def _recv_frame(self):
|
|
177
|
+
# Very basic, single-frame, unfragmented
|
|
178
|
+
try:
|
|
179
|
+
header = self.sock.recv(2)
|
|
180
|
+
except ssl.SSLError as ex:
|
|
181
|
+
self.logger.error(ex)
|
|
182
|
+
header = None
|
|
183
|
+
|
|
184
|
+
if header is None or len(header) < 2:
|
|
185
|
+
raise NoHeaderException()
|
|
186
|
+
|
|
187
|
+
fin_opcode = header[0]
|
|
188
|
+
masked_len = header[1]
|
|
189
|
+
|
|
190
|
+
if self.logger:
|
|
191
|
+
self.logger.debug(
|
|
192
|
+
f"fin opcode: {fin_opcode}, masked len: {masked_len}")
|
|
193
|
+
|
|
194
|
+
payload_len = masked_len & 0x7F
|
|
195
|
+
if payload_len == 126:
|
|
196
|
+
payload_len = struct.unpack(">H", self.sock.recv(2))[0]
|
|
197
|
+
elif payload_len == 127:
|
|
198
|
+
payload_len = struct.unpack(">Q", self.sock.recv(8))[0]
|
|
199
|
+
|
|
200
|
+
if masked_len & 0x80:
|
|
201
|
+
masking_key = self.sock.recv(4)
|
|
202
|
+
masked_data = self.sock.recv(payload_len)
|
|
203
|
+
data = bytes(
|
|
204
|
+
b ^ masking_key[i % 4]
|
|
205
|
+
for i, b in enumerate(masked_data))
|
|
206
|
+
else:
|
|
207
|
+
data = self.sock.recv(payload_len)
|
|
208
|
+
|
|
209
|
+
if self.is_trace_enabled:
|
|
210
|
+
self.logger.debug(data)
|
|
211
|
+
|
|
212
|
+
if self.is_binary:
|
|
213
|
+
return data
|
|
214
|
+
|
|
215
|
+
return data.decode("utf-8")
|
|
216
|
+
|
|
217
|
+
def send(
|
|
218
|
+
self,
|
|
219
|
+
message: Union[str, bytes],
|
|
220
|
+
opcode=0x1):
|
|
221
|
+
# Text or binary opcode (no fragmentation)
|
|
222
|
+
payload = message.encode("utf-8")\
|
|
223
|
+
if type(message) is str else message
|
|
224
|
+
header = bytes([0x80 | opcode])
|
|
225
|
+
length = len(payload)
|
|
226
|
+
if length <= 125:
|
|
227
|
+
header += bytes([0x80 | length])
|
|
228
|
+
elif length <= 65535:
|
|
229
|
+
header += bytes([0x80 | 126]) + struct.pack(">H", length)
|
|
230
|
+
else:
|
|
231
|
+
header += bytes([0x80 | 127]) + struct.pack(">Q", length)
|
|
232
|
+
|
|
233
|
+
# Mask the payload
|
|
234
|
+
masking_key = os.urandom(4)
|
|
235
|
+
masked_payload = bytes(
|
|
236
|
+
b ^ masking_key[i % 4]
|
|
237
|
+
for i, b in enumerate(payload))
|
|
238
|
+
frame = header + masking_key + masked_payload
|
|
239
|
+
self.sock.sendall(frame)
|
|
240
|
+
|
|
241
|
+
def dispose(self):
|
|
242
|
+
if self.sock:
|
|
243
|
+
self.sock.close()
|
|
244
|
+
is_same_thread = threading.current_thread().name == THREAD_NAME
|
|
245
|
+
if self.recv_thread and not is_same_thread:
|
|
246
|
+
self.recv_thread.join()
|
|
247
|
+
self.recv_thread = None
|
|
248
|
+
|
|
249
|
+
def close(self):
|
|
250
|
+
self.logger.debug("Start closing socket")
|
|
251
|
+
try:
|
|
252
|
+
self.is_closing = True
|
|
253
|
+
self.running = False
|
|
254
|
+
|
|
255
|
+
self.dispose()
|
|
256
|
+
|
|
257
|
+
self.on_close()
|
|
258
|
+
self.logger.debug("socket closed successfully")
|
|
259
|
+
except Exception as ex:
|
|
260
|
+
self.logger.error(ex)
|
|
261
|
+
self.on_error(ex)
|
|
262
|
+
finally:
|
|
263
|
+
self.is_closing = False
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import websocket
|
|
2
|
-
import threading
|
|
3
|
-
import requests
|
|
4
1
|
import traceback
|
|
5
2
|
import time
|
|
6
|
-
import ssl
|
|
7
3
|
from .reconnection import ConnectionStateChecker
|
|
8
4
|
from .connection import ConnectionState
|
|
9
5
|
from ...messages.ping_message import PingMessage
|
|
10
|
-
from ...hub.errors import HubError,
|
|
6
|
+
from ...hub.errors import HubError, UnAuthorizedHubError
|
|
11
7
|
from ...protocol.messagepack_protocol import MessagePackHubProtocol
|
|
12
|
-
from ...protocol.json_hub_protocol import JsonHubProtocol
|
|
13
8
|
from ..base_transport import BaseTransport
|
|
14
|
-
from ...helpers import Helpers
|
|
9
|
+
from ...helpers import Helpers, RequestHelpers
|
|
10
|
+
from .websocket_client import WebSocketClient
|
|
11
|
+
|
|
15
12
|
|
|
16
13
|
class WebsocketTransport(BaseTransport):
|
|
17
|
-
def __init__(
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
18
16
|
url="",
|
|
19
17
|
headers=None,
|
|
20
18
|
keep_alive_interval=15,
|
|
@@ -22,13 +20,14 @@ class WebsocketTransport(BaseTransport):
|
|
|
22
20
|
verify_ssl=False,
|
|
23
21
|
skip_negotiation=False,
|
|
24
22
|
enable_trace=False,
|
|
23
|
+
proxies: dict = {},
|
|
25
24
|
**kwargs):
|
|
26
25
|
super(WebsocketTransport, self).__init__(**kwargs)
|
|
27
26
|
self._ws = None
|
|
28
27
|
self.enable_trace = enable_trace
|
|
29
|
-
self._thread = None
|
|
30
28
|
self.skip_negotiation = skip_negotiation
|
|
31
29
|
self.url = url
|
|
30
|
+
self.proxies = proxies
|
|
32
31
|
if headers is None:
|
|
33
32
|
self.headers = dict()
|
|
34
33
|
else:
|
|
@@ -37,7 +36,6 @@ class WebsocketTransport(BaseTransport):
|
|
|
37
36
|
self.token = None # auth
|
|
38
37
|
self.state = ConnectionState.disconnected
|
|
39
38
|
self.connection_alive = False
|
|
40
|
-
self._thread = None
|
|
41
39
|
self._ws = None
|
|
42
40
|
self.verify_ssl = verify_ssl
|
|
43
41
|
self.connection_checker = ConnectionStateChecker(
|
|
@@ -46,9 +44,6 @@ class WebsocketTransport(BaseTransport):
|
|
|
46
44
|
)
|
|
47
45
|
self.reconnection_handler = reconnection_handler
|
|
48
46
|
|
|
49
|
-
if len(self.logger.handlers) > 0:
|
|
50
|
-
websocket.enableTrace(self.enable_trace, self.logger.handlers[0])
|
|
51
|
-
|
|
52
47
|
def is_running(self):
|
|
53
48
|
return self.state != ConnectionState.disconnected
|
|
54
49
|
|
|
@@ -56,8 +51,11 @@ class WebsocketTransport(BaseTransport):
|
|
|
56
51
|
if self.state == ConnectionState.connected:
|
|
57
52
|
self.connection_checker.stop()
|
|
58
53
|
self._ws.close()
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
self.state = ConnectionState.disconnected
|
|
55
|
+
self.handshake_received = False
|
|
56
|
+
|
|
57
|
+
def is_trace_enabled(self) -> bool:
|
|
58
|
+
return self._ws.is_trace_enabled
|
|
61
59
|
|
|
62
60
|
def start(self):
|
|
63
61
|
if not self.skip_negotiation:
|
|
@@ -69,39 +67,41 @@ class WebsocketTransport(BaseTransport):
|
|
|
69
67
|
|
|
70
68
|
self.state = ConnectionState.connecting
|
|
71
69
|
self.logger.debug("start url:" + self.url)
|
|
72
|
-
|
|
73
|
-
self._ws =
|
|
70
|
+
|
|
71
|
+
self._ws = WebSocketClient(
|
|
74
72
|
self.url,
|
|
75
|
-
|
|
73
|
+
headers=self.headers,
|
|
74
|
+
is_binary=type(self.protocol) is MessagePackHubProtocol,
|
|
75
|
+
verify_ssl=self.verify_ssl,
|
|
76
76
|
on_message=self.on_message,
|
|
77
77
|
on_error=self.on_socket_error,
|
|
78
78
|
on_close=self.on_close,
|
|
79
79
|
on_open=self.on_open,
|
|
80
|
+
enable_trace=self.enable_trace
|
|
80
81
|
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
))
|
|
87
|
-
self._thread.daemon = True
|
|
88
|
-
self._thread.start()
|
|
82
|
+
|
|
83
|
+
# ToDo
|
|
84
|
+
# if len(self.logger.handlers) > 0:
|
|
85
|
+
# self._ws.enableTrace(self.enable_trace, self.logger.handlers[0])
|
|
86
|
+
self._ws.connect()
|
|
89
87
|
return True
|
|
90
88
|
|
|
91
89
|
def negotiate(self):
|
|
92
90
|
negotiate_url = Helpers.get_negotiate_url(self.url)
|
|
93
91
|
self.logger.debug("Negotiate url:{0}".format(negotiate_url))
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
negotiate_url,
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
status_code, data = RequestHelpers.post(
|
|
94
|
+
negotiate_url,
|
|
95
|
+
headers=self.headers,
|
|
96
|
+
proxies=self.proxies,
|
|
97
|
+
verify_ssl=self.verify_ssl)
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if response.status_code != 401 else UnAuthorizedHubError()
|
|
99
|
+
self.logger.debug(
|
|
100
|
+
"Response status code{0}".format(status_code))
|
|
103
101
|
|
|
104
|
-
|
|
102
|
+
if status_code != 200:
|
|
103
|
+
raise HubError(status_code)\
|
|
104
|
+
if status_code != 401 else UnAuthorizedHubError()
|
|
105
105
|
|
|
106
106
|
if "connectionId" in data.keys():
|
|
107
107
|
self.url = Helpers.encode_connection_id(
|
|
@@ -117,7 +117,6 @@ class WebsocketTransport(BaseTransport):
|
|
|
117
117
|
self.token = data["accessToken"]
|
|
118
118
|
self.headers = {"Authorization": "Bearer " + self.token}
|
|
119
119
|
|
|
120
|
-
|
|
121
120
|
def evaluate_handshake(self, message):
|
|
122
121
|
self.logger.debug("Evaluating handshake {0}".format(message))
|
|
123
122
|
msg, messages = self.protocol.decode_handshake(message)
|
|
@@ -130,25 +129,21 @@ class WebsocketTransport(BaseTransport):
|
|
|
130
129
|
self.connection_checker.start()
|
|
131
130
|
else:
|
|
132
131
|
self.logger.error(msg.error)
|
|
133
|
-
self.on_socket_error(
|
|
132
|
+
self.on_socket_error(msg.error)
|
|
134
133
|
self.stop()
|
|
135
134
|
self.state = ConnectionState.disconnected
|
|
136
135
|
return messages
|
|
137
136
|
|
|
138
|
-
def on_open(self
|
|
137
|
+
def on_open(self):
|
|
139
138
|
self.logger.debug("-- web socket open --")
|
|
140
139
|
msg = self.protocol.handshake_message()
|
|
141
140
|
self.send(msg)
|
|
142
141
|
|
|
143
|
-
def on_close(self
|
|
142
|
+
def on_close(self):
|
|
144
143
|
self.logger.debug("-- web socket close --")
|
|
145
|
-
self.logger.debug(close_status_code)
|
|
146
|
-
self.logger.debug(close_reason)
|
|
147
144
|
self.state = ConnectionState.disconnected
|
|
148
145
|
if self._on_close is not None and callable(self._on_close):
|
|
149
146
|
self._on_close()
|
|
150
|
-
if callback is not None and callable(callback):
|
|
151
|
-
callback()
|
|
152
147
|
|
|
153
148
|
def on_reconnect(self):
|
|
154
149
|
self.logger.debug("-- web socket reconnecting --")
|
|
@@ -156,11 +151,10 @@ class WebsocketTransport(BaseTransport):
|
|
|
156
151
|
if self._on_close is not None and callable(self._on_close):
|
|
157
152
|
self._on_close()
|
|
158
153
|
|
|
159
|
-
def on_socket_error(self,
|
|
154
|
+
def on_socket_error(self, error: Exception):
|
|
160
155
|
"""
|
|
161
156
|
Args:
|
|
162
|
-
|
|
163
|
-
error ([type]): [description]
|
|
157
|
+
error (Exception): websocket error
|
|
164
158
|
|
|
165
159
|
Raises:
|
|
166
160
|
HubError: [description]
|
|
@@ -171,7 +165,7 @@ class WebsocketTransport(BaseTransport):
|
|
|
171
165
|
self.logger.error("{0} {1}".format(error, type(error)))
|
|
172
166
|
self._on_close()
|
|
173
167
|
self.state = ConnectionState.disconnected
|
|
174
|
-
#raise HubError(error)
|
|
168
|
+
# raise HubError(error)
|
|
175
169
|
|
|
176
170
|
def on_message(self, app, raw_message):
|
|
177
171
|
self.logger.debug("Message received{0}".format(raw_message))
|
|
@@ -185,7 +179,7 @@ class WebsocketTransport(BaseTransport):
|
|
|
185
179
|
return self._on_message(messages)
|
|
186
180
|
|
|
187
181
|
return []
|
|
188
|
-
|
|
182
|
+
|
|
189
183
|
return self._on_message(
|
|
190
184
|
self.protocol.parse_messages(raw_message))
|
|
191
185
|
|
|
@@ -195,14 +189,12 @@ class WebsocketTransport(BaseTransport):
|
|
|
195
189
|
self._ws.send(
|
|
196
190
|
self.protocol.encode(message),
|
|
197
191
|
opcode=0x2
|
|
198
|
-
if type(self.protocol)
|
|
192
|
+
if type(self.protocol) is MessagePackHubProtocol else
|
|
199
193
|
0x1)
|
|
200
194
|
self.connection_checker.last_message = time.time()
|
|
201
195
|
if self.reconnection_handler is not None:
|
|
202
196
|
self.reconnection_handler.reset()
|
|
203
|
-
except
|
|
204
|
-
websocket._exceptions.WebSocketConnectionClosedException,
|
|
205
|
-
OSError) as ex:
|
|
197
|
+
except OSError as ex:
|
|
206
198
|
self.handshake_received = False
|
|
207
199
|
self.logger.warning("Connection closed {0}".format(ex))
|
|
208
200
|
self.state = ConnectionState.disconnected
|
|
@@ -217,7 +209,8 @@ class WebsocketTransport(BaseTransport):
|
|
|
217
209
|
raise ex
|
|
218
210
|
|
|
219
211
|
def handle_reconnect(self):
|
|
220
|
-
if not self.reconnection_handler.reconnecting
|
|
212
|
+
if not self.reconnection_handler.reconnecting \
|
|
213
|
+
and self._on_reconnect is not None and \
|
|
221
214
|
callable(self._on_reconnect):
|
|
222
215
|
self._on_reconnect()
|
|
223
216
|
self.reconnection_handler.reconnecting = True
|
|
@@ -227,17 +220,15 @@ class WebsocketTransport(BaseTransport):
|
|
|
227
220
|
except Exception as ex:
|
|
228
221
|
self.logger.error(ex)
|
|
229
222
|
sleep_time = self.reconnection_handler.next()
|
|
230
|
-
|
|
231
|
-
target=self.deferred_reconnect,
|
|
232
|
-
args=(sleep_time,)
|
|
233
|
-
).start()
|
|
223
|
+
self.deferred_reconnect(sleep_time)
|
|
234
224
|
|
|
235
225
|
def deferred_reconnect(self, sleep_time):
|
|
236
226
|
time.sleep(sleep_time)
|
|
237
227
|
try:
|
|
238
228
|
if not self.connection_alive:
|
|
239
|
-
self.
|
|
229
|
+
if not self.connection_checker.running:
|
|
230
|
+
self.send(PingMessage())
|
|
240
231
|
except Exception as ex:
|
|
241
232
|
self.logger.error(ex)
|
|
242
233
|
self.reconnection_handler.reconnecting = False
|
|
243
|
-
self.connection_alive = False
|
|
234
|
+
self.connection_alive = False
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: signalrcore
|
|
3
|
-
Version: 0.9.
|
|
4
|
-
Summary: A Python SignalR Core client(json and messagepack),
|
|
3
|
+
Version: 0.9.6a2
|
|
4
|
+
Summary: A Python SignalR Core client(json and messagepack),with invocation auth and two way streaming.Compatible with azure / serverless functions.Also with automatic reconnect and manually reconnect.
|
|
5
5
|
Home-page: https://github.com/mandrewcito/signalrcore
|
|
6
6
|
Author: mandrewcito
|
|
7
|
-
Author-email:
|
|
8
|
-
License: UNKNOWN
|
|
7
|
+
Author-email: signalrcore@mandrewcito.dev
|
|
9
8
|
Keywords: signalr core client 3.1
|
|
10
|
-
|
|
11
|
-
Classifier:
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
|
|
16
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: msgpack ==1.0.2
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: coverage ; extra == 'dev'
|
|
17
|
+
Requires-Dist: flake8 ; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest ; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-cov ; extra == 'dev'
|
|
20
|
+
Requires-Dist: requests ; extra == 'dev'
|
|
17
21
|
|
|
18
22
|
# SignalR core client
|
|
19
23
|
[](https://www.paypal.me/mandrewcito/1)
|
|
@@ -37,24 +41,16 @@ Requires-Dist: msgpack (==1.0.2)
|
|
|
37
41
|
|
|
38
42
|
# Develop
|
|
39
43
|
|
|
40
|
-
Test server will be
|
|
44
|
+
Test server will be available in [here](https://github.com/mandrewcito/signalrcore-containertestservers) and docker compose is required.
|
|
41
45
|
|
|
42
46
|
```bash
|
|
43
47
|
git clone https://github.com/mandrewcito/signalrcore-containertestservers
|
|
44
48
|
cd signalrcore-containertestservers
|
|
45
|
-
docker
|
|
49
|
+
docker compose up
|
|
46
50
|
cd ../signalrcore
|
|
47
51
|
make tests
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
## Known Issues
|
|
51
|
-
|
|
52
|
-
Issues related with closing sockets are inherited from the websocket-client library. Due to these problems i can't update the library to versions higher than websocket-client 0.54.0.
|
|
53
|
-
I'm working to solve it but for now its patched (Error number 1. Raises an exception, and then exception is treated for prevent errors).
|
|
54
|
-
If I update the websocket library I fall into error number 2, on local machine I can't reproduce it but travis builds fail (sometimes and randomly :()
|
|
55
|
-
* [1. Closing socket error](https://github.com/slackapi/python-slackclient/issues/171)
|
|
56
|
-
* [2. Random errors closing socket](https://github.com/websocket-client/websocket-client/issues/449)
|
|
57
|
-
|
|
58
54
|
# A Tiny How To
|
|
59
55
|
|
|
60
56
|
## Connect to a server without auth
|
|
@@ -162,7 +158,7 @@ connection = HubConnectionBuilder()\
|
|
|
162
158
|
})\
|
|
163
159
|
.build()
|
|
164
160
|
```
|
|
165
|
-
##
|
|
161
|
+
## Configuring skip negotiation
|
|
166
162
|
```python
|
|
167
163
|
hub_connection = HubConnectionBuilder() \
|
|
168
164
|
.with_url("ws://"+server_url, options={
|
|
@@ -333,5 +329,3 @@ hub_connection.stop()
|
|
|
333
329
|
sys.exit(0)
|
|
334
330
|
|
|
335
331
|
```
|
|
336
|
-
|
|
337
|
-
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
signalrcore/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
signalrcore/helpers.py,sha256=
|
|
3
|
-
signalrcore/hub_connection_builder.py,sha256=
|
|
2
|
+
signalrcore/helpers.py,sha256=QELTUh8v-j1qgEo3IXlcTEToR0K3lbZNwrDZpuhtJS0,4422
|
|
3
|
+
signalrcore/hub_connection_builder.py,sha256=A8dtysW5R1_EAJ_GcZZHtea9NLu96dfKMsmmtuY6LRI,9161
|
|
4
4
|
signalrcore/subject.py,sha256=izH-qzaVPHrK8OgN2ZJ5A_1v4Lx5QtqC_zYfq59hFp4,2112
|
|
5
5
|
signalrcore/hub/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
signalrcore/hub/auth_hub_connection.py,sha256=YVsmRhGhG8CYi7egnYbFJXPB7dDiPOnwX45dQg8MhUA,964
|
|
7
|
-
signalrcore/hub/base_hub_connection.py,sha256=
|
|
8
|
-
signalrcore/hub/errors.py,sha256=
|
|
9
|
-
signalrcore/hub/handlers.py,sha256
|
|
7
|
+
signalrcore/hub/base_hub_connection.py,sha256=dj-uXjGaPNPA7lGmIscwsah_Y6BeukpbdEwKGG5_UJk,9939
|
|
8
|
+
signalrcore/hub/errors.py,sha256=EDJgMT-ZpWc1VIRA--Qjahg2LBK0u0mM5V5UX7-i-XQ,180
|
|
9
|
+
signalrcore/hub/handlers.py,sha256=-f6OLQ9AMwGqE7Gh-84oHf0bTgUVsSlFuRg_T0ff_T8,1914
|
|
10
10
|
signalrcore/messages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
signalrcore/messages/base_message.py,sha256=AAydVr-g85U2xthOM4jm2hqHQ3ZBaOSiO0hmXlqCEpI,447
|
|
12
12
|
signalrcore/messages/cancel_invocation_message.py,sha256=CLsAom4Ubf3bn8HroEOzgiye_6VI9RnjgJmxtwqshbo,647
|
|
@@ -21,28 +21,30 @@ signalrcore/messages/handshake/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
|
21
21
|
signalrcore/messages/handshake/request.py,sha256=zNM8PZ4m4W1OSlEnXCxjPLnlYNXPF7yrToDDPxdcb9c,152
|
|
22
22
|
signalrcore/messages/handshake/response.py,sha256=ekQx_MX9ZTxHgDa7uiKAivBjky8xVMV5ljjf-Rpeo5w,103
|
|
23
23
|
signalrcore/protocol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
signalrcore/protocol/base_hub_protocol.py,sha256=
|
|
24
|
+
signalrcore/protocol/base_hub_protocol.py,sha256=6eSUD84R97C7eRfNEzakYVDANg-PEnw2tJ3LhdkPK9k,3000
|
|
25
25
|
signalrcore/protocol/json_hub_protocol.py,sha256=aJ5rLdhLt3ijW_G1aATtTFL-nbSa3cZFlv2yzcox3hg,1654
|
|
26
|
-
signalrcore/protocol/messagepack_protocol.py,sha256=
|
|
26
|
+
signalrcore/protocol/messagepack_protocol.py,sha256=BS5pd5bDk8GIOcWJZptFPtlwVPU5Wp9w2F-4Sg19vpY,7054
|
|
27
27
|
signalrcore/protocol/handshake/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
signalrcore/transport/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
-
signalrcore/transport/base_transport.py,sha256=
|
|
29
|
+
signalrcore/transport/base_transport.py,sha256=1LVw5kNxU0BBAeNA5smkV2vnhGKq3uD42EoPo8iO8gU,1134
|
|
30
30
|
signalrcore/transport/websockets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
31
|
signalrcore/transport/websockets/connection.py,sha256=mBaZeNxcqWjDlNOnMWk8Pp7vu3g3_wY5oQPUdIL7L3E,140
|
|
32
32
|
signalrcore/transport/websockets/reconnection.py,sha256=y3SSYLl3VFXLVNDtTdojBbAvMDzHoiXJod7wu-L9D3w,2680
|
|
33
|
-
signalrcore/transport/websockets/
|
|
33
|
+
signalrcore/transport/websockets/websocket_client.py,sha256=h2Ve7r0rEjesblsa8TnzijRDA4Cp-GTqQCp0ysau_Fg,7933
|
|
34
|
+
signalrcore/transport/websockets/websocket_transport.py,sha256=2uuWYfINegW01PgaS86hsEEg-1hcKBpFiikLrtGcgfQ,8425
|
|
34
35
|
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
test/base_test_case.py,sha256=
|
|
36
|
-
test/client_streaming_test.py,sha256=
|
|
37
|
-
test/configuration_test.py,sha256=
|
|
38
|
-
test/open_close_test.py,sha256=
|
|
39
|
-
test/reconnection_test.py,sha256=
|
|
40
|
-
test/send_auth_errors_test.py,sha256=
|
|
41
|
-
test/send_auth_test.py,sha256=
|
|
42
|
-
test/send_test.py,sha256=
|
|
43
|
-
test/streaming_test.py,sha256=
|
|
44
|
-
|
|
45
|
-
signalrcore-0.9.
|
|
46
|
-
signalrcore-0.9.
|
|
47
|
-
signalrcore-0.9.
|
|
48
|
-
signalrcore-0.9.
|
|
36
|
+
test/base_test_case.py,sha256=HDTCbf4_xjeA0rX9vj30B3sGBetfA_u728gcQ48CTNM,1840
|
|
37
|
+
test/client_streaming_test.py,sha256=qXmzj1KQkAbGXj9nls3p0-9q6PirUj78f40AQE7MIX8,836
|
|
38
|
+
test/configuration_test.py,sha256=KBBObisq2RvOrDM8OW7KzQnOPgJmCyv-Ndr2ykPjS8U,2222
|
|
39
|
+
test/open_close_test.py,sha256=EKedyOWS3q86LzNQCJ8KaYoXEusAi8vXCW8K2hzD5o8,1665
|
|
40
|
+
test/reconnection_test.py,sha256=dvbWplOE5gU7Ve_yNb9TmmrKoUl0KgsI8ZQPN8UXsJw,4251
|
|
41
|
+
test/send_auth_errors_test.py,sha256=warY87prADkdh1PL3_hmtGQt8j_wqFz1puiYj-Z2SLk,1808
|
|
42
|
+
test/send_auth_test.py,sha256=R1tS6NCYdr9fmVMspQmZDmHbQpTLUrDnzukjgE-blO4,2733
|
|
43
|
+
test/send_test.py,sha256=lKOt4OC84GMN4wunpfq_GU-nQQ_N5cgkGJj-Da3u1s4,5942
|
|
44
|
+
test/streaming_test.py,sha256=GM7BpQwrP27Pjmhdt3vQUTAZyWyoyA2QThPymY7s3Ao,1839
|
|
45
|
+
test/subscribe_test.py,sha256=fQ5W_l9kX9dp4khwo6U4-8hEDYLLD5p9FdgwTeOAhDg,505
|
|
46
|
+
signalrcore-0.9.6a2.dist-info/LICENSE,sha256=zlgZo2qli0c9PLoPObfeGy9yEXuQOV2HF26LykBg5Ko,1101
|
|
47
|
+
signalrcore-0.9.6a2.dist-info/METADATA,sha256=-AvuZOscKnfnrr4ZjjBcRrg7stCPbWM_hS1P-jOnXTs,10087
|
|
48
|
+
signalrcore-0.9.6a2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
49
|
+
signalrcore-0.9.6a2.dist-info/top_level.txt,sha256=I28KSOVJS0rNgMCI8E8W-13gvoCGHkmwc6z4NSrQlo0,17
|
|
50
|
+
signalrcore-0.9.6a2.dist-info/RECORD,,
|