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

Commit 8492eda

Browse files
it's nice to have options
1 parent f0a39c9 commit 8492eda

6 files changed

Lines changed: 182 additions & 27 deletions

File tree

src/SqlStreamStore.HAL.Tests/OnlyReadsAreHandledTests.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace SqlStreamStore.HAL.Tests
77
using System.Net.Http;
88
using System.Threading.Tasks;
99
using Microsoft.Owin;
10+
using Shouldly;
1011
using Xunit;
1112
using AppFunc = System.Func<
1213
System.Collections.Generic.IDictionary<string, object>,
@@ -24,7 +25,6 @@ public class OnlyReadsAreHandledTests : IDisposable
2425
private static readonly HttpMethod[] s_methods =
2526
{
2627
HttpMethod.Delete,
27-
HttpMethod.Options,
2828
HttpMethod.Post,
2929
HttpMethod.Put,
3030
HttpMethod.Trace,
@@ -59,7 +59,7 @@ public async Task non_supported_method_on_stream(HttpMethod method, string path)
5959
{
6060
var response = await fixture.HttpClient.SendAsync(new HttpRequestMessage(method, path));
6161

62-
Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
62+
response.StatusCode.ShouldBe(HttpStatusCode.MethodNotAllowed);
6363
}
6464
}
6565

@@ -77,11 +77,10 @@ public async Task non_supported_method_on_all_stream(HttpMethod method, string p
7777
{
7878
var response = await fixture.HttpClient.SendAsync(new HttpRequestMessage(method, path));
7979

80-
Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
80+
response.StatusCode.ShouldBe(HttpStatusCode.MethodNotAllowed);
8181
}
8282
}
8383

84-
8584
public void Dispose() => _streamStore.Dispose();
8685
}
8786
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
namespace SqlStreamStore.HAL.Tests
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net;
7+
using System.Net.Http;
8+
using System.Threading.Tasks;
9+
using Shouldly;
10+
using Xunit;
11+
12+
public class OptionsTests : IDisposable
13+
{
14+
private readonly SqlStreamStoreHalMiddlewareFixture _fixture;
15+
16+
public OptionsTests()
17+
{
18+
_fixture = new SqlStreamStoreHalMiddlewareFixture();
19+
}
20+
21+
public void Dispose() => _fixture.Dispose();
22+
23+
public static IEnumerable<object[]> OptionsAllowedMethodCases()
24+
{
25+
yield return new object[]
26+
{
27+
"/stream",
28+
new[] { HttpMethod.Get, HttpMethod.Head, HttpMethod.Options }
29+
};
30+
31+
yield return new object[]
32+
{
33+
"/stream/123",
34+
new[] { HttpMethod.Get, HttpMethod.Head, HttpMethod.Options }
35+
};
36+
37+
yield return new object[]
38+
{
39+
"/streams/a-stream",
40+
new[] { HttpMethod.Get, HttpMethod.Head, HttpMethod.Options, HttpMethod.Delete, HttpMethod.Post }
41+
};
42+
43+
yield return new object[]
44+
{
45+
"/streams/a-stream/0",
46+
new[] { HttpMethod.Get, HttpMethod.Head, HttpMethod.Options }
47+
};
48+
}
49+
50+
[Theory, MemberData(nameof(OptionsAllowedMethodCases))]
51+
public async Task options_returns_the_correct_cors_headers(string requestUri, HttpMethod[] allowedMethods)
52+
{
53+
using(var response = await _fixture.HttpClient.SendAsync(
54+
new HttpRequestMessage(HttpMethod.Options, requestUri)))
55+
{
56+
response.StatusCode.ShouldBe(HttpStatusCode.OK);
57+
response.Headers.GetValues("Access-Control-Allow-Headers")
58+
.ShouldBe(new[] { "Content-Type", "X-Requested-With", "Authorization" }, true);
59+
response.Headers.GetValues("Access-Control-Allow-Origin")
60+
.ShouldBe(new[] { "*" }, true);
61+
response.Headers.GetValues("Access-Control-Allow-Methods")
62+
.ShouldBe(allowedMethods.Select(_ => _.Method), true);
63+
}
64+
}
65+
}
66+
}

src/SqlStreamStore.HAL/OwinContextExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
namespace SqlStreamStore.HAL
22
{
33
using System.IO;
4+
using System.Linq;
5+
using System.Net.Http;
46
using System.Threading.Tasks;
57
using Microsoft.Owin;
68
using Newtonsoft.Json;
@@ -36,6 +38,23 @@ public static async Task WriteHalResponse(this IOwinContext context, Response re
3638
}
3739
}
3840

