Skip to content
This repository was archived by the owner on Sep 3, 2024. It is now read-only.

Commit 43ad2fc

Browse files
handling special case where request uri is not rooted at the pathbase
1 parent 67571e7 commit 43ad2fc

File tree

7 files changed

+171
-13
lines changed

7 files changed

+171
-13
lines changed

src/SqlStreamStore.Server/Browser/SqlStreamStoreBrowserMiddleware.cs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@ namespace SqlStreamStore.Server.Browser
1212
internal static class SqlStreamStoreBrowserMiddleware
1313
{
1414
public static IApplicationBuilder UseSqlStreamStoreBrowser(
15-
this IApplicationBuilder builder)
15+
this IApplicationBuilder builder,
16+
Type rootType = default)
1617
{
18+
rootType = rootType ?? typeof(SqlStreamStoreBrowserMiddleware);
1719
var sqlStreamStoreBrowserFileProvider = new EmbeddedFileProvider(
18-
typeof(SqlStreamStoreBrowserMiddleware).Assembly,
19-
typeof(SqlStreamStoreBrowserMiddleware).Namespace);
20+
rootType.Assembly,
21+
rootType.Namespace);
2022

21-
var staticFiles = typeof(SqlStreamStoreBrowserMiddleware).Assembly.GetManifestResourceNames()
22-
.Where(name => name.StartsWith(typeof(SqlStreamStoreBrowserMiddleware).Namespace));
23+
var staticFiles = rootType.Assembly.GetManifestResourceNames()
24+
.Where(name => name.StartsWith(rootType.Namespace));
2325

2426
Log.Debug(
25-
"The following embedded resources were found and will be served as static content: {staticFiles}",
27+
"The following embedded resources were found and will be served as static content: {staticFiles}",
2628
string.Join(", ", staticFiles));
2729

2830
return builder.Use(IndexPage).UseStaticFiles(new StaticFileOptions
@@ -32,11 +34,12 @@ public static IApplicationBuilder UseSqlStreamStoreBrowser(
3234

3335
Task IndexPage(HttpContext context, Func<Task> next)
3436
{
35-
if(GetAcceptHeaders(context.Request).Contains("text/html"))
37+
if (!GetAcceptHeaders(context.Request).Contains("text/html"))
3638
{
37-
context.Request.Path = new PathString("/index.html");
39+
return TryRedirectStaticContent(context, next);
3840
}
3941

42+
context.Request.Path = new PathString("/index.html");
4043
return next();
4144
}
4245
}
@@ -47,5 +50,36 @@ private static string[] GetAcceptHeaders(HttpRequest contextRequest)
4750
value => MediaTypeWithQualityHeaderValue.TryParse(value, out var header)
4851
? header.MediaType
4952
: null);
53+
54+
private static Task TryRedirectStaticContent(HttpContext context, Func<Task> next)
55+
{
56+
if (!context.Request.Path.HasValue)
57+
{
58+
return next();
59+
}
60+
61+
var requestPieces = context.Request.Path.Value.Split('/');
62+
63+
for (var i = 2; i < requestPieces.Length; i++)
64+
{
65+
if (requestPieces[i] != "static")
66+
{
67+
continue;
68+
}
69+
70+
var staticFilePath = new string[requestPieces.Length - i];
71+
for (var j = i; j < requestPieces.Length; j++)
72+
{
73+
staticFilePath[j - i] = requestPieces[j];
74+
}
75+
76+
context.Response.StatusCode = 308;
77+
context.Response.Headers["Location"] =
78+
$"{string.Join("/", Enumerable.Repeat("..", i + 1))}/{string.Join("/", staticFilePath)}";
79+
return Task.CompletedTask;
80+
}
81+
82+
return next();
83+
}
5084
}
5185
}

