Skip to content

Commit 18cc895

Browse files
committed
Improve the error handling for HTTP client so consumers can trigger appropriate behavior
Signed-off-by: Patrick Assuied <[email protected]>
1 parent e7c85ce commit 18cc895

File tree

3 files changed

+76
-4
lines changed

3 files changed

+76
-4
lines changed

dapr/clients/exceptions.py

+6
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,22 @@ def __init__(
3333
message: Optional[str],
3434
error_code: Optional[str] = ERROR_CODE_UNKNOWN,
3535
raw_response_bytes: Optional[bytes] = None,
36+
status_code: Optional[int] = None,
37+
reason: Optional[str] = None,
3638
):
3739
self._message = message
3840
self._error_code = error_code
3941
self._raw_response_bytes = raw_response_bytes
42+
self._status_code = status_code
43+
self._reason = reason
4044

4145
def as_dict(self):
4246
return {
4347
'message': self._message,
4448
'errorCode': self._error_code,
4549
'raw_response_bytes': self._raw_response_bytes,
50+
'status_code': self._status_code,
51+
'reason': self._reason,
4652
}
4753

4854
def as_json_safe_dict(self):

dapr/clients/http/client.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -106,22 +106,39 @@ async def convert_to_error(self, response: aiohttp.ClientResponse) -> DaprIntern
106106
try:
107107
error_body = await response.read()
108108
if (error_body is None or len(error_body) == 0) and response.status == 404:
109-
return DaprInternalError('Not Found', ERROR_CODE_DOES_NOT_EXIST)
109+
return DaprInternalError(
110+
f'HTTP status code: {response.status}',
111+
error_code=ERROR_CODE_DOES_NOT_EXIST,
112+
status_code=response.status,
113+
reason=response.reason)
110114
error_info = self._serializer.deserialize(error_body)
111115
except Exception:
112116
return DaprInternalError(
113-
f'Unknown Dapr Error. HTTP status code: {response.status}',
117+
f'HTTP status code: {response.status}',
118+
error_code=ERROR_CODE_UNKNOWN,
114119
raw_response_bytes=error_body,
120+
status_code=response.status,
121+
reason=response.reason
115122
)
116123

117124
if error_info and isinstance(error_info, dict):
118125
message = error_info.get('message')
119126
error_code = error_info.get('errorCode') or ERROR_CODE_UNKNOWN
120-
return DaprInternalError(message, error_code, raw_response_bytes=error_body)
127+
status_code = response.status
128+
reason = response.reason
129+
return DaprInternalError(
130+
message or f'HTTP status code: {response.status}',
131+
error_code,
132+
raw_response_bytes=error_body,
133+
status_code=status_code,
134+
reason=reason)
121135

122136
return DaprInternalError(
123-
f'Unknown Dapr Error. HTTP status code: {response.status}',
137+
f'HTTP status code: {response.status}',
138+
error_code=ERROR_CODE_UNKNOWN,
124139
raw_response_bytes=error_body,
140+
status_code=response.status,
141+
reason=response.reason
125142
)
126143

127144
def get_ssl_context(self):

tests/clients/test_secure_http_service_invocation_client.py

+49
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
2424

2525
from dapr.clients import DaprClient, DaprGrpcClient
26+
from dapr.clients.exceptions import DaprInternalError
2627
from dapr.clients.health import DaprHealth
2728
from dapr.clients.http.client import DaprHttpClient
2829
from dapr.conf import settings
@@ -139,3 +140,51 @@ def test_timeout_exception_thrown_when_timeout_reached(self):
139140
self.server.set_server_delay(1.5)
140141
with self.assertRaises(TimeoutError):
141142
new_client.invoke_method(self.app_id, self.method_name, '')
143+
144+
def test_notfound_json_body_exception_thrown_with_status_code_and_reason(self):
145+
self.server.set_response(b'{"error": "Not found"}', code=404)
146+
with self.assertRaises(DaprInternalError) as context:
147+
self.client.invoke_method(self.app_id, self.method_name, '')
148+
149+
error_dict = context.exception.as_dict()
150+
self.assertEqual("HTTP status code: 404", error_dict.get('message'))
151+
self.assertEqual("UNKNOWN", error_dict.get('errorCode'))
152+
self.assertEqual(b'{"error": "Not found"}', error_dict.get('raw_response_bytes'))
153+
self.assertEqual(404, error_dict.get('status_code'))
154+
self.assertEqual('Not Found', error_dict.get('reason'))
155+
156+
def test_notfound_no_body_exception_thrown_with_status_code_and_reason(self):
157+
self.server.set_response(b'', code=404)
158+
with self.assertRaises(DaprInternalError) as context:
159+
self.client.invoke_method(self.app_id, self.method_name, '')
160+
161+
error_dict = context.exception.as_dict()
162+
self.assertEqual("HTTP status code: 404", error_dict.get('message'))
163+
self.assertEqual("ERR_DOES_NOT_EXIST", error_dict.get('errorCode'))
164+
self.assertEqual(None, error_dict.get('raw_response_bytes'))
165+
self.assertEqual(404, error_dict.get('status_code'))
166+
self.assertEqual('Not Found', error_dict.get('reason'))
167+
168+
def test_notfound_no_json_body_exception_thrown_with_status_code_and_reason(self):
169+
self.server.set_response(b"Not found", code=404)
170+
with self.assertRaises(DaprInternalError) as context:
171+
self.client.invoke_method(self.app_id, self.method_name, '')
172+
173+
error_dict = context.exception.as_dict()
174+
self.assertEqual("HTTP status code: 404", error_dict.get('message'))
175+
self.assertEqual("UNKNOWN", error_dict.get('errorCode'))
176+
self.assertEqual(b"Not found", error_dict.get('raw_response_bytes'))
177+
self.assertEqual(404, error_dict.get('status_code'))
178+
self.assertEqual('Not Found', error_dict.get('reason'))
179+
180+
def test_notfound_json_body_w_message_exception_thrown_with_status_code_and_reason(self):
181+
self.server.set_response(b'{"message": "My message", "errorCode": "MY_ERROR_CODE"}', code=404)
182+
with self.assertRaises(DaprInternalError) as context:
183+
self.client.invoke_method(self.app_id, self.method_name, '')
184+
185+
error_dict = context.exception.as_dict()
186+
self.assertEqual("My message", error_dict.get('message'))
187+
self.assertEqual("MY_ERROR_CODE", error_dict.get('errorCode'))
188+
self.assertEqual(b'{"message": "My message", "errorCode": "MY_ERROR_CODE"}', error_dict.get('raw_response_bytes'))
189+
self.assertEqual(404, error_dict.get('status_code'))
190+
self.assertEqual('Not Found', error_dict.get('reason'))

0 commit comments

Comments
 (0)