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

Fixed: SentryHttpFailedRequestHandler disposes Content on .net framework #3306

Merged
merged 5 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixes

- response.Content is not disposed by SentryHttpFailedRequestHandler on .net framework ([#3306](https://github.com/getsentry/sentry-dotnet/pull/3306))
jamescrosswell marked this conversation as resolved.
Show resolved Hide resolved

## 4.4.0

### Features
Expand Down
28 changes: 28 additions & 0 deletions src/Sentry/Internal/Extensions/HttpStatusExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Sentry.Internal.Extensions;

#if !NET5_0_OR_GREATER
internal static class HttpStatusExtensions
{
private const string HttpRequestExceptionMessage = "Response status code does not indicate success: {0}";

/// <summary>
/// This mimics the behaviour of <see cref="HttpResponseMessage.EnsureSuccessStatusCode"/> for netcore3.0 and later
/// by throwing an exception if the status code is outside the 200 range without disposing the content.
///
/// See https://github.com/getsentry/sentry-dotnet/issues/2684
/// </summary>
/// <param name="statusCode"></param>
/// <exception cref="HttpRequestException"></exception>
public static void EnsureSuccessStatusCode(this HttpStatusCode statusCode)
{
if ((int)statusCode < 200 || (int)statusCode > 299)
{
throw new HttpRequestException(string.Format(
CultureInfo.InvariantCulture,
HttpRequestExceptionMessage,
(int)statusCode
));
}
}
}
#endif
30 changes: 16 additions & 14 deletions src/Sentry/SentryHttpFailedRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Sentry.Internal.Extensions;
using Sentry.Protocol;

namespace Sentry;
Expand All @@ -19,23 +20,17 @@ protected internal override void DoEnsureSuccessfulResponse([NotNull] HttpReques
return;
}

#if NET5_0_OR_GREATER
// Starting with .NET 5, the content and headers are guaranteed to not be null.
var bodySize = response.Content.Headers.ContentLength;
#else
// We have to get the content body size before calling EnsureSuccessStatusCode,
// because older implementations of EnsureSuccessStatusCode disposes the content.
// See https://github.com/dotnet/runtime/issues/24845

// The ContentLength might be null (but that's ok).
// See https://github.com/dotnet/runtime/issues/16162
var bodySize = response.Content?.Headers?.ContentLength;
#endif

// Capture the event
try
{
#if NET5_0_OR_GREATER
response.EnsureSuccessStatusCode();
#else
// Use our own implementation of EnsureSuccessStatusCode because older implementations of
// EnsureSuccessStatusCode disposes the content.
// See https://github.com/dotnet/runtime/issues/24845
jamescrosswell marked this conversation as resolved.
Show resolved Hide resolved
response.StatusCode.EnsureSuccessStatusCode();
#endif
}
catch (HttpRequestException exception)
{
Expand All @@ -54,7 +49,14 @@ protected internal override void DoEnsureSuccessfulResponse([NotNull] HttpReques
var responseContext = new Response
{
StatusCode = (short)response.StatusCode,
BodySize = bodySize
#if NET5_0_OR_GREATER
// Starting with .NET 5, the content and headers are guaranteed to not be null.
BodySize = response.Content.Headers.ContentLength
#else
// The ContentLength might be null (but that's ok).
// See https://github.com/dotnet/runtime/issues/16162
BodySize = response.Content?.Headers?.ContentLength
#endif
};

if (!Options.SendDefaultPii)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace Sentry.Tests.Internals.Extensions;

#if !NET5_0_OR_GREATER
public class HttpRequestExceptionMessageTests
{
[Fact]
public void EnsureSuccessStatusCode_StatusCodeInRange_DoesNotThrow()
{
// Arrange
const HttpStatusCode statusCode = HttpStatusCode.OK; // Any status code in the 200-299 range

// Act
var act = () => statusCode.EnsureSuccessStatusCode();

// Assert
act.Should().NotThrow<HttpRequestException>();
}

[Fact]
public void EnsureSuccessStatusCode_StatusCodeOutOfRange_ThrowsHttpRequestException()
{
var unsuccessfulStatusCodes = new List<int>();
unsuccessfulStatusCodes.AddRange(Enumerable.Range(0, 199));
unsuccessfulStatusCodes.AddRange(Enumerable.Range(300, 600));
foreach (var i in unsuccessfulStatusCodes)
{
// Arrange and Act
var statusCode = (HttpStatusCode)i;
var act = () => statusCode.EnsureSuccessStatusCode();

// Assert
act.Should().Throw<HttpRequestException>()
.WithMessage(string.Format(
CultureInfo.InvariantCulture,
"Response status code does not indicate success: {0}",
(int)statusCode
));
}
}
}
#endif
Loading