Skip to content

Commit 93b83d1

Browse files
authored
Remove requests_unixsocket (#604)
This includes implementation to connect to a Unix socket without `requests_unixsocket`. This allows us to remove this dependency along with the version restrictions for `urllib3` and `requests`; Fixes #583 #600
2 parents 0f102cb + fcae421 commit 93b83d1

File tree

4 files changed

+80
-13
lines changed

4 files changed

+80
-13
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ dist/
66
.coverage
77
.tox
88

9+
# Tox tests leftovers
10+
build
11+
912
# Translations
1013
*.mo
1114

pylxd/client.py

+69-7
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,21 @@
1414
import json
1515
import os
1616
import re
17+
import socket
1718
from enum import Enum
1819
from typing import NamedTuple
1920
from urllib import parse
2021

2122
import requests
22-
import requests_unixsocket
23+
import requests.adapters
24+
import urllib3
25+
import urllib3.connection
2326
from cryptography import x509
2427
from cryptography.hazmat.primitives import hashes
2528
from ws4py.client import WebSocketBaseClient
2629

2730
from pylxd import exceptions, managers
2831

29-
requests_unixsocket.monkeypatch()
30-
3132
SNAP_ROOT = os.path.expanduser("~/snap/lxd/common/config/")
3233
APT_ROOT = os.path.expanduser("~/.config/lxc/")
3334
CERT_FILE_NAME = "client.crt"
@@ -51,6 +52,9 @@ class Cert(NamedTuple):
5152
key=os.path.join(CERTS_PATH, KEY_FILE_NAME),
5253
) # pragma: no cover
5354

55+
DEFAULT_SCHEME = "http+unix://"
56+
SOCKET_CONNECTION_TIMEOUT = 60
57+
5458

5559
class EventType(Enum):
5660
All = "all"
@@ -59,6 +63,65 @@ class EventType(Enum):
5963
Lifecycle = "lifecycle"
6064

6165

66+
class _UnixSocketHTTPConnection(urllib3.connection.HTTPConnection, object):
67+
def __init__(self, unix_socket_url):
68+
super(_UnixSocketHTTPConnection, self).__init__(
69+
"localhost", timeout=SOCKET_CONNECTION_TIMEOUT
70+
)
71+
self.unix_socket_url = unix_socket_url
72+
self.timeout = SOCKET_CONNECTION_TIMEOUT
73+
self.sock = None
74+
75+
def __del__(self):
76+
if self.sock:
77+
self.sock.close()
78+
79+
def connect(self):
80+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
81+
sock.settimeout(self.timeout)
82+
socket_path = parse.unquote(parse.urlparse(self.unix_socket_url).netloc)
83+
sock.connect(socket_path)
84+
self.sock = sock
85+
86+
87+
class _UnixSocketHTTPConnectionPool(urllib3.HTTPConnectionPool):
88+
def __init__(self, socket_path):
89+
super(_UnixSocketHTTPConnectionPool, self).__init__("localhost")
90+
self.socket_path = socket_path
91+
92+
def _new_conn(self):
93+
return _UnixSocketHTTPConnection(self.socket_path)
94+
95+
96+
class _UnixAdapter(requests.adapters.HTTPAdapter):
97+
def __init__(self, pool_connections=25, *args, **kwargs):
98+
super(_UnixAdapter, self).__init__(*args, **kwargs)
99+
self.pools = urllib3._collections.RecentlyUsedContainer(
100+
pool_connections, dispose_func=lambda p: p.close()
101+
)
102+
103+
def get_connection(self, url, proxies):
104+
with self.pools.lock:
105+
conn = self.pools.get(url)
106+
if conn:
107+
return conn
108+
109+
conn = _UnixSocketHTTPConnectionPool(url)
110+
self.pools[url] = conn
111+
112+
return conn
113+
114+
# This method is needed fo compatibility with later requests versions.
115+
def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None):
116+
return self.get_connection(request.url, None)
117+
118+
def request_url(self, request, proxies):
119+
return request.path_url
120+
121+
def close(self):
122+
self.pools.clear()
123+
124+
62125
class LXDSSLAdapter(requests.adapters.HTTPAdapter):
63126
def cert_verify(self, conn, url, verify, cert):
64127
with open(verify, "rb") as fd:
@@ -74,11 +137,10 @@ def get_session_for_url(url: str, verify=None, cert=None) -> requests.Session:
74137
75138
Call sites can use this to customise the session before passing into a Client.
76139
"""
77-
session: requests.Session
78-
if url.startswith("http+unix://"):
79-
session = requests_unixsocket.Session()
140+
session = requests.Session()
141+
if url.startswith(DEFAULT_SCHEME):
142+
session.mount(DEFAULT_SCHEME, _UnixAdapter())
80143
else:
81-
session = requests.Session()
82144
session.cert = cert
83145
session.verify = verify
84146

pylxd/tests/test_client.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import pytest
2020
import requests
21-
import requests_unixsocket
21+
import requests.adapters
2222

2323
from pylxd import client, exceptions
2424

@@ -631,13 +631,17 @@ class TestGetSessionForUrl(TestCase):
631631
def test_session_unix_socket(self):
632632
"""http+unix URL return a requests_unixsocket session."""
633633
session = client.get_session_for_url("http+unix://test.com")
634-
self.assertIsInstance(session, requests_unixsocket.Session)
634+
self.assertIsInstance(
635+
session.get_adapter("http+unix://"), requests.adapters.HTTPAdapter
636+
)
635637

636638
def test_session_http(self):
637639
"""HTTP nodes return the default requests session."""
638640
session = client.get_session_for_url("http://test.com")
639641
self.assertIsInstance(session, requests.Session)
640-
self.assertNotIsInstance(session, requests_unixsocket.Session)
642+
self.assertRaises(
643+
requests.exceptions.InvalidSchema, session.get_adapter, "http+unix://"
644+
)
641645

642646
def test_session_cert(self):
643647
"""If certs are given, they're set on the Session."""

setup.cfg

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ packages = find:
2020
install_requires =
2121
cryptography >= 3.2
2222
python-dateutil >= 2.4.2
23-
requests >= 2.20.0, < 2.32.0
23+
requests >= 2.20.0
2424
requests-toolbelt >= 0.8.0
25-
requests-unixsocket >= 0.1.5
26-
urllib3 < 2
2725
ws4py != 0.3.5, >= 0.3.4 # 0.3.5 is broken for websocket support
2826

2927
[options.extras_require]

0 commit comments

Comments
 (0)