diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 57681dd..b268719 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,7 +1,7 @@ - 1.0.0-rc1 + 1.0.0-rc2 João P. Bragança https://github.com/damianh/SqlStreamStore.HAL https://github.com/damianh/SqlStreamStore.HAL/blob/master/LICENSE diff --git a/src/SqlStreamStore.HAL.DevServer/DevServerStartup.cs b/src/SqlStreamStore.HAL.DevServer/DevServerStartup.cs index 8789643..0919391 100644 --- a/src/SqlStreamStore.HAL.DevServer/DevServerStartup.cs +++ b/src/SqlStreamStore.HAL.DevServer/DevServerStartup.cs @@ -1,11 +1,15 @@ namespace SqlStreamStore.HAL.DevServer { using System; + using System.Linq; + using System.Net.Http; + using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Primitives; using MidFunc = System.Func< Microsoft.AspNetCore.Http.HttpContext, System.Func, @@ -15,10 +19,12 @@ internal class DevServerStartup : IStartup { private readonly IStreamStore _streamStore; + private readonly HttpClient _httpClient; public DevServerStartup(IStreamStore streamStore) { _streamStore = streamStore; + _httpClient = new HttpClient(); } public IServiceProvider ConfigureServices(IServiceCollection services) => services @@ -27,8 +33,10 @@ public IServiceProvider ConfigureServices(IServiceCollection services) => servic public void Configure(IApplicationBuilder app) => app .UseResponseCompression() + .Use(VaryAccept) .Use(CatchAndDisplayErrors) - .Use(AllowAllOrigins) + .Use(SqlStreamStreamBrowserJavascript) + .Use(SqlStreamStreamBrowserHtml) .UseSqlStreamStoreHal(_streamStore); private static MidFunc CatchAndDisplayErrors => async (context, next) => @@ -43,19 +51,81 @@ public void Configure(IApplicationBuilder app) => app } }; - // don't actually do this in production - private static MidFunc AllowAllOrigins => (context, next) => + private static MidFunc VaryAccept => (context, next) => { - context.Response.OnStarting(_ => - { - var response = (HttpResponse) _; - response.Headers["Access-Control-Allow-Origin"] = "*"; + Task Vary(object state) + { + var response = (HttpResponse)state; + + response.Headers.AppendCommaSeparatedValues("Vary", "Accept"); + + return Task.CompletedTask; + } + + context.Response.OnStarting(Vary, context.Response); + + return next(); + }; - return Task.CompletedTask; - }, - context.Response); + private MidFunc SqlStreamStreamBrowserJavascript => (context, next) => + { + if(context.Request.Path.Value?.EndsWith(".js") ?? false) + { + var segments = context.Request.Path.ToUriComponent().Split('/'); + if(segments.Length > 2) + { + return RedirectToPathBase(context, $"/{segments.Last()}"); + } + return ForwardToClientDevServer( + context, + context.Request.PathBase + context.Request.Path); + } return next(); }; + + private MidFunc SqlStreamStreamBrowserHtml => (context, next) + => GetAcceptHeaders(context.Request) + .Any(header => header == "text/html") + ? ForwardToClientDevServer(context, context.Request.PathBase.ToUriComponent()) + : next(); + + private static string[] GetAcceptHeaders(HttpRequest contextRequest) + => Array.ConvertAll( + contextRequest.Headers.GetCommaSeparatedValues("Accept"), + value => MediaTypeWithQualityHeaderValue.TryParse(value, out var header) + ? header.MediaType + : null); + + private Task RedirectToPathBase(HttpContext context, PathString path) + { + context.Response.Redirect(context.Request.PathBase + path); + + return Task.CompletedTask; + } + + private async Task ForwardToClientDevServer(HttpContext context, PathString path) + { + using(var request = new HttpRequestMessage( + new HttpMethod(context.Request.Method), + new UriBuilder + { + Port = 3000, + Host = "localhost", + Path = path.ToUriComponent(), + Query = context.Request.QueryString.ToUriComponent() + }.Uri)) + using(var response = await _httpClient.SendAsync(request)) + using(var stream = await response.Content.ReadAsStreamAsync()) + { + context.Response.StatusCode = (int) response.StatusCode; + foreach(var header in response.Headers.Concat(response.Content.Headers)) + { + context.Response.Headers.Add(header.Key, new StringValues(header.Value.ToArray())); + } + + await stream.CopyToAsync(context.Response.Body, 8192, context.RequestAborted); + } + } } } \ No newline at end of file diff --git a/src/SqlStreamStore.HAL.Tests/StreamMetadataTests.cs b/src/SqlStreamStore.HAL.Tests/StreamMetadataTests.cs index 05a98c4..fd9a1e0 100644 --- a/src/SqlStreamStore.HAL.Tests/StreamMetadataTests.cs +++ b/src/SqlStreamStore.HAL.Tests/StreamMetadataTests.cs @@ -40,7 +40,8 @@ await _fixture.HttpClient.SendAsync( ((string) resource.State.metadataJson).ShouldBeNull(); resource.ShouldLink(Constants.Relations.Self, "metadata"); - resource.ShouldLink(Constants.Relations.Feed, "../"); + resource.ShouldLink(Constants.Relations.Metadata, "metadata"); + resource.ShouldLink(Constants.Relations.Feed, $"../{StreamId}"); } } @@ -85,7 +86,8 @@ await _fixture.HttpClient.SendAsync( })).ShouldBeTrue(); resource.ShouldLink(Constants.Relations.Self, "metadata"); - resource.ShouldLink(Constants.Relations.Feed, "../"); + resource.ShouldLink(Constants.Relations.Metadata, "metadata"); + resource.ShouldLink(Constants.Relations.Feed, $"../{StreamId}"); } } @@ -126,7 +128,8 @@ public async Task set_metadata() })).ShouldBeTrue(); resource.ShouldLink(Constants.Relations.Self, "metadata"); - resource.ShouldLink(Constants.Relations.Feed, "../"); + resource.ShouldLink(Constants.Relations.Metadata, "metadata"); + resource.ShouldLink(Constants.Relations.Feed, $"../{StreamId}"); } } diff --git a/src/SqlStreamStore.HAL/Constants.cs b/src/SqlStreamStore.HAL/Constants.cs index 0a341a6..db007f6 100644 --- a/src/SqlStreamStore.HAL/Constants.cs +++ b/src/SqlStreamStore.HAL/Constants.cs @@ -31,6 +31,8 @@ public static class Relations public const string Feed = "streamStore:feed"; public const string Message = "streamStore:message"; public const string Metadata = "streamStore:metadata"; + public const string AppendToStream = "streamStore:append"; + public const string Delete = "streamStore:delete"; } public static class Streams @@ -38,18 +40,6 @@ public static class Streams public const string All = "stream"; public const string Metadata = "metadata"; } - - public static IReadOnlyDictionary ReasonPhrases { get; } - = new ReadOnlyDictionary(new Dictionary - { - [200] = "OK", - [201] = "Created", - [307] = "Moved Temporarily", - [400] = "Bad Request", - [404] = "Not Found", - [405] = "Method Not Allowed", - [409] = "Conflict" - }); public static class ReadDirection { diff --git a/src/SqlStreamStore.HAL/ExceptionHandlingMiddleware.cs b/src/SqlStreamStore.HAL/ExceptionHandlingMiddleware.cs index ecbae99..22e391e 100644 --- a/src/SqlStreamStore.HAL/ExceptionHandlingMiddleware.cs +++ b/src/SqlStreamStore.HAL/ExceptionHandlingMiddleware.cs @@ -39,7 +39,8 @@ private static readonly IDictionary> s_exception [typeof(InvalidAppendRequestException)] = ex => new Response(new HALResponse(new { type = ex.GetType().Name, - title = "Bad format." + title = "Bad format.", + detail = ex.Message }), 400), [typeof(Exception)] = s_defaultExceptionHandler }; diff --git a/src/SqlStreamStore.HAL/HttpContextExtensions.cs b/src/SqlStreamStore.HAL/HttpContextExtensions.cs index b7d6123..4ccc9e1 100644 --- a/src/SqlStreamStore.HAL/HttpContextExtensions.cs +++ b/src/SqlStreamStore.HAL/HttpContextExtensions.cs @@ -1,12 +1,15 @@ namespace SqlStreamStore.HAL { + using System; using System.IO; using System.Linq; using System.Net.Http; + using System.Net.Http.Headers; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; + using SqlStreamStore.Streams; internal static class HttpContextExtensions { @@ -76,6 +79,14 @@ public static int GetExpectedVersion(this HttpRequest request) request.Headers[Constants.Headers.ExpectedVersion], out var expectedVersion) ? expectedVersion - : Streams.ExpectedVersion.Any; + : ExpectedVersion.Any; + + public static string[] GetAcceptHeaders(this HttpRequest contextRequest) + => Array.ConvertAll( + contextRequest.Headers + .GetCommaSeparatedValues("Accept"), + value => MediaTypeWithQualityHeaderValue.TryParse(value, out var header) + ? header.MediaType + : null); } } \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/AllStreamResource.cs b/src/SqlStreamStore.HAL/Resources/AllStreamResource.cs index 7211339..e66a70d 100644 --- a/src/SqlStreamStore.HAL/Resources/AllStreamResource.cs +++ b/src/SqlStreamStore.HAL/Resources/AllStreamResource.cs @@ -66,7 +66,9 @@ public async Task GetPage( payload, metadata = message.JsonMetadata }) - .AddLinks(Links.Message.Self(message))))); + .AddLinks( + Links.Message.Self(message), + Links.Message.Feed(message))))); if(operation.FromPositionInclusive == Position.End) { @@ -157,6 +159,10 @@ public static class Message public static Link Self(StreamMessage message) => new Link( Constants.Relations.Self, $"streams/{message.StreamId}/{message.StreamVersion}"); + + public static Link Feed(StreamMessage message) => new Link( + Constants.Relations.Feed, + $"streams/{message.StreamId}"); } } } diff --git a/src/SqlStreamStore.HAL/Resources/AppendStreamOperation.cs b/src/SqlStreamStore.HAL/Resources/AppendStreamOperation.cs index 2d88f11..11cde5b 100644 --- a/src/SqlStreamStore.HAL/Resources/AppendStreamOperation.cs +++ b/src/SqlStreamStore.HAL/Resources/AppendStreamOperation.cs @@ -51,34 +51,44 @@ private AppendStreamOperation(HttpRequest request, JObject body) : this(request, new JArray { body }) { } - private static NewStreamMessage ParseNewStreamMessage(JToken newStreamMessage, int index) + private static NewStreamMessageDto ParseNewStreamMessage(JToken newStreamMessage, int index) { if(!Guid.TryParse(newStreamMessage.Value("messageId"), out var messageId)) { - throw new InvalidAppendRequestException($"'{nameof(messageId)}' at index {index} was improperly formatted."); + throw new InvalidAppendRequestException( + $"'{nameof(messageId)}' at index {index} was improperly formatted."); } + if(messageId == Guid.Empty) { throw new InvalidAppendRequestException($"'{nameof(messageId)}' at index {index} was empty."); } + var type = newStreamMessage.Value("type"); if(type == null) { throw new InvalidAppendRequestException($"'{nameof(type)}' at index {index} was not set."); } - - return new NewStreamMessage( - messageId, - type, - newStreamMessage.Value("jsonData").ToString(), - newStreamMessage.Value("jsonMetadata")?.ToString()); + + return new NewStreamMessageDto + { + MessageId = messageId, + Type = type, + JsonData = newStreamMessage.Value("jsonData"), + JsonMetadata = newStreamMessage.Value("jsonMetadata") + }; } + public string StreamId { get; } public int ExpectedVersion { get; } - public NewStreamMessage[] NewStreamMessages { get; } + public NewStreamMessageDto[] NewStreamMessages { get; } - public Task Invoke(IStreamStore streamStore, CancellationToken ct) - => streamStore.AppendToStream(StreamId, ExpectedVersion, NewStreamMessages, ct); + public Task Invoke(IStreamStore streamStore, CancellationToken ct) + => streamStore.AppendToStream( + StreamId, + ExpectedVersion, + Array.ConvertAll(NewStreamMessages, dto => dto.ToNewStreamMessage()), + ct); } } \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/NewStreamMessageDto.cs b/src/SqlStreamStore.HAL/Resources/NewStreamMessageDto.cs new file mode 100644 index 0000000..dc53635 --- /dev/null +++ b/src/SqlStreamStore.HAL/Resources/NewStreamMessageDto.cs @@ -0,0 +1,17 @@ +namespace SqlStreamStore.HAL.Resources +{ + using System; + using Newtonsoft.Json.Linq; + using SqlStreamStore.Streams; + + internal class NewStreamMessageDto + { + public Guid MessageId { get; set; } + public string Type { get; set; } + public JToken JsonData { get; set; } + public JToken JsonMetadata { get; set; } + + public NewStreamMessage ToNewStreamMessage() + => new NewStreamMessage(MessageId, Type, JsonData.ToString(), JsonMetadata?.ToString()); + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/Schema/AppendToStream.json b/src/SqlStreamStore.HAL/Resources/Schema/AppendToStream.json new file mode 100644 index 0000000..f933fe8 --- /dev/null +++ b/src/SqlStreamStore.HAL/Resources/Schema/AppendToStream.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/hyper-schema#", + "title": "Append to Stream", + "type": "object", + "required": [ + "messageId", + "type" + ], + "properties": { + "messageId": { + "type": "string", + "pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$", + "x-schema-form": { + "key": "messageId", + "type": "uuid" + } + }, + "type": { + "type": "string" + }, + "jsonData": { + "type": "object", + "x-schema-form": { + "key": "jsonData", + "type": "textarea", + "rows": 30 + } + }, + "jsonMetadata": { + "type": "string", + "x-schema-form": { + "key": "jsonMetadata", + "type": "textarea", + "rows": 30 + } + } + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/Schema/DeleteStream.json b/src/SqlStreamStore.HAL/Resources/Schema/DeleteStream.json new file mode 100644 index 0000000..85fd65d --- /dev/null +++ b/src/SqlStreamStore.HAL/Resources/Schema/DeleteStream.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/hyper-schema#", + "title": "Delete Stream", + "type": "object" +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/Schema/DeleteStreamMessage.json b/src/SqlStreamStore.HAL/Resources/Schema/DeleteStreamMessage.json new file mode 100644 index 0000000..ce184c1 --- /dev/null +++ b/src/SqlStreamStore.HAL/Resources/Schema/DeleteStreamMessage.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/hyper-schema#", + "title": "Delete Stream Message", + "type": "object" +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/Schema/SetStreamMetadata.json b/src/SqlStreamStore.HAL/Resources/Schema/SetStreamMetadata.json new file mode 100644 index 0000000..5bb72ae --- /dev/null +++ b/src/SqlStreamStore.HAL/Resources/Schema/SetStreamMetadata.json @@ -0,0 +1,23 @@ +{ + "title": "Set Stream Metadata", + "type": "object", + "$schema": "http://json-schema.org/draft-07/hyper-schema#", + "properties": { + "maxCount": { + "type": "integer", + "minimum": 1 + }, + "maxAge": { + "type": "integer", + "minimum": 1 + }, + "metadataJson": { + "type": "object", + "x-schema-form": { + "key": "metadataJson", + "type": "textarea", + "rows": 30 + } + } + } +} diff --git a/src/SqlStreamStore.HAL/Resources/Schemas.cs b/src/SqlStreamStore.HAL/Resources/Schemas.cs new file mode 100644 index 0000000..b487462 --- /dev/null +++ b/src/SqlStreamStore.HAL/Resources/Schemas.cs @@ -0,0 +1,41 @@ +namespace SqlStreamStore.HAL.Resources +{ + using System; + using System.Collections.Concurrent; + using System.IO; + using System.Reflection; + using Halcyon.HAL; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + internal static class Schemas + { + private static readonly ConcurrentDictionary s_schemas + = new ConcurrentDictionary(); + + public static HALResponse AppendToStream => GetSchema(nameof(AppendToStream)); + public static HALResponse SetStreamMetadata => GetSchema(nameof(SetStreamMetadata)); + public static HALResponse DeleteStream => GetSchema(nameof(DeleteStream)); + public static HALResponse DeleteStreamMessage => GetSchema(nameof(DeleteStreamMessage)); + + private static HALResponse GetSchema(string name) => s_schemas.GetOrAdd(name, ReadSchema); + + private static HALResponse ReadSchema(string name) + { + using(Stream stream = typeof(Schemas) + .GetTypeInfo().Assembly + .GetManifestResourceStream(typeof(Schemas), $"Schema.{name}.json")) + { + if(stream == null) + { + throw new Exception($"Embedded resource, {name}, not found. BUG!"); + } + + using(var reader = new JsonTextReader(new StreamReader(stream))) + { + return new HALResponse(JObject.Load(reader)); + } + } + } + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/SetStreamMetadataDto.cs b/src/SqlStreamStore.HAL/Resources/SetStreamMetadataDto.cs new file mode 100644 index 0000000..379a2af --- /dev/null +++ b/src/SqlStreamStore.HAL/Resources/SetStreamMetadataDto.cs @@ -0,0 +1,11 @@ +namespace SqlStreamStore.HAL.Resources +{ + using Newtonsoft.Json.Linq; + + internal class SetStreamMetadataDto + { + public JToken MetadataJson { get; set; } + public int? MaxCount { get; set; } + public int? MaxAge { get; set; } + } +} \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/StreamMessageResource.cs b/src/SqlStreamStore.HAL/Resources/StreamMessageResource.cs index 9cb8aef..2f546af 100644 --- a/src/SqlStreamStore.HAL/Resources/StreamMessageResource.cs +++ b/src/SqlStreamStore.HAL/Resources/StreamMessageResource.cs @@ -72,6 +72,9 @@ public async Task GetMessage( payload, metadata = message.JsonMetadata }) + .AddEmbeddedResource( + Constants.Relations.Delete, + Schemas.DeleteStreamMessage) .AddLinks(Links.Self(operation)) .AddLinks(Links.Navigation(operation, message)) .AddLinks(Links.Message(operation))); diff --git a/src/SqlStreamStore.HAL/Resources/StreamMetadataResource.cs b/src/SqlStreamStore.HAL/Resources/StreamMetadataResource.cs index 077c474..a4871d6 100644 --- a/src/SqlStreamStore.HAL/Resources/StreamMetadataResource.cs +++ b/src/SqlStreamStore.HAL/Resources/StreamMetadataResource.cs @@ -8,13 +8,6 @@ namespace SqlStreamStore.HAL.Resources internal class StreamMetadataResource : IResource { - private static readonly Link[] s_links = - { - new Link(Constants.Relations.Self, Constants.Streams.Metadata), - new Link(Constants.Relations.Metadata, Constants.Streams.Metadata), - new Link(Constants.Relations.Feed, "../") - }; - private readonly IStreamStore _streamStore; public HttpMethod[] Options { get; } = @@ -39,13 +32,20 @@ public async Task GetStreamMetadata( var result = await operation.Invoke(_streamStore, cancellationToken); var response = new Response(new HALResponse(new - { - result.StreamId, - result.MetadataStreamVersion, - result.MaxAge, - result.MaxCount, - result.MetadataJson - }).AddLinks(s_links), + { + result.StreamId, + result.MetadataStreamVersion, + result.MaxAge, + result.MaxCount, + result.MetadataJson + }) + .AddLinks( + Links.Self(), + Links.Metadata(), + Links.Feed(operation)) + .AddEmbeddedResource( + Constants.Relations.Metadata, + Schemas.SetStreamMetadata), result.MetadataStreamVersion >= 0 ? 200 : 404); return response; @@ -64,9 +64,23 @@ public async Task SetStreamMetadata( operation.MaxCount, operation.MetadataJson }) - .AddLinks(s_links)); + .AddLinks( + Links.Self(), + Links.Metadata(), + Links.Feed(operation))); return response; } + + private static class Links + { + public static Link Self() => new Link(Constants.Relations.Self, Constants.Streams.Metadata); + public static Link Metadata() => new Link(Constants.Relations.Metadata, Constants.Streams.Metadata); + public static Link Feed(GetStreamMetadataOperation operation) => Link(operation.StreamId); + public static Link Feed(SetStreamMetadataOperation operation) => Link(operation.StreamId); + + private static Link Link(string streamId) + => new Link(Constants.Relations.Feed, $"../{streamId}"); + } } } \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/Resources/StreamResource.cs b/src/SqlStreamStore.HAL/Resources/StreamResource.cs index 2c4e785..4bf1dc2 100644 --- a/src/SqlStreamStore.HAL/Resources/StreamResource.cs +++ b/src/SqlStreamStore.HAL/Resources/StreamResource.cs @@ -75,6 +75,13 @@ public async Task GetPage(ReadStreamOperation operation, CancellationT .AddLinks(Links.Navigation(page, operation)) .AddLinks(Links.Feed(operation)) .AddLinks(Links.Metadata(operation)) + .AddLinks(Links.Index()) + .AddEmbeddedResource( + Constants.Relations.AppendToStream, + Schemas.AppendToStream) + .AddEmbeddedResource( + Constants.Relations.Delete, + Schemas.DeleteStream) .AddEmbeddedCollection( Constants.Relations.Message, streamMessages.Zip( @@ -90,7 +97,9 @@ public async Task GetPage(ReadStreamOperation operation, CancellationT payload, metadata = message.JsonMetadata }) - .AddLinks(Links.Message.Self(message)))), + .AddLinks( + Links.Message.Self(message), + Links.Message.Feed(message)))), page.Status == PageReadStatus.StreamNotFound ? 404 : 200); } @@ -173,6 +182,11 @@ public static Link Metadata(ReadStreamOperation operation) Constants.Relations.Metadata, $"{operation.StreamId}/metadata"); + public static Link Index() + => new Link( + Constants.Relations.Index, + ".."); + public static IEnumerable Navigation(ReadStreamPage page, ReadStreamOperation operation) { var first = First(page, operation); @@ -195,6 +209,10 @@ public static class Message public static Link Self(StreamMessage message) => new Link( Constants.Relations.Self, $"{message.StreamId}/{message.StreamVersion}"); + + public static Link Feed(StreamMessage message) => new Link( + Constants.Relations.Feed, + message.StreamId); } } } diff --git a/src/SqlStreamStore.HAL/SqlStreamStore.HAL.csproj b/src/SqlStreamStore.HAL/SqlStreamStore.HAL.csproj index b925c23..7d6105e 100644 --- a/src/SqlStreamStore.HAL/SqlStreamStore.HAL.csproj +++ b/src/SqlStreamStore.HAL/SqlStreamStore.HAL.csproj @@ -12,4 +12,7 @@ + + + \ No newline at end of file diff --git a/src/SqlStreamStore.HAL/SqlStreamStoreHalMiddleware.cs b/src/SqlStreamStore.HAL/SqlStreamStoreHalMiddleware.cs index 117de03..c542ddb 100644 --- a/src/SqlStreamStore.HAL/SqlStreamStoreHalMiddleware.cs +++ b/src/SqlStreamStore.HAL/SqlStreamStoreHalMiddleware.cs @@ -2,7 +2,6 @@ { using System; using System.Linq; - using System.Net.Http.Headers; using System.Threading.Tasks; using Halcyon.HAL; using Microsoft.AspNetCore.Builder; @@ -27,12 +26,9 @@ private static MidFunc MethodsNotAllowed(params string[] methods) => (context, n return Task.CompletedTask; }; - private static MidFunc AcceptOnlyHalJson => (context, next) => + private static MidFunc AcceptHalJson => (context, next) => { - var accept = context.Request.Headers.GetCommaSeparatedValues("Accept") - .Select(value => MediaTypeWithQualityHeaderValue.TryParse(value, out var header) - ? header.MediaType - : null); + var accept = context.Request.GetAcceptHeaders(); return accept.Any(header => header == Constants.Headers.ContentTypes.HalJson || header == Constants.Headers.ContentTypes.Any) @@ -72,7 +68,7 @@ public static IApplicationBuilder UseSqlStreamStoreHal( return builder .Use(ExceptionHandlingMiddleware.HandleExceptions) - .Use(AcceptOnlyHalJson) + .Use(AcceptHalJson) .Use(Index) .Map("/stream", UseAllStream(streamStore)) .Map("/streams", UseStream(streamStore));