@@ -1,5 +1,6 @@ | |||
using System; | |||
using DotNetCore.CAP; | |||
using Microsoft.AspNetCore.Http; | |||
using Microsoft.Extensions.DependencyInjection; | |||
namespace Microsoft.AspNetCore.Builder | |||
@@ -33,5 +34,24 @@ namespace Microsoft.AspNetCore.Builder | |||
bootstrapper.BootstrapAsync(); | |||
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.Text; | |||
using System.Text.RegularExpressions; | |||
using Microsoft.AspNetCore.Http; | |||
namespace DotNetCore.CAP.Dashboard | |||
{ | |||
@@ -24,4 +25,22 @@ namespace DotNetCore.CAP.Dashboard | |||
public DashboardRequest Request { 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 Microsoft.AspNetCore.Http; | |||
namespace DotNetCore.CAP.Dashboard | |||
{ | |||
@@ -15,4 +17,28 @@ namespace DotNetCore.CAP.Dashboard | |||
public abstract string GetQuery(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.Globalization; | |||
using System.IO; | |||
using System.Threading.Tasks; | |||
using Microsoft.AspNetCore.Http; | |||
namespace DotNetCore.CAP.Dashboard | |||
{ | |||
@@ -14,4 +16,39 @@ namespace DotNetCore.CAP.Dashboard | |||
public abstract void SetExpire(DateTimeOffset? value); | |||
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; | |||
} | |||
} | |||
} |