@@ -1,5 +1,6 @@ | |||||
using System; | using System; | ||||
using DotNetCore.CAP; | using DotNetCore.CAP; | ||||
using Microsoft.AspNetCore.Http; | |||||
using Microsoft.Extensions.DependencyInjection; | using Microsoft.Extensions.DependencyInjection; | ||||
namespace Microsoft.AspNetCore.Builder | namespace Microsoft.AspNetCore.Builder | ||||
@@ -33,5 +34,24 @@ namespace Microsoft.AspNetCore.Builder | |||||
bootstrapper.BootstrapAsync(); | bootstrapper.BootstrapAsync(); | ||||
return app; | return app; | ||||
} | } | ||||
public static IApplicationBuilder UseCapDashboard( | |||||
this IApplicationBuilder app, | |||||
string pathMatch = "/cap") | |||||
{ | |||||
if (app == null) throw new ArgumentNullException(nameof(app)); | |||||
if (pathMatch == null) throw new ArgumentNullException(nameof(pathMatch)); | |||||
var marker = app.ApplicationServices.GetService<CapMarkerService>(); | |||||
if (marker == null) | |||||
{ | |||||
throw new InvalidOperationException("Add Cap must be called on the service collection."); | |||||
} | |||||
app.Map(new PathString(pathMatch), x => x.UseMiddleware<DashboardMiddleware>(storage, options, routes)); | |||||
return app; | |||||
} | |||||
} | } | ||||
} | } |
@@ -0,0 +1,61 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Net; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using DotNetCore.CAP.Dashboard; | |||||
using Microsoft.AspNetCore.Http; | |||||
namespace DotNetCore.CAP | |||||
{ | |||||
public class DashboardMiddleware | |||||
{ | |||||
private readonly DashboardOptions _options; | |||||
private readonly RequestDelegate _next; | |||||
private readonly IStorage _storage; | |||||
private readonly RouteCollection _routes; | |||||
public DashboardMiddleware(RequestDelegate next, DashboardOptions options, IStorage storage, RouteCollection routes) | |||||
{ | |||||
if (next == null) throw new ArgumentNullException(nameof(next)); | |||||
if (storage == null) throw new ArgumentNullException(nameof(storage)); | |||||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||||
if (routes == null) throw new ArgumentNullException(nameof(routes)); | |||||
_next = next; | |||||
_options = options; | |||||
_storage = storage; | |||||
_routes = routes; | |||||
} | |||||
public Task Invoke(HttpContext httpContext) | |||||
{ | |||||
var context = new CapDashboardContext(_storage, _options, httpContext); | |||||
var findResult = _routes.FindDispatcher(httpContext.Request.Path.Value); | |||||
if (findResult == null) | |||||
{ | |||||
return _next.Invoke(httpContext); | |||||
} | |||||
// ReSharper disable once LoopCanBeConvertedToQuery | |||||
foreach (var filter in _options.Authorization) | |||||
{ | |||||
if (!filter.Authorize(context)) | |||||
{ | |||||
var isAuthenticated = httpContext.User?.Identity?.IsAuthenticated; | |||||
httpContext.Response.StatusCode = isAuthenticated == true | |||||
? (int)HttpStatusCode.Forbidden | |||||
: (int)HttpStatusCode.Unauthorized; | |||||
return Task.FromResult(0); | |||||
} | |||||
} | |||||
context.UriMatch = findResult.Item2; | |||||
return findResult.Item1.Dispatch(context); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,43 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using Microsoft.Extensions.DependencyInjection; | |||||
namespace DotNetCore.CAP | |||||
{ | |||||
internal sealed class DashboardOptionsExtension : ICapOptionsExtension | |||||
{ | |||||
private readonly Action<DashboardOptions> _options; | |||||
public DashboardOptionsExtension(Action<DashboardOptions> option) | |||||
{ | |||||
_options = option; | |||||
} | |||||
public void AddServices(IServiceCollection services) | |||||
{ | |||||
var dashboardOptions = new DashboardOptions(); | |||||
_options?.Invoke(dashboardOptions); | |||||
services.AddSingleton(dashboardOptions); | |||||
} | |||||
} | |||||
public static class CapOptionsExtensions | |||||
{ | |||||
/// <summary> | |||||
/// Configuration to use kafka in CAP. | |||||
/// </summary> | |||||
/// <param name="options">Provides programmatic configuration for the kafka .</param> | |||||
/// <returns></returns> | |||||
public static CapOptions UseDashboard(this CapOptions capOptions, Action<DashboardOptions> options) | |||||
{ | |||||
if (options == null) throw new ArgumentNullException(nameof(options)); | |||||
capOptions.RegisterExtension(new DashboardOptionsExtension(options)); | |||||
return capOptions; | |||||
} | |||||
} | |||||
} |
@@ -2,6 +2,7 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Text; | using System.Text; | ||||
using System.Text.RegularExpressions; | using System.Text.RegularExpressions; | ||||
using Microsoft.AspNetCore.Http; | |||||
namespace DotNetCore.CAP.Dashboard | namespace DotNetCore.CAP.Dashboard | ||||
{ | { | ||||
@@ -24,4 +25,22 @@ namespace DotNetCore.CAP.Dashboard | |||||
public DashboardRequest Request { get; protected set; } | public DashboardRequest Request { get; protected set; } | ||||
public DashboardResponse Response { get; protected set; } | public DashboardResponse Response { get; protected set; } | ||||
} | } | ||||
public sealed class CapDashboardContext : DashboardContext | |||||
{ | |||||
public CapDashboardContext( | |||||
IStorage storage, | |||||
DashboardOptions options, | |||||
HttpContext httpContext) | |||||
: base(storage, options) | |||||
{ | |||||
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); | |||||
HttpContext = httpContext; | |||||
Request = new CapDashboardRequest(httpContext); | |||||
Response = new CapDashboardResponse(httpContext); | |||||
} | |||||
public HttpContext HttpContext { get; } | |||||
} | |||||
} | } |
@@ -1,5 +1,7 @@ | |||||
using System.Collections.Generic; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Microsoft.AspNetCore.Http; | |||||
namespace DotNetCore.CAP.Dashboard | namespace DotNetCore.CAP.Dashboard | ||||
{ | { | ||||
@@ -15,4 +17,28 @@ namespace DotNetCore.CAP.Dashboard | |||||
public abstract string GetQuery(string key); | public abstract string GetQuery(string key); | ||||
public abstract Task<IList<string>> GetFormValuesAsync(string key); | public abstract Task<IList<string>> GetFormValuesAsync(string key); | ||||
} | } | ||||
internal sealed class CapDashboardRequest : DashboardRequest | |||||
{ | |||||
private readonly HttpContext _context; | |||||
public CapDashboardRequest(HttpContext context) | |||||
{ | |||||
if (context == null) throw new ArgumentNullException(nameof(context)); | |||||
_context = context; | |||||
} | |||||
public override string Method => _context.Request.Method; | |||||
public override string Path => _context.Request.Path.Value; | |||||
public override string PathBase => _context.Request.PathBase.Value; | |||||
public override string LocalIpAddress => _context.Connection.LocalIpAddress.ToString(); | |||||
public override string RemoteIpAddress => _context.Connection.RemoteIpAddress.ToString(); | |||||
public override string GetQuery(string key) => _context.Request.Query[key]; | |||||
public override async Task<IList<string>> GetFormValuesAsync(string key) | |||||
{ | |||||
var form = await _context.Request.ReadFormAsync(); | |||||
return form[key]; | |||||
} | |||||
} | |||||
} | } |
@@ -1,6 +1,8 @@ | |||||
using System; | using System; | ||||
using System.Globalization; | |||||
using System.IO; | using System.IO; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Microsoft.AspNetCore.Http; | |||||
namespace DotNetCore.CAP.Dashboard | namespace DotNetCore.CAP.Dashboard | ||||
{ | { | ||||
@@ -14,4 +16,39 @@ namespace DotNetCore.CAP.Dashboard | |||||
public abstract void SetExpire(DateTimeOffset? value); | public abstract void SetExpire(DateTimeOffset? value); | ||||
public abstract Task WriteAsync(string text); | public abstract Task WriteAsync(string text); | ||||
} | } | ||||
internal sealed class CapDashboardResponse : DashboardResponse | |||||
{ | |||||
private readonly HttpContext _context; | |||||
public CapDashboardResponse( HttpContext context) | |||||
{ | |||||
if (context == null) throw new ArgumentNullException(nameof(context)); | |||||
_context = context; | |||||
} | |||||
public override string ContentType | |||||
{ | |||||
get { return _context.Response.ContentType; } | |||||
set { _context.Response.ContentType = value; } | |||||
} | |||||
public override int StatusCode | |||||
{ | |||||
get { return _context.Response.StatusCode; } | |||||
set { _context.Response.StatusCode = value; } | |||||
} | |||||
public override Stream Body => _context.Response.Body; | |||||
public override Task WriteAsync(string text) | |||||
{ | |||||
return _context.Response.WriteAsync(text); | |||||
} | |||||
public override void SetExpire(DateTimeOffset? value) | |||||
{ | |||||
_context.Response.Headers["Expires"] = value?.ToString("r", CultureInfo.InvariantCulture); | |||||
} | |||||
} | |||||
} | } |
@@ -0,0 +1,48 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
using System.Text.RegularExpressions; | |||||
namespace DotNetCore.CAP.Dashboard | |||||
{ | |||||
public class RouteCollection | |||||
{ | |||||
private readonly List<Tuple<string, IDashboardDispatcher>> _dispatchers | |||||
= new List<Tuple<string, IDashboardDispatcher>>(); | |||||
public void Add(string pathTemplate, IDashboardDispatcher dispatcher) | |||||
{ | |||||
if (pathTemplate == null) throw new ArgumentNullException(nameof(pathTemplate)); | |||||
if (dispatcher == null) throw new ArgumentNullException(nameof(dispatcher)); | |||||
_dispatchers.Add(new Tuple<string, IDashboardDispatcher>(pathTemplate, dispatcher)); | |||||
} | |||||
public Tuple<IDashboardDispatcher, Match> FindDispatcher(string path) | |||||
{ | |||||
if (path.Length == 0) path = "/"; | |||||
foreach (var dispatcher in _dispatchers) | |||||
{ | |||||
var pattern = dispatcher.Item1; | |||||
if (!pattern.StartsWith("^", StringComparison.OrdinalIgnoreCase)) | |||||
pattern = "^" + pattern; | |||||
if (!pattern.EndsWith("$", StringComparison.OrdinalIgnoreCase)) | |||||
pattern += "$"; | |||||
var match = Regex.Match( | |||||
path, | |||||
pattern, | |||||
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline); | |||||
if (match.Success) | |||||
{ | |||||
return new Tuple<IDashboardDispatcher, Match>(dispatcher.Item2, match); | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
} | |||||
} |