Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 81 additions & 52 deletions dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
Expand All @@ -32,6 +33,7 @@
using Microsoft.Extensions.Caching.SqlServer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;
Expand All @@ -41,6 +43,7 @@ namespace GeneXus.Application
public class Program
{
const string DEFAULT_PORT = "80";
const int GRACEFUL_SHUTDOWN_DELAY_SECONDS = 30;
static string DEFAULT_SCHEMA = Uri.UriSchemeHttp;

public static void Main(string[] args)
Expand Down Expand Up @@ -78,13 +81,14 @@ public static void Main(string[] args)
{
Console.Error.WriteLine("ERROR:");
Console.Error.WriteLine("Web Host terminated unexpectedly: {0}", e.Message);
}
}
}

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseContentRoot(Startup.LocalPath)
.UseShutdownTimeout(TimeSpan.FromSeconds(GRACEFUL_SHUTDOWN_DELAY_SECONDS))
.Build();

public static IWebHost BuildWebHostPort(string[] args, string port)
Expand All @@ -94,11 +98,12 @@ public static IWebHost BuildWebHostPort(string[] args, string port)
static IWebHost BuildWebHostPort(string[] args, string port, string schema)
{
return WebHost.CreateDefaultBuilder(args)
.UseUrls($"{schema}://*:{port}")
.UseStartup<Startup>()
.UseWebRoot(Startup.LocalPath)
.UseContentRoot(Startup.LocalPath)
.Build();
.UseUrls($"{schema}://*:{port}")
.UseStartup<Startup>()
.UseWebRoot(Startup.LocalPath)
.UseContentRoot(Startup.LocalPath)
.UseShutdownTimeout(TimeSpan.FromSeconds(GRACEFUL_SHUTDOWN_DELAY_SECONDS))
.Build();
}

