Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle invalid JSON HTTP response from Gateway for error scenarios #4162

Closed
ealsur opened this issue Oct 27, 2023 · 5 comments · Fixed by #4229
Closed

Handle invalid JSON HTTP response from Gateway for error scenarios #4162

ealsur opened this issue Oct 27, 2023 · 5 comments · Fixed by #4229
Assignees
Labels
feature-request New feature or request

Comments

@ealsur
Copy link
Member

ealsur commented Oct 27, 2023

We have received multiple reports of cases where the SDK throws a JSON serialization exception when processing HTTP responses from the Cosmos DB Gateway, for example:

Exception: Newtonsoft.Json.JsonReaderException: Error reading JObject from JsonReader. Path '', line 0, position 0.   
  at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings)    
  at Microsoft.Azure.Documents.JsonSerializable.LoadFrom(JsonReader reader, JsonSerializerSettings serializerSettings)    
  at Microsoft.Azure.Documents.JsonSerializable.LoadFrom[T](JsonTextReader jsonReader, ITypeResolver`1 typeResolver, JsonSerializerSettings settings)    
  at Microsoft.Azure.Cosmos.GatewayStoreClient.<CreateDocumentClientExceptionAsync>d__11.MoveNext() --- End of stack trace from previous location where exception was thrown ---    
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()    
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)    
  at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()    
  at Microsoft.Azure.Cosmos.GatewayStoreClient.<ParseResponseAsync>d__9.MoveNext()

Microsoft.Azure.Cosmos.GatewayStoreClient.<ParseResponseAsync> hints as this being an HTTP response from Gateway.

Microsoft.Azure.Cosmos.GatewayStoreClient.<CreateDocumentClientExceptionAsync> means the statuscode of the HTTP response was a failure (not 200 or any success).

Which means, Gateway returned a failure but the content was invalid.

SDK checks the Content-Type header and if it's application/json then expects the content to be valid JSON:

if (string.Equals(responseMessage.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase))
{
Stream readStream = await responseMessage.Content.ReadAsStreamAsync();
Error error = Documents.Resource.LoadFrom<Error>(readStream);

In all these cases what happened was that the content was either empty or invalid JSON.

To avoid the exception in the most common case (empty), we should check the Content-Size of the response, and if it's 0, either throw an exception saying that the response is invalid because it's empty or avoid attempting the JSON deserialization of the body and just produce the error response using the statuscode as if it had no content.

Expected outcome

  • If the Content-Size header is application/json but the content size is 0, skip attempting to deserialize the content
  • If the Content-Size header is application/json but the content size > 0, attempt to deserialize the content. If that fails, follow the code path that treats the content as text (non-JSON path).
@sourabh1007
Copy link
Contributor

Doesn't it service responsibility to define a standard format of response in case of error which any SDK can understand and use (or log) the information accordingly.
Right now, it looks like, service sends response in whatever format they want, and SDK is trying to handle all the scenarios.

@ealsur
Copy link
Member Author

ealsur commented Oct 27, 2023

Agree that the service should behave correctly. But this change would avoid the conversation be "The SDK is throwing an exception, it's probably a problem in the SDK" which is what happened in all cases this was reported. We then found out that it was never the SDK problem. Making this change would surface the actional information that is required to course the investigation correctly.

@philipthomas-MSFT philipthomas-MSFT moved this from Triage to In Progress in Azure Cosmos SDKs Nov 1, 2023
@ealsur ealsur changed the title Include more details on failed HTTP response JSON deserialization Handle invalid JSON HTTP response from Gateway for error scenarios Nov 3, 2023
@philipthomas-MSFT
Copy link
Contributor

philipthomas-MSFT commented Dec 26, 2023

I am able to repro this in branch. Will work through suggested solutions.

@philipthomas-MSFT
Copy link
Contributor

philipthomas-MSFT commented Dec 26, 2023

@ealsur

Clarification:

  • What HttpStatus codes are normally associate with CreateDocumentClientExceptionAsync? I understand that we think that there will never be a 200 with a CreateDocumentClientExceptionAsync. How can we verify this?
  • When you say "Content-Size" you really mean ContentLength, correct? Or is there a header key called "Content-Size" that has a different behavior and function as ContentLength?
  • Not seeing a custom exception for invalid responses, unless we reuse DocumentClientException. I would like to create a new one, but want to make sure I am not duplicating efforts.
    • When throwing this new exception or using DocumentClientException, we should inherit the HttpStatus code from the failed response, but will it cause any side effects (ie. retry policies, timeouts, etc.)?
  • Should we return the failed response ResourceAddress, RequestStatistics, all other Header information, etc.? Why and what is the impact?
  • What about scenarios when the Content has whitespaces? The ContentLength will not be 0. So checking ContentLength alone will not bullet proof this.

Testing

Testing for empty and whitespaces.

@philipthomas-MSFT
Copy link
Contributor

Blocked while waiting on approvals and feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request New feature or request
Projects
Status: Done
3 participants