41+
public static void SetStandardCorsHeaders(this IOwinContext context, params HttpMethod[] allowedMethods)
42+
{
43+
if(allowedMethods?.Length > 0)
44+
{
45+
context.Response.Headers.AppendValues("Access-Control-Allow-Methods",
46+
allowedMethods.Select(_ => _.Method).ToArray());
47+
}
48+
49+
context.Response.Headers.AppendValues(
50+
"Access-Control-Allow-Headers",
51+
"Content-Type",
52+
"X-Requested-With",
53+
"Authorization");
54+
55+
context.Response.Headers.AppendValues("Access-Control-Allow-Origin", "*");
56+
}
57+
3958
public static bool IsGetOrHead(this IOwinContext context)
4059
=> context.Request.Method == "GET" || context.Request.Method == "HEAD";
4160

@@ -45,5 +64,8 @@ public static bool IsPost(this IOwinContext context)
4564
public static bool IsDelete(this IOwinContext context)
4665
=> context.Request.Method == "DELETE";
4766

67+
public static bool IsOptions(this IOwinContext context)
68+
=> context.Request.Method == "OPTIONS";
69+
4870
}
4971
}

src/SqlStreamStore.HAL/ReadAllStreamMiddleware.cs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace SqlStreamStore.HAL
22
{
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
35
using Microsoft.Owin;
46
using Microsoft.Owin.Builder;
57
using Owin;
@@ -17,7 +19,9 @@ public static MidFunc UseStreamStore(IReadonlyStreamStore streamStore)
1719

1820
var builder = new AppBuilder()
1921
.MapWhen(IsStream, inner => inner.Use(GetStream(allStream)))
20-
.MapWhen(IsStreamMessage, inner => inner.Use(GetStreamMessage(allStream)));
22+
.MapWhen(IsStreamOptions, inner => inner.Use(GetStreamOptions))
23+
.MapWhen(IsStreamMessage, inner => inner.Use(GetStreamMessage(allStream)))
24+
.MapWhen(IsStreamMessageOptions, inner => inner.Use(GetStreamMessageOptions));
2125

2226
return next =>
2327
{
@@ -27,11 +31,23 @@ public static MidFunc UseStreamStore(IReadonlyStreamStore streamStore)
2731
};
2832
}
2933

34+
private static bool IsStream(PathString requestPath)
35+
=> !requestPath.HasValue;
36+
3037
private static bool IsStream(IOwinContext context)
31-
=> context.IsGetOrHead() && !context.Request.Path.HasValue;
38+
=> context.IsGetOrHead() && IsStream(context.Request.Path);
39+
40+
private static bool IsStreamOptions(IOwinContext context)
41+
=> context.IsOptions() && IsStream(context.Request.Path);
42+
43+
private static bool IsStreamMessage(PathString requestPath)
44+
=> long.TryParse(requestPath.Value?.Remove(0, 1), out var _);
3245

3346
private static bool IsStreamMessage(IOwinContext context)
34-
=> context.IsGetOrHead() && long.TryParse(context.Request.Path.Value?.Remove(0, 1), out var _);
47+
=> context.IsGetOrHead() && IsStreamMessage(context.Request.Path);
48+
49+
private static bool IsStreamMessageOptions(IOwinContext context)
50+
=> context.IsOptions() && IsStreamMessage(context.Request.Path);
3551

3652
private static MidFunc GetStream(AllStreamResource allStream) => next => async env =>
3753
{
@@ -46,6 +62,18 @@ private static MidFunc GetStream(AllStreamResource allStream) => next => async e
4662
await context.WriteHalResponse(response);
4763
}
4864
};
65+
66+
private static MidFunc GetStreamOptions => next => env =>
67+
{
68+
var context = new OwinContext(env);
69+
70+
context.SetStandardCorsHeaders(
71+
HttpMethod.Get,
72+
HttpMethod.Head,
73+
HttpMethod.Options);
74+
75+
return Task.CompletedTask;
76+
};
4977

5078
private static MidFunc GetStreamMessage(AllStreamResource allStream) => next => async env =>
5179
{
@@ -60,5 +88,18 @@ private static MidFunc GetStreamMessage(AllStreamResource allStream) => next =>
6088
await context.WriteHalResponse(response);
6189
}
6290
};
91+
92+
private static MidFunc GetStreamMessageOptions => next => env =>
93+
{
94+
var context = new OwinContext(env);
95+
96+
context.SetStandardCorsHeaders(
97+
HttpMethod.Get,
98+
HttpMethod.Head,
99+
HttpMethod.Options);
100+
101+
return Task.CompletedTask;
102+
};
103+
63104
}
64105
}