private static void LocatePhysicalLocalPath()
Expand All @@ -123,15 +128,15 @@ public static IApplicationBuilder UseGXHandlerFactory(this IApplicationBuilder b
public static IApplicationBuilder MapWebSocketManager(this IApplicationBuilder app, string basePath)
{
return app
.Map($"{basePath}/gxwebsocket" , (_app) => _app.UseMiddleware<Notifications.WebSocket.WebSocketManagerMiddleware>())
.Map($"{basePath}/gxwebsocket.svc", (_app) => _app.UseMiddleware<Notifications.WebSocket.WebSocketManagerMiddleware>()); //Compatibility reasons. Remove in the future.
.Map($"{basePath}/gxwebsocket", (_app) => _app.UseMiddleware<Notifications.WebSocket.WebSocketManagerMiddleware>())
.Map($"{basePath}/gxwebsocket.svc", (_app) => _app.UseMiddleware<Notifications.WebSocket.WebSocketManagerMiddleware>()); //Compatibility reasons. Remove in the future.
}
}
public class CustomBadRequestObjectResult : ObjectResult
{
static readonly IGXLogger log = GXLoggerFactory.GetLogger(typeof(CustomBadRequestObjectResult).FullName);
public CustomBadRequestObjectResult(ActionContext context)
: base(HttpHelper.GetJsonError(StatusCodes.Status400BadRequest.ToString(), HttpHelper.StatusCodeToTitle(HttpStatusCode.BadRequest)))
: base(HttpHelper.GetJsonError(StatusCodes.Status400BadRequest.ToString(), HttpHelper.StatusCodeToTitle(HttpStatusCode.BadRequest)))
{
LogErrorResponse(context);
StatusCode = StatusCodes.Status400BadRequest;
Expand Down Expand Up @@ -180,7 +185,7 @@ public class Startup
internal const string GX_CONTROLLERS = "gxcontrollers";
internal static string DefaultFileName { get; set; }

public List<string> servicesBase = new List<string>();
public List<string> servicesBase = new List<string>();

private GXRouting gxRouting;
public Startup(IConfiguration configuration, IHostingEnvironment env)
Expand All @@ -197,6 +202,10 @@ public void ConfigureServices(IServiceCollection services)
{
OpenTelemetryService.Setup(services);

services.AddHealthChecks()
.AddCheck("liveness", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
.AddCheck("readiness", () => HealthCheckResult.Healthy(), tags: new[] { "ready" });

IMvcBuilder builder = services.AddMvc(option =>
{
option.EnableEndpointRouting = false;
Expand Down Expand Up @@ -249,7 +258,7 @@ public void ConfigureServices(IServiceCollection services)
string sessionCookieName = GxWebSession.GetSessionCookieName(VirtualPath);
if (!string.IsNullOrEmpty(sessionCookieName))
{
options.Cookie.Name=sessionCookieName;
options.Cookie.Name = sessionCookieName;
GxWebSession.SessionCookieName = sessionCookieName;
}
string sameSite;
Expand All @@ -274,20 +283,20 @@ public void ConfigureServices(IServiceCollection services)
services.AddResponseCompression(options =>
{
options.MimeTypes = new[]
{
// Default
"text/plain",
"text/css",
"application/javascript",
"text/html",
"application/xml",
"text/xml",
"application/json",
"text/json",
// Custom
"application/json",
"application/pdf"
};
{
// Default
"text/plain",
"text/css",
"application/javascript",
"text/html",
"application/xml",
"text/xml",
"application/json",
"text/json",
// Custom
"application/json",
"application/pdf"
};
options.EnableForHttps = true;
});
}
Expand All @@ -296,7 +305,7 @@ public void ConfigureServices(IServiceCollection services)

private void RegisterControllerAssemblies(IMvcBuilder mvcBuilder)
{

if (RestAPIHelpers.ServiceAsController())
{
mvcBuilder.AddMvcOptions(options => options.ModelBinderProviders.Insert(0, new QueryStringModelBinderProvider()));
Expand Down Expand Up @@ -365,9 +374,9 @@ private void RegisterRestServices(IMvcBuilder mvcBuilder)
try
{
string[] controllerAssemblyQualifiedName = new string(File.ReadLines(svcFile).First().SkipWhile(c => c != '"')
.Skip(1)
.TakeWhile(c => c != '"')
.ToArray()).Trim().Split(',');
.Skip(1)
.TakeWhile(c => c != '"')
.ToArray()).Trim().Split(',');
string controllerAssemblyName = controllerAssemblyQualifiedName.Last();
if (!serviceAssemblies.Contains(controllerAssemblyName))
{
Expand Down Expand Up @@ -428,25 +437,25 @@ private void DefineCorsPolicy(IServiceCollection services)
services.AddCors(options =>
{
options.AddPolicy(name: CORS_POLICY_NAME,
policy =>
{
policy.WithOrigins(origins);
if (!corsAllowedOrigins.Contains(CORS_ANY_ORIGIN))
{
policy.AllowCredentials();
}
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.SetPreflightMaxAge(TimeSpan.FromSeconds(CORS_MAX_AGE_SECONDS));
});
policy =>
{
policy.WithOrigins(origins);
if (!corsAllowedOrigins.Contains(CORS_ANY_ORIGIN))
{
policy.AllowCredentials();
}
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.SetPreflightMaxAge(TimeSpan.FromSeconds(CORS_MAX_AGE_SECONDS));
});
});
}
}
}

private void ConfigureSessionService(IServiceCollection services, ISessionService sessionService)
{

if (sessionService is GxRedisSession)
{
GxRedisSession gxRedisSession = (GxRedisSession)sessionService;
Expand Down Expand Up @@ -504,8 +513,11 @@ private void ConfigureSessionService(IServiceCollection services, ISessionServic
}
}
}
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env, ILoggerFactory loggerFactory, IHttpContextAccessor contextAccessor)
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env, ILoggerFactory loggerFactory, IHttpContextAccessor contextAccessor, Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime)
{
// Registrar para el graceful shutdown
applicationLifetime.ApplicationStopping.Register(OnShutdown);

string baseVirtualPath = string.IsNullOrEmpty(VirtualPath) ? VirtualPath : $"/{VirtualPath}";
LogConfiguration.SetupLog4Net();
AppContext.Configure(contextAccessor);
Expand Down Expand Up @@ -571,6 +583,17 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();

// Endpoints para health checks (Kubernetes probes)
endpoints.MapHealthChecks($"{baseVirtualPath}/_gx/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});

endpoints.MapHealthChecks($"{baseVirtualPath }/_gx/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
});
if (log.IsCriticalEnabled && env.IsDevelopment())
{
Expand Down Expand Up @@ -623,7 +646,7 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
},
ContentTypeProvider = provider
});

app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandler = new CustomExceptionHandlerMiddleware().Invoke,
Expand Down Expand Up @@ -666,7 +689,7 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos

app.UseGXHandlerFactory(basePath);

app.Run(async context =>
app.Run(async context =>
{
await Task.FromException(new PageNotFoundException(context.Request.Path.Value));
});
Expand All @@ -692,13 +715,13 @@ private void ConfigureSwaggerUI(IApplicationBuilder app, string baseVirtualPath)
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint($"../../{finfo.Name}", finfo.Name);
options.RoutePrefix =$"{baseVirtualPathWithSep}{finfo.Name}/{SWAGGER_SUFFIX}";
options.RoutePrefix = $"{baseVirtualPathWithSep}{finfo.Name}/{SWAGGER_SUFFIX}";
});
if (finfo.Name.Equals(SWAGGER_DEFAULT_YAML, StringComparison.OrdinalIgnoreCase) && File.Exists(Path.Combine(LocalPath, DEVELOPER_MENU)))
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint($"../../{SWAGGER_DEFAULT_YAML}", SWAGGER_DEFAULT_YAML);
options.RoutePrefix =$"{baseVirtualPathWithSep}{DEVELOPER_MENU}/{SWAGGER_SUFFIX}";
options.RoutePrefix = $"{baseVirtualPathWithSep}{DEVELOPER_MENU}/{SWAGGER_SUFFIX}";
});

}
Expand All @@ -709,11 +732,17 @@ private void ConfigureSwaggerUI(IApplicationBuilder app, string baseVirtualPath)
}
}

