Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
Expand Down Expand Up @@ -150,6 +151,24 @@ internal static async Task<DocumentClientException> CreateDocumentClientExceptio
// If service rejects the initial payload like header is to large it will return an HTML error instead of JSON.
if (string.Equals(responseMessage.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase))
{
// For more information, see https://github.com/Azure/azure-cosmos-dotnet-v3/issues/4162.
if (await GatewayStoreClient.IsJsonHTTPResponseFromGatewayInvalidAsync(responseMessage))
{
return new DocumentClientException(
new Error
{
Code = responseMessage.StatusCode.ToString(),
Message = "Invalid JSON HTTP response from Gateway."
},
responseMessage.Headers,
responseMessage.StatusCode)
{
StatusDescription = responseMessage.ReasonPhrase,
ResourceAddress = resourceIdOrFullName,
RequestStatistics = requestStatistics
};
}

Stream readStream = await responseMessage.Content.ReadAsStreamAsync();
Error error = Documents.Resource.LoadFrom<Error>(readStream);
return new DocumentClientException(
Expand Down Expand Up @@ -197,6 +216,19 @@ internal static async Task<DocumentClientException> CreateDocumentClientExceptio
}
}

/// <summary>
/// Checking if exception response (deserializable Error object) is valid based on the content length.
/// For more information, see <see href="https://github.com/Azure/azure-cosmos-dotnet-v3/issues/4162."/>.
/// </summary>
/// <param name="responseMessage"></param>
private static async Task<bool> IsJsonHTTPResponseFromGatewayInvalidAsync(HttpResponseMessage responseMessage)
{
string readString = await responseMessage.Content.ReadAsStringAsync();

return responseMessage.Content?.Headers?.ContentLength == 0 ||
readString.Trim().Length == 0;
}

internal static bool IsAllowedRequestHeader(string headerName)
{
if (!headerName.StartsWith("x-ms", StringComparison.OrdinalIgnoreCase))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Tracing;
using Microsoft.Azure.Cosmos.Tracing.TraceData;
using Microsoft.Azure.Documents;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

/// <summary>
/// Tests for <see cref="GatewayStoreClient"/>.
/// </summary>
[TestClass]
public class GatewayStoreClientTests
{
/// <summary>
/// Testing the exception behavior when a response from the Gateway has no response (deserializable Error object) based on the content length.
/// For more information, see <see href="https://github.com/Azure/azure-cosmos-dotnet-v3/issues/4162"/>.
/// </summary>
/// <returns></returns>
[TestMethod]
[DataRow(@"")]
[DataRow(@" ")]
public async Task CreateDocumentClientExceptionInvalidJsonResponseFromGatewayTestAsync(string content)
{
HttpResponseMessage responseMessage = new(statusCode: System.Net.HttpStatusCode.NotFound)
{
RequestMessage = new HttpRequestMessage(
method: HttpMethod.Get,
requestUri: @"https://pt_ac_test_uri.com/"),
Content = new StringContent(
content: content),
};

responseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

IClientSideRequestStatistics requestStatistics = new ClientSideRequestStatisticsTraceDatum(
startTime: DateTime.UtcNow,
trace: NoOpTrace.Singleton);

DocumentClientException documentClientException = await GatewayStoreClient.CreateDocumentClientExceptionAsync(
responseMessage: responseMessage,
requestStatistics: requestStatistics);

Assert.IsNotNull(value: documentClientException);
Assert.AreEqual(expected: HttpStatusCode.NotFound, actual: documentClientException.StatusCode);
Assert.IsTrue(condition: documentClientException.Message.Contains("Invalid JSON HTTP response from Gateway."));

Assert.IsNotNull(value: documentClientException.Error);
Assert.AreEqual(expected: HttpStatusCode.NotFound.ToString(), actual: documentClientException.Error.Code);
Assert.AreEqual(expected: "Invalid JSON HTTP response from Gateway.", actual: documentClientException.Error.Message);
}

/// <summary>
/// Testing the exception behavior when a response from the Gateway has a response (deserializable Error object) based the content length.
/// For more information, see <see href="https://github.com/Azure/azure-cosmos-dotnet-v3/issues/4162"/>.
/// </summary>
/// <returns></returns>
[TestMethod]
[DataRow(@"This is the content of a test error message.")]
public async Task CreateDocumentClientExceptionValidJsonResponseFromGatewayTestAsync(string content)
{
HttpResponseMessage responseMessage = new(statusCode: System.Net.HttpStatusCode.NotFound)
{
RequestMessage = new HttpRequestMessage(
method: HttpMethod.Get,
requestUri: @"https://pt_ac_test_uri.com/"),
Content = new StringContent(
content: JsonConvert.SerializeObject(
value: new Error() { Code = HttpStatusCode.NotFound.ToString(), Message = content })),
};

responseMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

IClientSideRequestStatistics requestStatistics = new ClientSideRequestStatisticsTraceDatum(
startTime: DateTime.UtcNow,
trace: NoOpTrace.Singleton);

DocumentClientException documentClientException = await GatewayStoreClient.CreateDocumentClientExceptionAsync(
responseMessage: responseMessage,
requestStatistics: requestStatistics);

Assert.IsNotNull(value: documentClientException);
Assert.AreEqual(expected: HttpStatusCode.NotFound, actual: documentClientException.StatusCode);
Assert.IsTrue(condition: documentClientException.Message.Contains(content));

Assert.IsNotNull(value: documentClientException.Error);
Assert.AreEqual(expected: HttpStatusCode.NotFound.ToString(), actual: documentClientException.Error.Code);
Assert.AreEqual(expected: content, actual: documentClientException.Error.Message);
}
}
}