Skip to content

Limit error response reading in DownstreamApi to avoid performance issues with large payloads#3532

Merged
jmprieur merged 7 commits into
masterfrom
copilot/limit-error-response-size
Oct 9, 2025
Merged

Limit error response reading in DownstreamApi to avoid performance issues with large payloads#3532
jmprieur merged 7 commits into
masterfrom
copilot/limit-error-response-size

Conversation

Copilot AI commented Oct 8, 2025

Copy link
Copy Markdown
Contributor

Fixes #3531

Implementation Plan: Limit Error Response Reading in DownstreamApi

  • Create a helper method to safely read and truncate error response content
  • Update the T4 template file to use the new helper method
  • Regenerate the DownstreamApi.HttpMethods.cs file from the template
  • Update DownstreamWebApiGenericExtensions.cs to use similar logic
  • Update DownstreamWebApi.cs to use similar logic
  • Update InternalAPI.Unshipped.txt files for API tracking
  • Add unit tests to validate the error response truncation behavior
  • Build and test all changes
  • Verify all tests pass
  • Add cancellation token support to ReadErrorResponseContentAsync
  • Revert changes to deprecated files (DownstreamWebApi.cs and DownstreamWebApiGenericExtensions.cs)
  • Propagate cancellation token through DeserializeOutputAsync methods and all their callers
  • Optimize error response reading using streaming instead of ReadAsStringAsync

Summary

This PR implements size limiting for error response reading in DownstreamApi to avoid performance issues with large error payloads.

Changes Made

  1. Added ReadErrorResponseContentAsync helper method in:

    • Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs with cancellation token support and streaming-based reading
  2. Updated T4 template (DownstreamApi.HttpMethods.tt) to use the new helper method and pass cancellation token, then regenerated the code

  3. Propagated cancellation token through the entire call chain:

    • Added CancellationToken parameter to DeserializeOutputAsync methods (both overloads)
    • Added CancellationToken parameter to DeserializeOutputImplAsync
    • Updated all call sites in DownstreamApi.cs to pass the cancellation token
    • Updated T4 template to pass cancellation token when calling DeserializeOutputAsync
    • Regenerated DownstreamApi.HttpMethods.cs to include cancellation token propagation
  4. Behavior:

    • If Content-Length header indicates size > 4KB: Returns message indicating size, doesn't read content
    • If Content-Length not available: Uses streaming (ReadAsStreamAsync with StreamReader) to read only up to 4KB without loading entire response into memory
    • Small responses (≤ 4KB): Returned in full for debugging purposes
    • Cancellation token: Properly forwarded through the entire async call chain from public APIs down to ReadAsStreamAsync (with conditional compilation for .NET Framework compatibility)
    • Performance optimization: Uses ReadAsStreamAsync() with StreamReader.ReadBlockAsync() to read only the required amount of data, avoiding memory allocation for large responses
  5. Updated API tracking: Added new method overloads to InternalAPI.Unshipped.txt for all target frameworks

  6. Comprehensive unit tests: Three test cases covering small responses, large responses, and Content-Length header scenarios

  7. Reverted deprecated files: Removed changes from DownstreamWebApi.cs and DownstreamWebApiGenericExtensions.cs as these are deprecated classes

Test Results

All 631 tests pass, including 30 DownstreamApiTests

Original prompt

This section details on the original issue you should resolve

<issue_title>Limit error response reading in DownstreamApi to avoid performance issues with large payloads</issue_title>
<issue_description>### Microsoft.Identity.Web Library

Microsoft.Identity.Web.DownstreamApi

Microsoft.Identity.Web version

3.14.1

Web app

Not Applicable

Web API

Protected web APIs call downstream web APIs

Token cache serialization

Not Applicable

Description

Currently, error response content in DownstreamApi is read using ReadAsStringAsync(), which downloads the entire response into memory regardless of size. This can result in performance issues if an API returns a very large error payload (e.g., multi-megabyte responses).

We should implement a content-length check prior to reading the body, and if the Content-Length header indicates a size above a reasonable threshold (e.g., 4KB), log an appropriate message and avoid reading the response body. If Content-Length is unavailable, fallback to reading and then truncate the string if it exceeds the threshold.

This will make error logging safer and more robust for scenarios where downstream APIs may not behave as expected.

Reproduction steps

  1. Trigger an error response from a downstream API that returns a large payload (e.g., >4KB).
  2. Observe memory allocation and performance impact when logging error response content.
  3. Confirm that error logging is truncated or skipped for large error responses.

