diff --git a/onvif/client.py b/onvif/client.py index 916e368..f479c4d 100644 --- a/onvif/client.py +++ b/onvif/client.py @@ -96,6 +96,23 @@ def original_load(self, *args: Any, **kwargs: Any) -> None: return original_load(self, *args, **kwargs) +class AsyncTransportProtocolErrorHandler(AsyncTransport): + """Retry on remote protocol error. + + http://datatracker.ietf.org/doc/html/rfc2616#section-8.1.4 allows the server + # to close the connection at any time, we treat this as a normal and try again + # once since + """ + + @retry_connection_error(attempts=2, exception=httpx.RemoteProtocolError) + async def post(self, address, message, headers): + return await super().post(address, message, headers) + + @retry_connection_error(attempts=2, exception=httpx.RemoteProtocolError) + async def get(self, address, params, headers): + return await super().get(address, params, headers) + + async def _cached_document(url: str) -> Document: """Load external XML document from disk.""" if url in _DOCUMENT_CACHE: @@ -223,9 +240,9 @@ def __init__( verify=_NO_VERIFY_SSL_CONTEXT, timeout=timeouts, limits=_HTTPX_LIMITS ) self.transport = ( - AsyncTransport(client=client, wsdl_client=wsdl_client) + AsyncTransportProtocolErrorHandler(client=client, wsdl_client=wsdl_client) if no_cache - else AsyncTransport( + else AsyncTransportProtocolErrorHandler( client=client, wsdl_client=wsdl_client, cache=SqliteCache() ) ) diff --git a/onvif/wrappers.py b/onvif/wrappers.py index 2f062f2..13578c5 100644 --- a/onvif/wrappers.py +++ b/onvif/wrappers.py @@ -19,6 +19,7 @@ def retry_connection_error( attempts: int = DEFAULT_ATTEMPTS, + exception: httpx.HTTPError = httpx.RequestError, ) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]: """Define a wrapper to retry on connection error.""" @@ -37,7 +38,7 @@ async def _async_wrap_connection_error_retry( # type: ignore[return] for attempt in range(attempts): try: return await func(*args, **kwargs) - except httpx.RequestError as ex: + except exception as ex: # # We should only need to retry on RemoteProtocolError but some cameras # are flakey and sometimes do not respond to the Renew request so we