-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: add mermaid in memory http server (#1794)
* initial import of mermaid logic * add logging wrappers and rework http server injection * Update ci.yml * Update ci.yml * Update ci.yml * Update Whipstaff.UnitTests.csproj * Update Whipstaff.UnitTests.csproj * add browser and channel support * Update Whipstaff.UnitTests.csproj * Update PlaywrightRendererTests.cs * refactor http message logic * vs file save issue * reduce pkgs used for mermaid proj * add browser type test * test ToHttpRequestMessage method * add npm restore to csproj * add gzip creation of files * fix files not being copied to wwwroot after npm restore * logic to decompress result back to playwright * add content type lookup * recalc embedded files on clean build
- Loading branch information
Showing
24 changed files
with
3,455 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
wwwroot/lib |
59 changes: 59 additions & 0 deletions
59
src/Whipstaff.Mermaid/HttpServer/EmbeddedGzipFileProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
#if TBC | ||
using System.Reflection; | ||
using Microsoft.Extensions.FileProviders; | ||
|
||
namespace Whipstaff.Mermaid.HttpServer | ||
{ | ||
/// <summary> | ||
/// Wrapper for the <see cref="EmbeddedFileProvider"/> that provides Gzip decompression for embedded resources. | ||
/// </summary> | ||
public class EmbeddedGzipFileProvider : EmbeddedFileProvider | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EmbeddedGzipFileProvider" /> class using the specified | ||
/// assembly with the base namespace defaulting to the assembly name. | ||
/// </summary> | ||
/// <param name="assembly">The assembly that contains the embedded resources.</param> | ||
public EmbeddedGzipFileProvider(Assembly assembly) | ||
: base(assembly) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EmbeddedGzipFileProvider" /> class using the specified | ||
/// assembly and base namespace. | ||
/// </summary> | ||
/// <param name="assembly">The assembly that contains the embedded resources.</param> | ||
/// <param name="baseNamespace">The base namespace that contains the embedded resources.</param> | ||
public EmbeddedGzipFileProvider(Assembly assembly, string? baseNamespace) | ||
: base(assembly, baseNamespace) | ||
{ | ||
} | ||
|
||
/// <summary> | ||
/// Locates a file at the given path. | ||
/// </summary> | ||
/// <param name="subpath">The path that identifies the file. </param> | ||
/// <returns> | ||
/// The file information. Caller must check Exists property. A <see cref="NotFoundFileInfo" /> if the file could | ||
/// not be found. | ||
/// </returns> | ||
// ReSharper disable once IdentifierTypo | ||
public new IFileInfo GetFileInfo(string subpath) | ||
{ | ||
var baseResponse = base.GetFileInfo(subpath); | ||
|
||
if (!baseResponse.Exists) | ||
{ | ||
return baseResponse; | ||
} | ||
|
||
return new EmbeddedResourceGzipFileInfo(baseResponse); | ||
} | ||
} | ||
} | ||
#endif |
70 changes: 70 additions & 0 deletions
70
src/Whipstaff.Mermaid/HttpServer/EmbeddedResourceGzipFileInfo.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
#if TBC | ||
using System; | ||
using System.IO; | ||
using System.IO.Compression; | ||
using System.Reflection; | ||
using Microsoft.Extensions.FileProviders; | ||
|
||
namespace Whipstaff.Mermaid.HttpServer | ||
{ | ||
/// <summary> | ||
/// Represents a file in the embedded resources that is compressed with Gzip. | ||
/// </summary> | ||
public sealed class EmbeddedResourceGzipFileInfo : IFileInfo | ||
{ | ||
private readonly IFileInfo _baseResponse; | ||
private long? _length; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="EmbeddedResourceGzipFileInfo"/> class. | ||
/// </summary> | ||
/// <param name="baseResponse">The compressed response to handle.</param> | ||
public EmbeddedResourceGzipFileInfo(IFileInfo baseResponse) | ||
{ | ||
_baseResponse = baseResponse; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public bool Exists => _baseResponse.Exists; | ||
|
||
/// <inheritdoc /> | ||
public bool IsDirectory => _baseResponse.IsDirectory; | ||
|
||
/// <inheritdoc /> | ||
public DateTimeOffset LastModified => _baseResponse.LastModified; | ||
|
||
/// <inheritdoc /> | ||
public long Length | ||
{ | ||
get | ||
{ | ||
if (!_length.HasValue) | ||
{ | ||
using var stream = CreateReadStream(); | ||
_length = stream.Length; | ||
} | ||
|
||
return _length.Value; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
public string Name => _baseResponse.Name; | ||
|
||
/// <inheritdoc /> | ||
public string? PhysicalPath => _baseResponse.PhysicalPath; | ||
|
||
/// <inheritdoc /> | ||
public Stream CreateReadStream() | ||
{ | ||
var stream = _baseResponse.CreateReadStream(); | ||
var gZipStream = new GZipStream(stream, CompressionMode.Decompress); | ||
return gZipStream; | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.TestHost; | ||
|
||
namespace Whipstaff.Mermaid.HttpServer | ||
{ | ||
/// <summary> | ||
/// In memory HTTP server for Mermaid. | ||
/// </summary> | ||
public class MermaidHttpServer : TestServer | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MermaidHttpServer"/> class. | ||
/// </summary> | ||
/// <param name="builder">Web host builder used to create a server instance.</param> | ||
public MermaidHttpServer(IWebHostBuilder builder) | ||
: base(builder) | ||
{ | ||
} | ||
} | ||
} |
177 changes: 177 additions & 0 deletions
177
src/Whipstaff.Mermaid/HttpServer/MermaidHttpServerFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Text.Encodings.Web; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Features; | ||
using Microsoft.AspNetCore.StaticFiles; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.FileProviders; | ||
using Microsoft.Extensions.Logging; | ||
|
||
namespace Whipstaff.Mermaid.HttpServer | ||
{ | ||
/// <summary> | ||
/// Factory for the Mermaid in memory HTTP server. | ||
/// </summary> | ||
public static class MermaidHttpServerFactory | ||
{ | ||
/// <summary> | ||
/// Gets the In Memory Test Server. | ||
/// </summary> | ||
/// <param name="loggerFactory">Logging Factory.</param> | ||
/// <returns>In memory HTTP server instance.</returns> | ||
public static MermaidHttpServer GetTestServer(ILoggerFactory loggerFactory) | ||
{ | ||
var builder = GetWebHostBuilder(loggerFactory); | ||
var testServer = new MermaidHttpServer(builder); | ||
return testServer; | ||
} | ||
|
||
private static IWebHostBuilder GetWebHostBuilder(ILoggerFactory loggerFactory) | ||
{ | ||
var embeddedProvider = new EmbeddedFileProvider( | ||
typeof(MermaidHttpServerFactory).Assembly, | ||
typeof(MermaidHttpServerFactory).Namespace + ".wwwroot"); | ||
|
||
var builder = new WebHostBuilder() | ||
.ConfigureLogging(loggingBuilder => ConfigureLogging( | ||
loggingBuilder, | ||
loggerFactory)) | ||
.ConfigureServices((_, serviceCollection) => ConfigureServices( | ||
serviceCollection, | ||
embeddedProvider)) | ||
.Configure((_, applicationBuilder) => ConfigureApp( | ||
applicationBuilder, | ||
embeddedProvider)); | ||
|
||
return builder; | ||
} | ||
|
||
private static void ConfigureApp(IApplicationBuilder applicationBuilder, EmbeddedFileProvider embeddedFileProvider) | ||
{ | ||
_ = applicationBuilder.Use(async (context, next) => | ||
{ | ||
var url = context.Request.Path.Value; | ||
|
||
if (url != null | ||
&& url.Contains("/lib/mermaid", StringComparison.OrdinalIgnoreCase) | ||
&& !url.EndsWith(".gz", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
// rewrite and continue processing | ||
context.Request.Path += ".gz"; | ||
} | ||
|
||
await next(); | ||
}); | ||
|
||
_ = applicationBuilder.UseStaticFiles(new StaticFileOptions | ||
{ | ||
FileProvider = embeddedFileProvider, | ||
OnPrepareResponseAsync = OnPrepareResponseAsync, | ||
HttpsCompression = HttpsCompressionMode.DoNotCompress | ||
}); | ||
|
||
_ = applicationBuilder.MapWhen(IsMermaidPost, AppConfiguration); | ||
} | ||
|
||
private static Task OnPrepareResponseAsync(StaticFileResponseContext arg) | ||
{ | ||
if (!arg.File.Name.EndsWith(".gz", StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
|
||
var filename = arg.File.Name[..^3]; | ||
|
||
var headers = arg.Context.Response.Headers; | ||
headers["Content-Encoding"] = "gzip"; | ||
if (new FileExtensionContentTypeProvider().TryGetContentType(filename, out var contentType)) | ||
{ | ||
headers["Content-Type"] = contentType; | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
private static void AppConfiguration(IApplicationBuilder applicationBuilder) | ||
{ | ||
applicationBuilder.Run(Handler); | ||
} | ||
|
||
private static async Task Handler(HttpContext context) | ||
{ | ||
var request = context.Request; | ||
var response = context.Response; | ||
|
||
var diagramFormStringValues = request.Form["diagram"]; | ||
if (diagramFormStringValues.Count < 1) | ||
{ | ||
await WriteNoDiagramResponse(response).ConfigureAwait(false); | ||
return; | ||
} | ||
|
||
var diagram = diagramFormStringValues[0]; | ||
if (string.IsNullOrWhiteSpace(diagram)) | ||
{ | ||
await WriteNoDiagramResponse(response).ConfigureAwait(false); | ||
return; | ||
} | ||
|
||
response.StatusCode = 200; | ||
response.ContentType = "text/html"; | ||
|
||
var sb = new System.Text.StringBuilder(); | ||
_ = sb.AppendLine(@"<!DOCTYPE html>"); | ||
_ = sb.AppendLine(@"<html lang=""en"" xmlns=""http://www.w3.org/1999/xhtml"">"); | ||
_ = sb.AppendLine(@"<head>"); | ||
_ = sb.AppendLine(@" <meta charset=""utf-8"" />"); | ||
_ = sb.AppendLine(@" <title>MermaidJS factory</title>"); | ||
_ = sb.AppendLine(@"</head>"); | ||
_ = sb.AppendLine(@"<body>"); | ||
_ = sb.AppendLine(@" <pre class=""mermaid"" name=""mermaid-element"" id=""mermaid-element"">"); | ||
_ = sb.AppendLine(HtmlEncoder.Default.Encode(diagram)); | ||
_ = sb.AppendLine(@" </pre>"); | ||
_ = sb.AppendLine(@" <script type=""module"">"); | ||
_ = sb.AppendLine(@" import mermaid from '/lib/mermaid/mermaid.esm.min.mjs';"); | ||
_ = sb.AppendLine(@" mermaid.initialize({ startOnLoad: false });"); | ||
_ = sb.AppendLine(@" await mermaid.run();"); | ||
_ = sb.AppendLine(@" </script>"); | ||
_ = sb.AppendLine(@"</body>"); | ||
_ = sb.AppendLine(@"</html>"); | ||
|
||
await response.WriteAsync(sb.ToString()) | ||
.ConfigureAwait(false); | ||
} | ||
|
||
private static async Task WriteNoDiagramResponse(HttpResponse response) | ||
{ | ||
response.StatusCode = 400; | ||
response.ContentType = "text/plain"; | ||
await response.WriteAsync("No diagram passed in request").ConfigureAwait(false); | ||
} | ||
|
||
private static bool IsMermaidPost(HttpContext arg) | ||
{ | ||
var request = arg.Request; | ||
|
||
return request.Method.Equals("POST", StringComparison.Ordinal) && | ||
request.Path.Equals("/index.html", StringComparison.OrdinalIgnoreCase); | ||
} | ||
|
||
private static void ConfigureServices(IServiceCollection serviceCollection, EmbeddedFileProvider embeddedFileProvider) | ||
{ | ||
_ = serviceCollection.AddSingleton<IFileProvider>(embeddedFileProvider); | ||
} | ||
|
||
private static void ConfigureLogging(ILoggingBuilder loggingBuilder, ILoggerFactory loggerFactory) | ||
{ | ||
_ = loggingBuilder.Services.AddSingleton(loggerFactory); | ||
} | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/Whipstaff.Mermaid/Playwright/GetDiagramResponseModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
namespace Whipstaff.Mermaid.Playwright | ||
{ | ||
/// <summary> | ||
/// Response Model for <see cref="PlaywrightRenderer.GetDiagram"/>. | ||
/// </summary> | ||
/// <param name="Svg">The diagram in SVG format.</param> | ||
/// <param name="Png">PNG image of the diagram as a byte array.</param> | ||
#pragma warning disable CA1819 // Properties should not return arrays | ||
public sealed record GetDiagramResponseModel(string Svg, byte[] Png) | ||
#pragma warning restore CA1819 // Properties should not return arrays | ||
{ | ||
} | ||
} |
Oops, something went wrong.