public ResponseWrapper(RequestDelegate next, ILogger <ResponseWrapper> logger, IApiResponseBuilder responseBuilder, IConfiguration configuration) { this.next = next; this.logger = logger; this.responseBuilder = responseBuilder; this.includePath = configuration.GetValue(CubesConstants.Config_HostWrapPath, "/api/"); this.excludePath = configuration.GetValue(CubesConstants.Config_HostWrapPathExclude, ""); }
public ResponseWrapper(RequestDelegate next, IApiResponseBuilder responseBuilder, IConfiguration configuration, JsonSerializerSettings jsonSerializerSettings) { this.next = next; this.responseBuilder = responseBuilder; this.jsonSerializerSettings = jsonSerializerSettings; this.includePath = configuration.GetValue(CubesConstants.Config_HostWrapPath, "/api/"); this.excludePath = configuration.GetValue(CubesConstants.Config_HostWrapPathExclude, ""); }
public static IApplicationBuilder UseCustomExceptionHandler(this IApplicationBuilder app, IApiResponseBuilder responseBuilder, ILoggerFactory loggerFactory) { var logger = loggerFactory.CreateLogger("Cubes.Web.CustomExceptionHandler"); app.UseExceptionHandler(appError => { appError.Run(async context => { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.ContentType = "application/json"; var contextFeature = context.Features.Get <IExceptionHandlerFeature>(); if (contextFeature != null) { var ex = contextFeature.Error; var frame = new StackTrace(ex, true).GetFrame(0); var details = new StringBuilder(); var methodInfo = $"{frame.GetMethod().DeclaringType.FullName}.{frame.GetMethod().Name}()"; var fileInfo = $"{Path.GetFileName(frame.GetFileName())}, line {frame.GetFileLineNumber()}"; details .Append(ex.GetType().Name) .Append(": ") .AppendLine(ex.Message); details .Append(methodInfo) .Append(" in ") .AppendLine(fileInfo); var apiResponse = responseBuilder.Create() .HasErrors() .WithStatusCode(context.Response.StatusCode) .WithMessage("An unhandled exception occurred while processing the request.") .WithResponse(new { Details = details.ToString(), RequestInfo = $"{context.Request.Path} [{context.Request.Method}]" }); await context .Response .WriteAsync(apiResponse.ToString()); } }); }); return(app); }
public static IApplicationBuilder UseCubesApi(this IApplicationBuilder app, IConfiguration configuration, IWebHostEnvironment env, IApiResponseBuilder responseBuilder, ILoggerFactory loggerFactory) { var enableCompression = configuration.GetValue <bool>(CubesConstants.Config_HostEnableCompression, true); if (enableCompression) { app.UseResponseCompression(); } if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseCustomExceptionHandler(responseBuilder, loggerFactory); } var corsPolicies = configuration .GetCorsPolicies() .Select(p => p.PolicyName) .ToList(); foreach (var policy in corsPolicies) { app.UseCors(policy); } return(app .UseHomePage() .UseAdminPage(configuration, loggerFactory) .UseCubesSwagger() .UseStaticContent(configuration, loggerFactory) .UseCubesMiddleware(loggerFactory, responseBuilder) .UseResponseWrapper()); }
public ApiCaller() { _responseBuilder = new ApiResponseBuilder(); }
public static IApplicationBuilder UseCubesMiddleware(this IApplicationBuilder app, ILoggerFactory loggerFactory, IApiResponseBuilder responseBuilder) { app.Use(async(ctx, next) => { // Prepare var logger = loggerFactory.CreateLogger(CUBES_MIDDLEWARE_LOGGER); var requestID = Guid.NewGuid().ToString("N"); var requestInfo = $"{ctx.Request.Method} {ctx.Request.Path}{ctx.Request.Query.AsString()}"; // Add to headers ctx.Request.Headers.Add(CUBES_HEADER_REQUEST_ID, new[] { requestID }); // Provide context information var ctxProvider = ctx.RequestServices.GetService <IContextProvider>(); var context = new Context(requestID, requestInfo); ctxProvider.Current = context; var watcher = Stopwatch.StartNew(); ctx.Response.OnStarting(() => { watcher.Stop(); ctx.Response.Headers.Add(CUBES_HEADER_REQUEST_ID, new[] { requestID }); return(Task.FromResult(0)); }); await next.Invoke(); // Just in case... watcher.Stop(); var httpContextAccessor = ctx.RequestServices.GetService <IHttpContextAccessor>(); // Inform user logger.LogInformation("{IP} [{startedAt}] \"{info}\", {statusCode}, {elapsed} ms", httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString(), context.StartedAt, context.SourceInfo, ctx.Response.StatusCode, watcher.ElapsedMilliseconds); }); app.UseStatusCodePages(async context => { context.HttpContext.Response.ContentType = "application/json"; var apiResponse = responseBuilder.Create() .HasErrors() .WithStatusCode(context.HttpContext.Response.StatusCode) .WithMessage($"Invalid request, status code: {context.HttpContext.Response.StatusCode}") .WithResponse(new { Details = $"URL: {context.HttpContext.Request.Path}, Method: {context.HttpContext.Request.Method}" }); await context .HttpContext .Response .WriteAsync(apiResponse.ToString()); }); return(app); }
public static IApplicationBuilder UseCubesMiddleware(this IApplicationBuilder app, ILoggerFactory loggerFactory, IApiResponseBuilder responseBuilder, IMetrics metrics, JsonSerializerSettings serializerSettings) { app.Use(async(ctx, next) => { var endpoint = ctx.GetEndpoint(); // Prepare var logger = loggerFactory.CreateLogger(CUBES_MIDDLEWARE_LOGGER); var requestID = Guid.NewGuid().ToString("N"); var requestInfo = $"{ctx.Request.Method} {ctx.Request.Path}{ctx.Request.Query.AsString()}"; // Add to headers ctx.Request.Headers.Add(CUBES_HEADER_REQUEST_ID, new[] { requestID }); // Provide context information var ctxProvider = ctx.RequestServices.GetService <IContextProvider>(); var context = new Context(requestID, requestInfo); ctxProvider.Current = context; var watch = Stopwatch.StartNew(); ctx.Response.OnStarting(() => { watch.Stop(); ctx.Response.Headers.Add(CUBES_HEADER_REQUEST_ID, new[] { requestID }); return(Task.FromResult(0)); }); await next.Invoke(); // Just in case... watch.Stop(); var httpContextAccessor = ctx.RequestServices.GetService <IHttpContextAccessor>(); // Inform user var level = ctx.Response.StatusCode >= 400 && ctx.Response.StatusCode < 500 ? LogLevel.Warning : ctx.Response.StatusCode >= 500 ? LogLevel.Error : LogLevel.Information; logger.Log(level, "{IP} [{startedAt}] \"{info}\", {statusCode}, {elapsed} ms", httpContextAccessor.HttpContext.Connection.RemoteIpAddress, context.StartedAt, context.SourceInfo, ctx.Response.StatusCode, watch.ElapsedMilliseconds); // Metrics var path = (endpoint as RouteEndpoint)?.RoutePattern?.RawText ?? ctx.Request.Path; metrics .GetCounter(CubesCoreMetrics.CubesCoreApiCallsCount) .WithLabels(ctx.Request.Method, path) .Inc(); metrics .GetHistogram(CubesCoreMetrics.CubesCoreApiCallsDuration) .WithLabels(ctx.Request.Method, path) .Observe(watch.Elapsed.TotalSeconds); }); app.UseStatusCodePages(async context => { context.HttpContext.Response.ContentType = "application/json"; var apiResponse = responseBuilder.Create() .WithStatusCode(context.HttpContext.Response.StatusCode) .WithMessage($"Invalid request, status code: {context.HttpContext.Response.StatusCode}") .WithData(new { Details = $"URL: {context.HttpContext.Request.Path}, Method: {context.HttpContext.Request.Method}" }); await context .HttpContext .Response .WriteAsync(apiResponse.AsJson(serializerSettings)); }); return(app); }
public static IApplicationBuilder UseCubesApi(this IApplicationBuilder app, IConfiguration configuration, IWebHostEnvironment env, IApiResponseBuilder responseBuilder, ILoggerFactory loggerFactory, IMetrics metrics, JsonSerializerSettings serializerSettings) { var enableCompression = configuration.GetValue <bool>(CubesConstants.Config_HostEnableCompression, true); if (enableCompression) { app.UseResponseCompression(); } if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseCustomExceptionHandler(responseBuilder, loggerFactory, serializerSettings); } var corsPolicies = configuration .GetCorsPolicies() .Select(p => p.PolicyName) .ToList(); foreach (var policy in corsPolicies) { app.UseCors(policy); } // Based on: // https://gunnarpeipman.com/aspnet-core-health-checks/ // https://www.youtube.com/watch?v=bdgtYbGYsK0 // https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks var options = new HealthCheckOptions { AllowCachingResponses = false, ResponseWriter = async(context, result) => { context.Response.ContentType = MediaTypeNames.Application.Json; var response = new { Status = result.Status.ToString(), Checks = result.Entries.Select(e => new { Component = e.Key, e.Value.Description, Status = e.Value.Status.ToString(), e.Value.Duration }).ToList(), Duration = result.TotalDuration }; await context.Response.WriteAsync(response.AsJson()); } }; var hcEndpoint = configuration.GetValue(CubesConstants.Config_HostHealthCheckEndpoint, "/healthcheck"); if (!hcEndpoint.StartsWith("/")) { hcEndpoint = "/" + hcEndpoint; } app.UseHealthChecks(hcEndpoint, options); var metricsEndpoint = configuration.GetValue(CubesConstants.Config_HostMetricsEndpoint, "/metrics"); if (!metricsEndpoint.StartsWith("/")) { metricsEndpoint = "/" + metricsEndpoint; } return(app .UseHomePage() .UseAdminPage(configuration, loggerFactory) .UseCubesSwagger() .UseStaticContent(configuration, loggerFactory) .UseMetricServer(metricsEndpoint) .UseCubesMiddleware(loggerFactory, responseBuilder, metrics, serializerSettings) // TODO Add UseCubesApplications to provide a "hook" for application while setting the pipeline .UseResponseWrapper()); }