src/SqlStreamStore.Server/SqlStreamStoreServerStartup.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Microsoft.AspNetCore.Hosting;
55
using Microsoft.AspNetCore.Http;
66
using Microsoft.Extensions.DependencyInjection;
7-
using Serilog;
87
using SqlStreamStore.HAL;
98
using SqlStreamStore.Server.Browser;
109

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Net;
5+
using System.Net.Http;
6+
using System.Net.Http.Headers;
7+
using System.Threading.Tasks;
8+
using Microsoft.AspNetCore.Hosting;
9+
using Microsoft.AspNetCore.TestHost;
10+
using SqlStreamStore.Server.Browser;
11+
using Xunit;
12+
13+
namespace SqlStreamStore.Server.Tests.Browser
14+
{
15+
public class SqlStreamStoreBrowserTests : IDisposable
16+
{
17+
private readonly TestServer _server;
18+
private readonly HttpClient _httpClient;
19+
20+
public SqlStreamStoreBrowserTests()
21+
{
22+
_server = new TestServer(
23+
new WebHostBuilder()
24+
.Configure(app => app.UseSqlStreamStoreBrowser(typeof(SqlStreamStoreBrowserTests))));
25+
26+
_httpClient = new HttpClient(_server.CreateHandler())
27+
{
28+
BaseAddress = new UriBuilder().Uri
29+
};
30+
}
31+
32+
public static IEnumerable<object[]> IndexPageCases()
33+
{
34+
yield return new object[] {"/"};
35+
yield return new object[] {"/stream"};
36+
yield return new object[] {"/streams/a-stream"};
37+
yield return new object[] {"/streams/a-stream/metadata"};
38+
}
39+
40+
[Theory, MemberData(nameof(IndexPageCases))]
41+
public async Task RequestsForHtmlReturnTheIndexPage(string path)
42+
{
43+
using (var response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, path)
44+
{
45+
Headers = {Accept = {new MediaTypeWithQualityHeaderValue("text/html")}}
46+
}))
47+
{
48+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
49+
Assert.Equal(await GetStaticEmbeddedResource("index.html"), await response.Content.ReadAsStringAsync());
50+
}
51+
}
52+
53+
[Fact]
54+
public async Task RequestsForStaticFilesFromRootAreReturned()
55+
{
56+
using (var response = await _httpClient.SendAsync(
57+
new HttpRequestMessage(HttpMethod.Get, "/static/js/ws.js")
58+
{
59+
Headers = {Accept = {new MediaTypeWithQualityHeaderValue("*/*")}}
60+
}))
61+
{
62+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
63+
Assert.Equal(
64+
await GetStaticEmbeddedResource("static.js.ws.js"),
65+
await response.Content.ReadAsStringAsync());
66+
}
67+
}
68+
69+
public static IEnumerable<object[]> StaticContentCases()
70+
{
71+
yield return new object[] {"/stream/", "../../../"};
72+
yield return new object[] {"/streams/a-stream/", "../../../../"};
73+
yield return new object[] {"/streams/a-stream/metadata/", "../../../../../"};
74+
}
75+
76+
[Theory, MemberData(nameof(StaticContentCases))]
77+
public async Task RequestsForStaticAreRedirectedIfNotAtRoot(string path, string parent)
78+
{
79+
using (var response = await _httpClient.SendAsync(
80+
new HttpRequestMessage(HttpMethod.Get, $"{path}static/js/ws.js")
81+
{
82+
Headers = {Accept = {new MediaTypeWithQualityHeaderValue("*/*")}}
83+
}))
84+
{
85+
Assert.Equal(HttpStatusCode.PermanentRedirect, response.StatusCode);
86+
Assert.Equal($"{parent}static/js/ws.js", response.Headers.Location?.ToString());
87+
}
88+
}
89+
90+
private static async Task<string> GetStaticEmbeddedResource(string resource)
91+
{
92+
using (var stream = typeof(SqlStreamStoreBrowserTests)
93+
.Assembly
94+
.GetManifestResourceStream(typeof(SqlStreamStoreBrowserTests), resource))
95+
using (var reader = new StreamReader(stream))
96+
{
97+
return await reader.ReadToEndAsync();
98+
}
99+
}
100+
101+
public void Dispose()
102+
{
103+
_httpClient?.Dispose();
104+
_server?.Dispose();
105+
}
106+
}
107+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Title</title>
6+
</head>
7+
<body>
8+
9+
</body>
10+
</html>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(function () {
2+
})();

tests/SqlStreamStore.Server.Tests/SqlStreamStore.Server.Tests.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@
1313
<PackageReference Include="xunit" Version="2.4.1" />
1414
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
1515
</ItemGroup>
16+
<ItemGroup>
17+
<EmbeddedResource Include="Browser\**" Exclude="Browser\**\*.cs">
18+
<Link>Browser\%(RecursiveDir)%(Filename)%(Extension)</Link>
19+
</EmbeddedResource>
20+
</ItemGroup>
21+
<ItemGroup>
22+
<Content Include="Browser\static\js\ws.js" />
23+
</ItemGroup>
1624
</Project>

tests/SqlStreamStore.Server.Tests/SqlStreamStoreServerStartupTests.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.Hosting;
66
using Microsoft.AspNetCore.TestHost;
7-
using Microsoft.Extensions.DependencyInjection;
87
using SqlStreamStore;
98
using SqlStreamStore.HAL;
109
using SqlStreamStore.Server;
@@ -15,8 +14,7 @@ namespace SQLStreamStore.Server.Tests
1514
public class SqlStreamStoreServerStartupTests : IDisposable
1615
{
1716
private readonly InMemoryStreamStore _streamStore;
18-
private readonly IWebHost _host;
19-
private TestServer _server;
17+
private readonly TestServer _server;
2018
private readonly HttpClient _httpClient;
2119

2220
public SqlStreamStoreServerStartupTests()
@@ -52,7 +50,7 @@ public async Task StartsUp()
5250
public void Dispose()
5351
{
5452
_streamStore?.Dispose();
55-
_host?.Dispose();
53+
_server?.Dispose();
5654
_httpClient?.Dispose();
5755
}
5856
}

0 commit comments

Comments
 (0)