21
21
22
22
"""
23
23
24
- # imports
25
-
26
24
__version__ = "0.0.0+auto.0"
27
25
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ConnectionManager.git"
28
26
31
29
32
30
WIZNET5K_SSL_SUPPORT_VERSION = (9 , 1 )
33
31
34
- # typing
35
-
36
-
37
32
if not sys .implementation .name == "circuitpython" :
38
33
from typing import List , Optional , Tuple
39
34
46
41
)
47
42
48
43
49
- # ssl and pool helpers
50
-
51
-
52
44
class _FakeSSLSocket :
53
45
def __init__ (self , socket : CircuitPythonSocketType , tls_mode : int ) -> None :
54
46
self ._socket = socket
@@ -189,11 +181,8 @@ def get_radio_ssl_context(radio):
189
181
return _global_ssl_contexts [_get_radio_hash_key (radio )]
190
182
191
183
192
- # main class
193
-
194
-
195
184
class ConnectionManager :
196
- """A library for managing sockets accross libraries."""
185
+ """A library for managing sockets across multiple hardware platforms and libraries."""
197
186
198
187
def __init__ (
199
188
self ,
@@ -224,23 +213,24 @@ def _get_connected_socket( # pylint: disable=too-many-arguments
224
213
is_ssl : bool ,
225
214
ssl_context : Optional [SSLContextType ] = None ,
226
215
):
227
- try :
228
- socket = self ._socket_pool .socket (addr_info [0 ], addr_info [1 ])
229
- except (OSError , RuntimeError ) as exc :
230
- return exc
216
+
217
+ socket = self ._socket_pool .socket (addr_info [0 ], addr_info [1 ])
231
218
232
219
if is_ssl :
233
220
socket = ssl_context .wrap_socket (socket , server_hostname = host )
234
221
connect_host = host
235
222
else :
236
223
connect_host = addr_info [- 1 ][0 ]
237
- socket .settimeout (timeout ) # socket read timeout
224
+
225
+ # Set socket read and connect timeout.
226
+ socket .settimeout (timeout )
238
227
239
228
try :
240
229
socket .connect ((connect_host , port ))
241
- except (MemoryError , OSError ) as exc :
230
+ except (MemoryError , OSError ):
231
+ # If any connect problems, clean up and re-raise the problem exception.
242
232
socket .close ()
243
- return exc
233
+ raise
244
234
245
235
return socket
246
236
@@ -269,82 +259,82 @@ def close_socket(self, socket: SocketType) -> None:
269
259
self ._available_sockets .remove (socket )
270
260
271
261
def free_socket (self , socket : SocketType ) -> None :
272
- """Mark a managed socket as available so it can be reused."""
262
+ """Mark a managed socket as available so it can be reused. The socket is not closed. """
273
263
if socket not in self ._managed_socket_by_key .values ():
274
264
raise RuntimeError ("Socket not managed" )
275
265
self ._available_sockets .add (socket )
276
266
267
+ def _register_connected_socket (self , key , socket ):
268
+ self ._key_by_managed_socket [socket ] = key
269
+ self ._managed_socket_by_key [key ] = socket
270
+
271
+ # pylint: disable=too-many-arguments
277
272
def get_socket (
278
273
self ,
279
274
host : str ,
280
275
port : int ,
281
276
proto : str ,
282
277
session_id : Optional [str ] = None ,
283
278
* ,
284
- timeout : float = 1 ,
279
+ timeout : float = 1.0 ,
285
280
is_ssl : bool = False ,
286
281
ssl_context : Optional [SSLContextType ] = None ,
287
282
) -> CircuitPythonSocketType :
288
283
"""
289
- Get a new socket and connect.
290
-
291
- - **host** *(str)* – The host you are want to connect to: "www.adaftuit.com"
292
- - **port** *(int)* – The port you want to connect to: 80
293
- - **proto** *(str)* – The protocal you want to use: "http:"
294
- - **session_id** *(Optional[str])* – A unique Session ID, when wanting to have multiple open
295
- connections to the same host
296
- - **timeout** *(float)* – Time timeout used for connecting
297
- - **is_ssl** *(bool)* – If the connection is to be over SSL (auto set when proto is
298
- "https:")
299
- - **ssl_context** *(Optional[SSLContextType])* – The SSL context to use when making SSL
300
- requests
284
+ Get a new socket and connect to the given host.
285
+
286
+ :param str host: host to connect to, such as ``"www.example.org"``
287
+ :param int port: port to use for connection, such as ``80`` or ``443``
288
+ :param str proto: connection protocol: ``"http:"``, ``"https:"``, etc.
289
+ :param Optional[str]: unique session ID,
290
+ used for multiple simultaneous connections to the same host
291
+ :param float timeout: how long to wait to connect
292
+ :param bool is_ssl: ``True`` If the connection is to be over SSL;
293
+ automatically set when ``proto`` is ``"https:"`
294
+ :param Optional[SSLContextType]: SSL context to use when making SSL requests
301
295
"""
302
296
if session_id :
303
297
session_id = str (session_id )
304
298
key = (host , port , proto , session_id )
299
+
300
+ # Do we have already have a socket available for the requested connection?
305
301
if key in self ._managed_socket_by_key :
306
302
socket = self ._managed_socket_by_key [key ]
307
303
if socket in self ._available_sockets :
308
304
self ._available_sockets .remove (socket )
309
305
return socket
310
306
311
- raise RuntimeError (f"Socket already connected to { proto } //{ host } :{ port } " )
307
+ raise RuntimeError (
308
+ f"An existing socket is already connected to { proto } //{ host } :{ port } "
309
+ )
312
310
313
311
if proto == "https:" :
314
312
is_ssl = True
315
313
if is_ssl and not ssl_context :
316
- raise AttributeError (
317
- "ssl_context must be set before using adafruit_requests for https"
318
- )
314
+ raise ValueError ("ssl_context must be provided if using ssl" )
319
315
320
316
addr_info = self ._socket_pool .getaddrinfo (
321
317
host , port , 0 , self ._socket_pool .SOCK_STREAM
322
318
)[0 ]
323
319
324
- first_exception = None
325
- result = self ._get_connected_socket (
326
- addr_info , host , port , timeout , is_ssl , ssl_context
327
- )
328
- if isinstance (result , Exception ):
329
- # Got an error, if there are any available sockets, free them and try again
320
+ try :
321
+ socket = self ._get_connected_socket (
322
+ addr_info , host , port , timeout , is_ssl , ssl_context
323
+ )
324
+ self ._register_connected_socket (key , socket )
325
+ return socket
326
+ except (MemoryError , OSError , RuntimeError ):
327
+ # Could not get a new socket (or two, if SSL).
328
+ # If there are any available sockets, free them all and try again.
330
329
if self .available_socket_count :
331
- first_exception = result
332
330
self ._free_sockets ()
333
- result = self ._get_connected_socket (
331
+ socket = self ._get_connected_socket (
334
332
addr_info , host , port , timeout , is_ssl , ssl_context
335
333
)
336
- if isinstance (result , Exception ):
337
- last_result = f", first error: { first_exception } " if first_exception else ""
338
- raise RuntimeError (
339
- f"Error connecting socket: { result } { last_result } "
340
- ) from result
341
-
342
- self ._key_by_managed_socket [result ] = key
343
- self ._managed_socket_by_key [key ] = result
344
- return result
345
-
346
-
347
- # global helpers
334
+ self ._register_connected_socket (key , socket )
335
+ return socket
336
+ # Re-raise exception if no sockets could be freed.
337
+ raise
348
338
349
339
350
340
def connection_manager_close_all (
@@ -353,9 +343,9 @@ def connection_manager_close_all(
353
343
"""
354
344
Close all open sockets for pool, optionally release references.
355
345
356
- - **socket_pool** *( Optional[SocketpoolModuleType])* – A specifc SocketPool you want to close
357
- sockets for, leave blank for all SocketPools
358
- - **release_references** *( bool)* – Set to True if you want to also clear stored references to
346
+ :param Optional[SocketpoolModuleType] socket_pool:
347
+ a specific `SocketPool` whose sockets you want to close; `None`` means all `SocketPool`s
348
+ :param bool release_references: `` True`` if you want to also clear stored references to
359
349
the SocketPool and SSL contexts
360
350
"""
361
351
if socket_pool :
@@ -383,10 +373,7 @@ def connection_manager_close_all(
383
373
384
374
def get_connection_manager (socket_pool : SocketpoolModuleType ) -> ConnectionManager :
385
375
"""
386
- Get the ConnectionManager singleton for the given pool.
387
-
388
- - **socket_pool** *(Optional[SocketpoolModuleType])* – The SocketPool you want the
389
- ConnectionManager for
376
+ Get or create the ConnectionManager singleton for the given pool.
390
377
"""
391
378
if socket_pool not in _global_connection_managers :
392
379
_global_connection_managers [socket_pool ] = ConnectionManager (socket_pool )
0 commit comments