src/SqlStreamStore.HAL/ReadStreamMiddleware.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace SqlStreamStore.HAL
22
{
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
35
using Microsoft.Owin;
46
using Microsoft.Owin.Builder;
57
using Owin;
@@ -17,7 +19,9 @@ public static MidFunc UseStreamStore(IStreamStore streamStore)
1719

1820
var builder = new AppBuilder()
1921
.MapWhen(IsStreamMessage, inner => inner.Use(GetStreamMessage(streams)))
20-
.MapWhen(IsStream, inner => inner.Use(GetStream(streams)));
22+
.MapWhen(IsStreamMessageOptions, inner => inner.Use(GetStreamMessageOptions))
23+
.MapWhen(IsStream, inner => inner.Use(GetStream(streams)))
24+
.MapWhen(IsStreamOptions, inner => inner.Use(GetStreamOptions));
2125

2226
return next =>
2327
{
@@ -26,12 +30,25 @@ public static MidFunc UseStreamStore(IStreamStore streamStore)
2630
return builder.Build();
2731
};
2832
}
33+
34+
35+
private static bool IsStream(PathString requestPath)
36+
=> requestPath.Value?.Length > 1;
2937

3038
private static bool IsStream(IOwinContext context)
31-
=> context.IsGetOrHead() && context.Request.Path.Value?.Length > 1;
39+
=> context.IsGetOrHead() && IsStream(context.Request.Path);
40+
41+
private static bool IsStreamOptions(IOwinContext context)
42+
=> context.IsOptions() && IsStream(context.Request.Path);
43+
44+
private static bool IsStreamMessage(PathString requestPath)
45+
=> requestPath.Value?.Split('/')?.Length == 3;
3246

3347
private static bool IsStreamMessage(IOwinContext context)
34-
=> context.IsGetOrHead() && context.Request.Path.Value?.Split('/')?.Length == 3;
48+
=> context.IsGetOrHead() && IsStreamMessage(context.Request.Path);
49+
50+
private static bool IsStreamMessageOptions(IOwinContext context)
51+
=> context.IsOptions() && IsStreamMessage(context.Request.Path);
3552

3653
private static MidFunc GetStream(StreamResource stream) => next => async env =>
3754
{
@@ -47,6 +64,20 @@ private static MidFunc GetStream(StreamResource stream) => next => async env =>
4764
}
4865
};
4966

67+
private static MidFunc GetStreamOptions => next => env =>
68+
{
69+
var context = new OwinContext(env);
70+
71+
context.SetStandardCorsHeaders(
72+
HttpMethod.Get,
73+
HttpMethod.Head,
74+
HttpMethod.Options,
75+
HttpMethod.Post,
76+
HttpMethod.Delete);
77+
78+
return Task.CompletedTask;
79+
};
80+
5081
private static MidFunc GetStreamMessage(StreamResource stream) => next => async env =>
5182
{
5283
var context = new OwinContext(env);
@@ -60,5 +91,18 @@ private static MidFunc GetStreamMessage(StreamResource stream) => next => async
6091
await context.WriteHalResponse(response);
6192
}
6293
};
94+
95+
private static MidFunc GetStreamMessageOptions => next => env =>
96+
{
97+
var context = new OwinContext(env);
98+
99+
context.SetStandardCorsHeaders(
100+
HttpMethod.Get,
101+
HttpMethod.Head,
102+
HttpMethod.Options);
103+
104+
return Task.CompletedTask;
105+
};
106+
63107
}
64108
}

src/SqlStreamStore.HAL/SqlStreamStoreHalMiddleware.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,6 @@
1616

1717
public static class SqlStreamStoreHalMiddleware
1818
{
19-
private static MidFunc AccessControl => next => env =>
20-
{
21-
var context = new OwinContext(env);
22-
23-
context.Response.OnSendingHeaders(_ =>
24-
{
25-
context.Response.Headers["Access-Control-Allow-Methods"] = "GET, HEAD, OPTIONS, POST, DELETE";
26-
context.Response.Headers["Access-Control-Allow-Headers"]
27-
= "Content-Type, X-Requested-With, Authorization";
28-
context.Response.Headers["Access-Control-Allow-Origin"] = "*";
29-
},
30-
null);
31-
32-
return next(env);
33-
};
34-
3519
private static MidFunc AddReasonPhrase => next => env =>
3620
{
3721
var context = new OwinContext(env);
@@ -107,7 +91,6 @@ public static MidFunc UseSqlStreamStoreHal(IStreamStore streamStore)
10791

10892
var builder = new AppBuilder()
10993
.Use(ExceptionHandlingMiddleware.HandleExceptions)
110-
.Use(AccessControl)
11194
.Use(AddReasonPhrase)
11295
.Use(AcceptOnlyHalJson)
11396
.Use(Index)

0 commit comments

Comments
 (0)