Skip to content

Commit

Permalink
Bug fixes, etc
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr committed Oct 24, 2023
1 parent 7099f4b commit effca3d
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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") },

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ SPDX-License-Identifier: Apache-2.0
<match assemblyName="Microsoft.AspNetCore.Mvc.Core" className="Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker" minVersion="6.0.0.0">
<exactMethodMatcher methodName="InvokeActionMethodAsync" />
</match>
<match assemblyName="Microsoft.AspNetCore.Mvc.Core" className="Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker" minVersion="6.0.0.0">
<exactMethodMatcher methodName="InvokeActionMethodAsync" />
</match>
</tracerFactory>
</instrumentation>
</extension>
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// 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
/// </summary>
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)
Expand All @@ -30,63 +32,131 @@ 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<byte> buffer)
{
_baseStream.Write(buffer);
IsHtmlResponse(forceReCheck: true);
}

public override void WriteByte(byte value)
{
_baseStream.WriteByte(value);
}
public override void Write(ReadOnlySpan<byte> 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<byte> 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; }
public override bool CanSeek { get; }
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;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
Expand Down Expand Up @@ -165,4 +165,4 @@
<Target Name="Deploy" AfterTargets="Build">
<MSBuild Projects="$(ProjectPath)" Targets="WebPublish" Properties="PublishProfile=LocalDeploy" />
</Target>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -62,4 +62,4 @@
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -172,4 +172,4 @@
<Target Name="Deploy" AfterTargets="Build">
<MSBuild Projects="$(ProjectPath)" Targets="WebPublish" Properties="PublishProfile=LocalDeploy" />
</Target>
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -106,4 +106,4 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -175,4 +175,4 @@
<Target Name="Deploy" AfterTargets="Build">
<MSBuild Projects="$(ProjectPath)" Targets="WebPublish" Properties="PublishProfile=LocalDeploy" />
</Target>
</Project>
</Project>

0 comments on commit effca3d

Please sign in to comment.