private void OnShutdown()
{
GXLogging.Info(log, "Application gracefully shutting down... Waiting for in-process requests to complete.");
ThreadUtil.WaitForEnd();
}

private void AddRewrite(IApplicationBuilder app, string rewriteFile, string baseURL)
{
string rules = File.ReadAllText(rewriteFile);
rules = rules.Replace("{BASEURL}", baseURL);

using (var apacheModRewriteStreamReader = new StringReader(rules))
{
var options = new RewriteOptions().AddApacheModRewrite(apacheModRewriteStreamReader);
Expand All @@ -726,10 +755,10 @@ public class CustomExceptionHandlerMiddleware
static readonly IGXLogger log = GXLoggerFactory.GetLogger<CustomExceptionHandlerMiddleware>();
public async Task Invoke(HttpContext httpContext)
{
string httpReasonPhrase=string.Empty;
string httpReasonPhrase = string.Empty;
Exception ex = httpContext.Features.Get<IExceptionHandlerFeature>()?.Error;
HttpStatusCode httpStatusCode = (HttpStatusCode)httpContext.Response.StatusCode;
if (ex!=null)
if (ex != null)
{
if (ex is PageNotFoundException)
{
Expand All @@ -747,7 +776,7 @@ public async Task Invoke(HttpContext httpContext)
GXLogging.Error(log, $"Internal error", ex);
}
}
if (httpStatusCode!= HttpStatusCode.OK)
if (httpStatusCode != HttpStatusCode.OK)
{
string redirectPage = Config.MapCustomError(httpStatusCode.ToString(HttpHelper.INT_FORMAT));
if (!string.IsNullOrEmpty(redirectPage))
Expand All @@ -761,7 +790,7 @@ public async Task Invoke(HttpContext httpContext)
if (!string.IsNullOrEmpty(httpReasonPhrase))
{
IHttpResponseFeature responseReason = httpContext.Response.HttpContext.Features.Get<IHttpResponseFeature>();
if (responseReason!=null)
if (responseReason != null)
responseReason.ReasonPhrase = httpReasonPhrase;
}
}
Expand Down Expand Up @@ -818,7 +847,7 @@ public IActionResult Index()
}
internal class SetRoutePrefix : IApplicationModelConvention
{
private readonly AttributeRouteModel _routePrefix ;
private readonly AttributeRouteModel _routePrefix;
public SetRoutePrefix(IRouteTemplateProvider route)
{
_routePrefix = new AttributeRouteModel(route);
Expand Down
Loading