Skip to content

Commit c24c622

Browse files
Fix stream consumption bug in GatewayStoreClient.CreateDocumentClientExceptionAsync
Co-authored-by: kirankumarkolli <6880899+kirankumarkolli@users.noreply.github.com>
1 parent 956dab4 commit c24c622

2 files changed

Lines changed: 49 additions & 12 deletions

File tree

Microsoft.Azure.Cosmos/src/GatewayStoreClient.cs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -168,31 +168,37 @@ internal static async Task<DocumentClientException> CreateDocumentClientExceptio
168168
}
169169

170170
// If service rejects the initial payload like header is to large it will return an HTML error instead of JSON.
171+
string contentString = null;
171172
if (string.Equals(responseMessage.Content?.Headers?.ContentType?.MediaType, "application/json", StringComparison.OrdinalIgnoreCase) &&
172173
responseMessage.Content?.Headers.ContentLength > 0)
173174
{
174175
try
175176
{
176-
Stream contentAsStream = await responseMessage.Content.ReadAsStreamAsync();
177-
Error error = JsonSerializable.LoadFrom<Error>(stream: contentAsStream);
178-
179-
return new DocumentClientException(
180-
errorResource: error,
181-
responseHeaders: responseMessage.Headers,
182-
statusCode: responseMessage.StatusCode)
177+
// Buffer the content once to avoid "stream already consumed" issue
178+
contentString = await responseMessage.Content.ReadAsStringAsync();
179+
using (MemoryStream contentStream = new MemoryStream(Encoding.UTF8.GetBytes(contentString)))
183180
{
184-
StatusDescription = responseMessage.ReasonPhrase,
185-
ResourceAddress = resourceIdOrFullName,
186-
RequestStatistics = requestStatistics
187-
};
181+
Error error = JsonSerializable.LoadFrom<Error>(stream: contentStream);
182+
183+
return new DocumentClientException(
184+
errorResource: error,
185+
responseHeaders: responseMessage.Headers,
186+
statusCode: responseMessage.StatusCode)
187+
{
188+
StatusDescription = responseMessage.ReasonPhrase,
189+
ResourceAddress = resourceIdOrFullName,
190+
RequestStatistics = requestStatistics
191+
};
192+
}
188193
}
189194
catch
190195
{
191196
}
192197
}
193198

194199
StringBuilder contextBuilder = new StringBuilder();
195-
contextBuilder.AppendLine(await responseMessage.Content.ReadAsStringAsync());
200+
// Reuse the already buffered content if available, otherwise read it now
201+
contextBuilder.AppendLine(contentString ?? await responseMessage.Content.ReadAsStringAsync());
196202

197203
HttpRequestMessage requestMessage = responseMessage.RequestMessage;
198204

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreClientTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,37 @@ public async Task TestCreateDocumentClientExceptionWhenMediaTypeIsApplicationJso
246246
Assert.IsNotNull(value: documentClientException.Error.Message);
247247
}
248248

249+
/// <summary>
250+
/// Test to verify the fix for the stream consumption issue when JSON deserialization fails.
251+
/// This reproduces the scenario where a 403 response has application/json content type
252+
/// but invalid JSON content, which would previously cause "stream already consumed" exception.
253+
/// </summary>
254+
[TestMethod]
255+
[Owner("copilot")]
256+
public async Task TestStreamConsumptionBugFixWhenJsonDeserializationFails()
257+
{
258+
// Create invalid JSON content that will fail deserialization but has application/json content type
259+
string invalidJson = "{ \"error\": invalid json content that will fail parsing }";
260+
261+
HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.Forbidden)
262+
{
263+
RequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://test.com/dbs/db1/colls/coll1/docs/doc1"),
264+
Content = new StringContent(invalidJson, Encoding.UTF8, "application/json")
265+
};
266+
267+
IClientSideRequestStatistics requestStatistics = GatewayStoreClientTests.CreateClientSideRequestStatistics();
268+
269+
// This should NOT throw an InvalidOperationException about stream being consumed
270+
DocumentClientException exception = await GatewayStoreClient.CreateDocumentClientExceptionAsync(
271+
responseMessage: responseMessage,
272+
requestStatistics: requestStatistics);
273+
274+
// Verify the exception was created successfully with fallback logic
275+
Assert.IsNotNull(exception);
276+
Assert.AreEqual(HttpStatusCode.Forbidden, exception.StatusCode);
277+
Assert.IsTrue(exception.Message.Contains(invalidJson), "Exception message should contain the original invalid JSON content");
278+
}
279+
249280
private static IClientSideRequestStatistics CreateClientSideRequestStatistics()
250281
{
251282
return new ClientSideRequestStatisticsTraceDatum(

0 commit comments

Comments
 (0)