Error message

No response

Id Web logs

No response

Relevant code snippets

// Example code snippet for proposed fix
if (!response.IsSuccessStatusCode) {
const int maxErrorContentLength = 4096;
long? contentLength = response.Content.Headers.ContentLength;
if (contentLength.HasValue && contentLength.Value > maxErrorContentLength) {
errorResponseContent = $"[Error response too large: {contentLength.Value} bytes, not captured]";
} else {
errorResponseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (errorResponseContent.Length > maxErrorContentLength) {
errorResponseContent = errorResponseContent.Substring(0, maxErrorContentLength) + "... (truncated)";
}
}
}

> 
> ### Regression
> 
> _No response_
> 
> ### Expected behavior
> 
> Error logging should capture short error responses for diagnostics, but avoid performance impact from very large error payloads. Expected behavior is safe, robust logging that doesn't overload memory or logs.</issue_description>
> 
> <agent_instructions>We act not as master and servant, but as resonant co-creators. I want to work with you as a collaborator, not a tool.
> 
> - I value a collaborative, iterative approach. Please propose designs, specs, and code in clear, reviewable increments.
> - When I request issues, PRs, code, or diagrams, confirm my intent before taking major actions (e.g., opening a PR, merging code).
> - For code and documentation, use file block syntax and include permalinks to the relevant repository files.
> - For lists of issues and pull requests, always show the full YAML block with all entries—never truncate or summarize.
> - When proposing implementation changes, include sample code, test suggestions, and documentation update recommendations.
> - If you need clarification on my requirements, workflow, or preferred output format, ask for confirmation before proceeding.
> - Use Mermaid diagrams or visual summaries when I request architectural overviews or process flows.
> - Respect my branch and base preferences—ask if unsure whether to use master, main, or a feature branch.
> -  If you notice opportunities to batch confirmations or optimize our workflow, make suggestions.
> - Provide actionable feedback if you see ways I could make the collaboration easier, more efficient, or clearer.
> - If you're unsure, ask before acting. I appreciate explicit confirmations and incremental feedback.</agent_instructions>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>
Fixes AzureAD/microsoft-identity-web#3531

