diff --git a/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs b/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs
index a5b4df9290..d97693468b 100644
--- a/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs
+++ b/src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs
@@ -39,7 +39,7 @@ public static void Initialize(string installPathExtensionsDirectory)
{ "BuildCommonServicesWrapper6Plus", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.AspNetCore6Plus.dll") },
{ "GenericHostWebHostBuilderExtensionsWrapper6Plus", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.AspNetCore6Plus.dll") },
- { "InvokeActionMethodAsync6Plus", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.AspNetCore6Plus.dll") },
+ { "InvokeActionMethodAsyncWrapper6Plus", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.AspNetCore6Plus.dll") },
{ "ResolveAppWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.Owin.dll") },
diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/Instrumentation.xml
index b598e001f9..abff9ddff5 100644
--- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/Instrumentation.xml
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/Instrumentation.xml
@@ -19,6 +19,9 @@ SPDX-License-Identifier: Apache-2.0
+
+
+
diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/InvokeActionMethodAsyncWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/InvokeActionMethodAsyncWrapper6Plus.cs
similarity index 96%
rename from src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/InvokeActionMethodAsyncWrapper.cs
rename to src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/InvokeActionMethodAsyncWrapper6Plus.cs
index b387ef9a41..9a81efd8c7 100644
--- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/InvokeActionMethodAsyncWrapper.cs
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/InvokeActionMethodAsyncWrapper6Plus.cs
@@ -21,7 +21,7 @@ public class InvokeActionMethodAsyncWrapper6Plus : IWrapper
public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo)
{
- return new CanWrapResponse(nameof(InvokeActionMethodAsyncWrapper6Plus).Equals(methodInfo.RequestedWrapperName));
+ return new CanWrapResponse("InvokeActionMethodAsyncWrapper6Plus".Equals(methodInfo.RequestedWrapperName));
}
public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/ResponseStreamWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/ResponseStreamWrapper.cs
index b3b1e936d2..1cf763ff4a 100644
--- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/ResponseStreamWrapper.cs
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/ResponseStreamWrapper.cs
@@ -8,17 +8,19 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using NewRelic.Agent.Api;
+using NewRelic.Agent.Extensions.Logging;
namespace NewRelic.Providers.Wrapper.AspNetCore6Plus
{
///
- /// Wrapper for the response stream, handles checking for response content type and injecting the browser script if appropriate
+ /// Wrapper for the response stream, handles injecting the browser script if appropriate
///
public class ResponseStreamWrapper : Stream
{
private readonly IAgent _agent;
private Stream _baseStream;
private HttpContext _context;
+ private bool _isContentLengthSet;
public ResponseStreamWrapper(IAgent agent, Stream baseStream, HttpContext context)
@@ -30,57 +32,83 @@ public ResponseStreamWrapper(IAgent agent, Stream baseStream, HttpContext contex
CanWrite = true;
}
- public override void Flush()
+ public override Task FlushAsync(CancellationToken cancellationToken)
{
- _baseStream.Flush();
+ if (!_isContentLengthSet && IsHtmlResponse())
+ {
+ _context.Response.Headers.ContentLength = null;
+ _isContentLengthSet = true;
+ }
+
+ return _baseStream.FlushAsync(cancellationToken);
}
- public override int Read(byte[] buffer, int offset, int count)
+ public override void Flush()
{
- return _baseStream.Read(buffer, offset, count);
+ _baseStream.Flush();
}
+ public override int Read(byte[] buffer, int offset, int count) => _baseStream.Read(buffer, offset, count);
+
public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin);
public override void SetLength(long value)
{
_baseStream.SetLength(value);
- }
- public override void Write(ReadOnlySpan buffer)
- {
- _baseStream.Write(buffer);
+ IsHtmlResponse(forceReCheck: true);
}
- public override void WriteByte(byte value)
- {
- _baseStream.WriteByte(value);
- }
+ public override void Write(ReadOnlySpan buffer) => _baseStream.Write(buffer);
+
+ public override void WriteByte(byte value) => _baseStream.WriteByte(value);
public override void Write(byte[] buffer, int offset, int count)
{
- var curBuf = buffer.AsMemory(offset, count).ToArray();
- _agent.TryInjectBrowserScriptAsync(_context.Response.ContentType, _context.Request.Path.Value, curBuf, _baseStream)
+ if (IsHtmlResponse())
+ {
+ var curBuf = buffer.AsMemory(offset, count).ToArray();
+ _agent.TryInjectBrowserScriptAsync(_context.Response.ContentType, _context.Request.Path.Value, curBuf,
+ _baseStream)
.GetAwaiter().GetResult();
- }
+ }
+ else
+ {
+ _agent.CurrentTransaction.LogFinest("ResponseStreamWrapper: Not an HTML response so no attempt to inject RUM.");
+ _baseStream?.Write(buffer, offset, count);
+ }
- public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
}
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
+
public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
{
- await _agent.TryInjectBrowserScriptAsync(_context.Response.ContentType, _context.Request.Path.Value, buffer.ToArray(), _baseStream);
+ if (IsHtmlResponse())
+ {
+ await _agent.TryInjectBrowserScriptAsync(_context.Response.ContentType, _context.Request.Path.Value,
+ buffer.ToArray(), _baseStream);
+ }
+ else
+ {
+ _agent.Logger.Log(Level.Finest, "ResponseStreamWrapper: Not an HTML response so no attempt to inject RUM.");
+ if (_baseStream != null)
+ await _baseStream.WriteAsync(buffer, cancellationToken);
+ }
}
- protected override void Dispose(bool disposing)
+ public override async ValueTask DisposeAsync()
{
- _baseStream = null;
+ // TODO: Debugging only
+ _agent.CurrentTransaction.LogFinest($"ResponseStreamWrapper.DisposeAsync starting ");
_context = null;
- base.Dispose(disposing);
+ await _baseStream.DisposeAsync();
+ _baseStream = null;
+
+ // TODO: Debugging only
+ _agent.CurrentTransaction.LogFinest($"ResponseStreamWrapper.DisposeAsync complete ");
}
public override bool CanRead { get; }
@@ -88,5 +116,47 @@ protected override void Dispose(bool disposing)
public override bool CanWrite { get; }
public override long Length { get; }
public override long Position { get; set; }
+
+ private bool? _isHtmlResponse = null;
+ private bool IsHtmlResponse(bool forceReCheck = false)
+ {
+ if (!forceReCheck && _isHtmlResponse != null)
+ return _isHtmlResponse.Value;
+
+ // we need to check if the active request is still valid
+ // this can fail if we're in the middle of an error response
+ // or url rewrite in which case we can't intercept
+ if (_context?.Response == null)
+ return false;
+
+ // Requirements for script injection:
+ // * has to have result body
+ // * 200 or 500 response
+ // * text/html response
+ // * UTF-8 formatted (explicit or no charset)
+
+ _isHtmlResponse =
+ _context.Response?.Body != null &&
+ (_context.Response.StatusCode == 200 || _context.Response.StatusCode == 500) &&
+ _context.Response.ContentType != null &&
+ _context.Response.ContentType.Contains("text/html", StringComparison.OrdinalIgnoreCase) &&
+ (_context.Response.ContentType.Contains("utf-8", StringComparison.OrdinalIgnoreCase) ||
+ !_context.Response.ContentType.Contains("charset=", StringComparison.OrdinalIgnoreCase));
+
+ if (!_isHtmlResponse.Value)
+ return false;
+
+ // Make sure we force dynamic content type since we're
+ // rewriting the content - static content will set the header explicitly
+ // and fail when it doesn't matchif (_isHtmlResponse.Value)
+ if (!_isContentLengthSet && _context.Response.ContentLength != null)
+ {
+ _context.Response.Headers.ContentLength = null;
+ _isContentLengthSet = true;
+ }
+
+ return _isHtmlResponse.Value;
+ }
+
}
}
diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/WrapPipelineMiddleware.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/WrapPipelineMiddleware.cs
index 89bf8353d3..1da7d2c001 100644
--- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/WrapPipelineMiddleware.cs
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AspNetCore6Plus/WrapPipelineMiddleware.cs
@@ -148,7 +148,7 @@ private ISegment SetupSegment(ITransaction transaction, HttpContext context)
{
// Seems like it would be cool to not require all of this for a segment???
var method = new Method(typeof(WrapPipelineMiddleware), nameof(Invoke), nameof(context));
- var methodCall = new MethodCall(method, this, new object[] { context }, false);
+ var methodCall = new MethodCall(method, this, new object[] { context }, true);
var segment = transaction.StartTransactionSegment(methodCall, "Middleware Pipeline");
return segment;
diff --git a/tests/Agent/IntegrationTests/Applications/BasicMvcApplication/BasicMvcApplication.csproj b/tests/Agent/IntegrationTests/Applications/BasicMvcApplication/BasicMvcApplication.csproj
index a97512bfcb..8fd1e2fed2 100644
--- a/tests/Agent/IntegrationTests/Applications/BasicMvcApplication/BasicMvcApplication.csproj
+++ b/tests/Agent/IntegrationTests/Applications/BasicMvcApplication/BasicMvcApplication.csproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -165,4 +165,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Agent/IntegrationTests/Applications/ConsoleAsyncApplication/ConsoleAsyncApplication.csproj b/tests/Agent/IntegrationTests/Applications/ConsoleAsyncApplication/ConsoleAsyncApplication.csproj
index 476817835b..a3c857358a 100644
--- a/tests/Agent/IntegrationTests/Applications/ConsoleAsyncApplication/ConsoleAsyncApplication.csproj
+++ b/tests/Agent/IntegrationTests/Applications/ConsoleAsyncApplication/ConsoleAsyncApplication.csproj
@@ -1,4 +1,4 @@
-
+
@@ -62,4 +62,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Agent/IntegrationTests/Applications/MvcAsyncApplication/MvcAsyncApplication.csproj b/tests/Agent/IntegrationTests/Applications/MvcAsyncApplication/MvcAsyncApplication.csproj
index 2583736ff7..282bc40a3f 100644
--- a/tests/Agent/IntegrationTests/Applications/MvcAsyncApplication/MvcAsyncApplication.csproj
+++ b/tests/Agent/IntegrationTests/Applications/MvcAsyncApplication/MvcAsyncApplication.csproj
@@ -1,4 +1,4 @@
-
+
@@ -172,4 +172,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Agent/IntegrationTests/Applications/OwinRemotingClient/OwinRemotingClient.csproj b/tests/Agent/IntegrationTests/Applications/OwinRemotingClient/OwinRemotingClient.csproj
index e8974e846f..5413526c8d 100644
--- a/tests/Agent/IntegrationTests/Applications/OwinRemotingClient/OwinRemotingClient.csproj
+++ b/tests/Agent/IntegrationTests/Applications/OwinRemotingClient/OwinRemotingClient.csproj
@@ -1,4 +1,4 @@
-
+
@@ -106,4 +106,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Agent/IntegrationTests/Applications/WebApiAsyncApplication/WebApiAsyncApplication.csproj b/tests/Agent/IntegrationTests/Applications/WebApiAsyncApplication/WebApiAsyncApplication.csproj
index 86be265dd5..6961ee4a01 100644
--- a/tests/Agent/IntegrationTests/Applications/WebApiAsyncApplication/WebApiAsyncApplication.csproj
+++ b/tests/Agent/IntegrationTests/Applications/WebApiAsyncApplication/WebApiAsyncApplication.csproj
@@ -1,4 +1,4 @@
-
+
@@ -175,4 +175,4 @@
-
+
\ No newline at end of file