From ad30786de591bc9cf4ba23ce3a94de2b9b46c6b4 Mon Sep 17 00:00:00 2001 From: Jens Date: Sun, 23 Feb 2025 13:02:21 +0100 Subject: [PATCH 1/3] Ratelimit soap requests --- example.py | 14 +++++++++++--- ihcsdk/ihcclient.py | 2 +- ihcsdk/ihcconnection.py | 23 ++++++++++++++++++++++- ihcsdk/ihccontroller.py | 4 ++++ requerements.txt | 2 ++ 5 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 requerements.txt diff --git a/example.py b/example.py index 00a6274..02ae466 100644 --- a/example.py +++ b/example.py @@ -6,6 +6,7 @@ The resource will be toggled when the test starts, and after this you can set it using '1' and '2'. 'q' to quit """ + import logging import sys @@ -15,12 +16,13 @@ _LOGGER = logging.getLogger(__name__) + def main(): """Do the test""" - logging.basicConfig(stream=sys.stdout, level=logging.INFO) + logging.basicConfig(stream=sys.stdout, level=logging.WARNING) # set log level below to debug for further troubleshooting - logging.getLogger("ihcsdk").setLevel( logging.INFO) + logging.getLogger("ihcsdk").setLevel(logging.WARNING) starttime = datetime.now() @@ -51,6 +53,8 @@ def gettime(): print("Url response like a IHC controller - now authenticating") ihc = IHCController(url, username, password) + ihc.client.connection.min_interval = 0.05 + ihc.client.connection.logtiming = True if not ihc.authenticate(): print("Authenticate failed") exit() @@ -69,6 +73,7 @@ def gettime(): print("log: " + log) info = ihc.client.get_system_info() + print("System info: ") print(info) runtimevalue = ihc.get_runtime_value(resid) @@ -78,10 +83,14 @@ def gettime(): print("Runtime value: " + str(runtimevalue)) ihc.client.enable_runtime_notification(resid) + + logging.basicConfig(level=logging.DEBUG) + changes = ihc.client.wait_for_resource_value_changes(10) print(repr(changes)) ihc.add_notify_event(resid, on_ihc_change, True) + ihc.add_notify_event(2728463, on_ihc_change, True) while True: i = input() @@ -96,7 +105,6 @@ def gettime(): if i == "q": break ihc.disconnect() - ihc.client.connection.session.close() main() diff --git a/ihcsdk/ihcclient.py b/ihcsdk/ihcclient.py index 4b732be..76e3dcc 100644 --- a/ihcsdk/ihcclient.py +++ b/ihcsdk/ihcclient.py @@ -356,7 +356,7 @@ def __get_value(resource_value): case "WSBooleanValue": result = resource_value.find("./ns2:value", IHCSoapClient.ihcns).text == "true" case "WSIntegerValue": - result = int( resource_value.find("./ns2:integer", IHCSoapClient.ihcns).text) + result = int(resource_value.find("./ns2:integer", IHCSoapClient.ihcns).text) case "WSFloatingPointValue": result = round(float(resource_value.find("./ns2:floatingPointValue", IHCSoapClient.ihcns).text), 2) case "WSEnumValue": diff --git a/ihcsdk/ihcconnection.py b/ihcsdk/ihcconnection.py index ef1d90a..6632031 100644 --- a/ihcsdk/ihcconnection.py +++ b/ihcsdk/ihcconnection.py @@ -3,15 +3,17 @@ # pylint: disable=too-few-public-methods import logging import requests +import time import xml.etree.ElementTree from urllib.parse import urlparse from urllib3.util import Retry from requests.adapters import HTTPAdapter +from functools import wraps _LOGGER = logging.getLogger(__name__) - + class IHCConnection(object): """Implements a http connection to the controller""" @@ -35,6 +37,10 @@ def __init__(self, url: str): allowed_methods={"POST"}, ) self.session.mount("http://", HTTPAdapter(max_retries=self.retries)) + # default minimum time between calls in seconds (0 will not rate limit) + self.min_interval: float = 0.0 + self.last_call_time:float = 0 + self.logtiming = False def cert_verify(self): return None @@ -50,6 +56,7 @@ def soap_action(self, service, action, payloadbody): "SOAPAction": action, } try: + self.rateLimit() _LOGGER.debug("soap payload %s", payload) self.last_exception = None response = self.session.post( @@ -75,3 +82,17 @@ def soap_action(self, service, action, payloadbody): self.last_exception = exp self.last_response = response return False + + def rateLimit( self): + """ Rate limit the calls to this function.""" + current_time: float = time.time() + time_since_last_call: float = current_time - self.last_call_time + if self.logtiming: + _LOGGER.warning("time since last call %f sec", time_since_last_call) + # If not enough time has passed, sleep for the remaining time + if time_since_last_call < self.min_interval: + sleep_time:float = self.min_interval - time_since_last_call + _LOGGER.debug("Ratelimiting for %f sec", sleep_time) + time.sleep(sleep_time) + # Update the last call time and call the function + self.last_call_time = time.time() diff --git a/ihcsdk/ihccontroller.py b/ihcsdk/ihccontroller.py index c78ed49..c0abf82 100644 --- a/ihcsdk/ihccontroller.py +++ b/ihcsdk/ihccontroller.py @@ -70,6 +70,10 @@ def disconnect(self): TODO call disconnect on ihcclient """ self._notifyrunning = False + # wait for notify thread to finish + while self._notifythread.is_alive(): + time.sleep(0.1) # Optional sleep to prevent busy waiting + self.client.connection.session.close() def get_runtime_value(self, ihcid: int): """Get runtime value with re-authenticate if needed""" diff --git a/requerements.txt b/requerements.txt new file mode 100644 index 0000000..ace9e84 --- /dev/null +++ b/requerements.txt @@ -0,0 +1,2 @@ +requests +cryptography \ No newline at end of file From eaec73867ca1af6afb8161f5558e38062928b923 Mon Sep 17 00:00:00 2001 From: Jens Date: Sun, 23 Feb 2025 13:05:18 +0100 Subject: [PATCH 2/3] Cleanup --- ihcsdk/ihcconnection.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ihcsdk/ihcconnection.py b/ihcsdk/ihcconnection.py index 6632031..faa5b60 100644 --- a/ihcsdk/ihcconnection.py +++ b/ihcsdk/ihcconnection.py @@ -9,10 +9,8 @@ from urllib.parse import urlparse from urllib3.util import Retry from requests.adapters import HTTPAdapter -from functools import wraps _LOGGER = logging.getLogger(__name__) - class IHCConnection(object): """Implements a http connection to the controller""" From 1db219cc08017481a95ed43d24ff76eac17a2092 Mon Sep 17 00:00:00 2001 From: Jens Date: Sun, 23 Feb 2025 13:07:28 +0100 Subject: [PATCH 3/3] new version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a5c25e3..5f83128 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="ihcsdk", - version="2.8.9", + version="2.8.10", description="IHC Python SDK", long_description=( "SDK for connection to the LK IHC Controller. "