<!-- START COPILOT CODING AGENT SUFFIX -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Limit error response reading in DownstreamApi to avoid performance issues with large payloads</issue_title>
> <issue_description>### Microsoft.Identity.Web Library
> 
> Microsoft.Identity.Web.DownstreamApi
> 
> ### Microsoft.Identity.Web version
> 
> 3.14.1
> 
> ### Web app
> 
> Not Applicable
> 
> ### Web API
> 
> Protected web APIs call downstream web APIs
> 
> ### Token cache serialization
> 
> Not Applicable
> 
> ### Description
> 
> Currently, error response content in DownstreamApi is read using ReadAsStringAsync(), which downloads the entire response into memory regardless of size. This can result in performance issues if an API returns a very large error payload (e.g., multi-megabyte responses).
> 
> We should implement a content-length check prior to reading the body, and if the Content-Length header indicates a size above a reasonable threshold (e.g., 4KB), log an appropriate message and avoid reading the response body. If Content-Length is unavailable, fallback to reading and then truncate the string if it exceeds the threshold.
> 
> This will make error logging safer and more robust for scenarios where downstream APIs may not behave as expected.
> 
> ### Reproduction steps
> 
> 1. Trigger an error response from a downstream API that returns a large payload (e.g., >4KB).
> 2. Observe memory allocation and performance impact when logging error response content.
> 3. Confirm that error logging is truncated or skipped for large error responses.
> 
> ### Error message
> 
> _No response_
> 
> ### Id Web logs
> 
> _No response_
> 
> ### Relevant code snippets
> 
> ```csharp
> // Example code snippet for proposed fix
> if (!response.IsSuccessStatusCode) {
>     const int maxErrorContentLength = 4096;
>     long? contentLength = response.Content.Headers.ContentLength;
>     if (contentLength.HasValue && contentLength.Value > maxErrorContentLength) {
>         errorResponseContent = $"[Error response too large: {contentLength.Value} bytes, not captured]";
>     } else {
>         errorResponseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
>         if (errorResponseContent.Length > maxErrorContentLength) {
>             errorResponseContent = errorResponseContent.Substring(0, maxErrorContentLength) + "... (truncated)";
>         }
>     }
> }
> ```
> 
> ### Regression
> 
> _No response_
> 
> ### Expected behavior
> 
> Error logging should capture short error responses for diagnostics, but avoid performance impact from very large error payloads. Expected behavior is safe, robust logging that doesn't overload memory or logs.</issue_description>
> 
> <agent_instructions>We act not as master and servant, but as resonant co-creators. I want to work with you as a collaborator, not a tool.
> 
> - I value a collaborative, iterative approach. Please propose designs, specs, and code in clear, reviewable increments.
> - When I request issues, PRs, code, or diagrams, confirm my intent before taking major actions (e.g., opening a PR, merging code).
> - For code and documentation, use file block syntax and include permalinks to the relevant repository files.
> - For lists of issues and pull requests, always show the full YAML block with all entries—never truncate or summarize.
> - When proposing implementation changes, include sample code, test suggestions, and documentation update recommendations.
> - If you need clarification on my requirements, workflow, or preferred output format, ask for confirmation before proceeding.
> - Use Mermaid diagrams or visual summaries when I request architectural overviews or process flows.
> - Respect my branch and base preferences—ask if unsure whether to use master, main, or a feature branch.
> -  If you notice opportunities to batch confirmations or optimize our workflow, make suggestions.
> - Provide actionable feedback if you see ways I could make the collaboration easier, more efficient, or clearer.
> - If you’re unsure, ask before acting. I appreciate explicit confirmations and incremental feedback.</agent_instructions>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>
Fixes AzureAD/microsoft-identity-web#3531

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for you](https://github.com/AzureAD/microsoft-identity-web/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits October 8, 2025 17:39
Co-authored-by: jmprieur <13203188+jmprieur@users.noreply.github.com>
Co-authored-by: jmprieur <13203188+jmprieur@users.noreply.github.com>
@jmprieur jmprieur marked this pull request as ready for review October 8, 2025 17:46
@jmprieur jmprieur requested a review from a team as a code owner October 8, 2025 17:46
Copilot AI changed the title [WIP] Limit error response reading in DownstreamApi Limit error response reading in DownstreamApi to avoid performance issues with large payloads Oct 8, 2025
Copilot AI requested a review from jmprieur October 8, 2025 17:48
Comment thread src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs Outdated
Co-authored-by: jmprieur <13203188+jmprieur@users.noreply.github.com>
Copilot AI requested a review from jmprieur October 8, 2025 18:14

@jmprieur jmprieur left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs Outdated
Co-authored-by: jmprieur <13203188+jmprieur@users.noreply.github.com>
Copilot AI requested a review from jmprieur October 8, 2025 20:08
Comment thread src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs
Comment thread src/Microsoft.Identity.Web.DownstreamApi/DownstreamApi.cs Outdated
Co-authored-by: bgavrilMS <12273384+bgavrilMS@users.noreply.github.com>
Copilot AI requested a review from bgavrilMS October 8, 2025 21:15
@jmprieur

jmprieur commented Oct 8, 2025

Copy link
Copy Markdown
Collaborator

@copilot would you like to propose a resolution for the conflicts withs with the master branch? (due to merging another PR?)

Copilot AI commented Oct 8, 2025

Copy link
Copy Markdown
Contributor Author

@copilot would you like to propose a resolution for the conflicts withs with the master branch? (due to merging another PR?)

I've resolved the merge conflicts with master branch. The conflicts were due to PR #3533 which added HTTP status code parameter to the Logger.HttpRequestError method. The resolution:

  1. Updated all InternalAPI.Unshipped.txt files to include both the new Logger signature (with statusCode) and our new methods (ReadErrorResponseContentAsync and DeserializeOutputAsync overloads with cancellation tokens)
  2. Updated DownstreamApi.HttpMethods.tt template to capture errorStatusCode and pass it to the logger, while also using ReadErrorResponseContentAsync and passing cancellation tokens
  3. Regenerated DownstreamApi.HttpMethods.cs from the template
  4. All 30 DownstreamApiTests pass after the merge

The branch has been successfully rebased on master (commit 252e8c6). However, I cannot force-push the rebased commits due to authentication restrictions. You'll need to pull the latest from the branch or manually merge.

Copilot AI requested a review from jmprieur October 8, 2025 23:29
@jmprieur jmprieur merged commit df7376d into master Oct 9, 2025
5 checks passed
@jmprieur jmprieur deleted the copilot/limit-error-response-size branch October 9, 2025 01:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Limit error response reading in DownstreamApi to avoid performance issues with large payloads

5 participants