Skip to content

Commit

Permalink
feat: added traceid replacement in body of html files
Browse files Browse the repository at this point in the history
  • Loading branch information
pksorensen committed May 12, 2024
1 parent 3fe2ce2 commit 9d2b8df
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
node-version: '20'

- name: Add plugin for conventional commits
run: npm install conventional-changelog-conventionalcommits
run: npm install conventional-changelog-conventionalcommits@7.0.2
working-directory: ./.github/workflows

- name: Add plugin for executing bash commands
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
<None Include="..\..\README.md" Link="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="OpenTelemetry.Api" Version="1.8.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>
Expand Down
187 changes: 183 additions & 4 deletions src/EAVFW.Extensions.Infrastructure/Middlewares/NextJSMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,167 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace EAVFW.Extensions.Infrastructure
{

public class NextJSDocumentLoadPropagator : TextMapPropagator
{
public override ISet<string> Fields => new HashSet<string>();


public override void Inject<T>(PropagationContext context, T carrier, Action<T, string, string> setter)
{

}

public override PropagationContext Extract<T>(PropagationContext context, T carrier, Func<T, string, IEnumerable<string>> getter)
{
if (context.ActivityContext.IsValid())
{
// If a valid context has already been extracted, perform a noop.
return context;
}

if (carrier is HttpRequest request)
{
if (request.Path.StartsWithSegments("/_next", out var id))
{
request.Cookies.TryGetValue("traceparent", out var value);
if (ActivityContext.TryParse(value, null, out var tid))
{
return new PropagationContext(tid, context.Baggage);
}
}



}



return context;

}
}

public class CustomStream : Stream
{
private readonly Stream _stream;
private readonly Encoding _encoding;
private readonly string _pattern;
private readonly string _replacement;
private readonly Queue<char> _lastChars;
// private const string ReplaceTarget = "__REPLACE_TRACEID__";
// private const string Replacement = "HELLO WORLD";

public CustomStream(Stream stream, Encoding encoding, string pattern, string replacement)
{
_stream = stream;
_encoding = encoding;
_pattern = pattern;
_replacement = replacement;
_lastChars = new Queue<char>(pattern.Length);
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var str = _encoding.GetString(buffer, offset, count);



await _stream.WriteAsync(buffer, offset, count, cancellationToken);
}

public override void Write(byte[] buffer, int offset, int count)
{
var str = _encoding.GetString(buffer, offset, count);
foreach (var ch in str)
{
_lastChars.Enqueue(ch);
if (_lastChars.Count > _pattern.Length)
{
WriteChar(_lastChars.Dequeue());
}

if (_lastChars.Count == _pattern.Length && _lastChars.SequenceEqual(_pattern))
{
_lastChars.Clear();
var bytes = _encoding.GetBytes(_replacement);
_stream.Write(bytes, 0, bytes.Length);
}
}
}

private void WriteChar(char ch)
{
var bytes = _encoding.GetBytes(new[] { ch });
_stream.Write(bytes, 0, bytes.Length);
}
private async Task WriteCharAsync(char ch)
{
var bytes = _encoding.GetBytes(new[] { ch });
await _stream.WriteAsync(bytes, 0, bytes.Length);
}


// Other Stream methods should be implemented as well, here are some of them:

public override bool CanRead => _stream.CanRead;

public override bool CanSeek => _stream.CanSeek;

public override bool CanWrite => _stream.CanWrite;

public override long Length => _stream.Length;

public override long Position
{
get => _stream.Position;
set => _stream.Position = value;
}

public override void Flush()
{

while (_lastChars.Count > 0)
{
WriteChar(_lastChars.Dequeue());
}
_stream.Flush();

}

public override async Task FlushAsync(CancellationToken cancellationToken)
{

while (_lastChars.Count > 0)
{
await WriteCharAsync(_lastChars.Dequeue());
}
await _stream.FlushAsync();

}

public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin);

public override void SetLength(long value) => _stream.SetLength(value);

public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count);
}

public class NextJSMiddleware
{
private readonly RequestDelegate _next;
Expand Down Expand Up @@ -49,12 +198,42 @@ public async Task Invoke(HttpContext httpContext)
{

var matched = _routes.FirstOrDefault(k => k.Value.IsMatch(httpContext.Request.Path));
if (!matched.Equals(default(KeyValuePair<string, Regex>)))
if (httpContext.Request.Path == "/" || !matched.Equals(default(KeyValuePair<string, Regex>)))
{
var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;

_logger.LogInformation("Route matched with NextJS Route: " + matched.Key);
httpContext.Request.Path = $"{matched.Key}";
httpContext.Request.Path = $"{matched.Key ?? "/index.html"}";

httpContext.Response.Cookies.Append("traceparent", traceId);

// httpContext.Response.Body = new CustomStream(httpContext.Response.Body, Encoding.UTF8, "__REPLACE_TRACEID__", traceId);


var originalStream = httpContext.Response.Body;
var bufferStream = new MemoryStream();
httpContext.Response.Body = bufferStream;
await _next(httpContext);

bufferStream.Seek(0, SeekOrigin.Begin);

var reader = new StreamReader(bufferStream);
var response = await reader.ReadToEndAsync();


response = response.Replace("__REPLACE_TRACEID__", traceId);

// The response string is modified here

var writer = new StreamWriter(originalStream);
await writer.WriteAsync(response);
await writer.FlushAsync();

}
else
{
await _next(httpContext);
}
await _next(httpContext);
}

}
Expand Down

0 comments on commit 9d2b8df

Please sign in to comment.