/* * Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.WebServer * File: WebserverBase.cs * * WebserverBase.cs is part of VNLib.WebServer which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.WebServer is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 2 of the License, * or (at your option) any later version. * * VNLib.WebServer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/. */ using System; using System.Linq; using System.Text.Json; using System.Diagnostics; using VNLib.Net.Http; using VNLib.Utils; using VNLib.Utils.Extensions; using VNLib.Plugins.Runtime; using VNLib.Plugins.Essentials.ServiceStack; using VNLib.Plugins.Essentials.ServiceStack.Construction; using VNLib.WebServer.Config; using VNLib.WebServer.Transport; using VNLib.WebServer.RuntimeLoading; namespace VNLib.WebServer.Bootstrap { internal abstract class WebserverBase(ServerLogger logger, IServerConfig config, ProcessArguments procArgs) : VnDisposeable { protected readonly ProcessArguments procArgs = procArgs; protected readonly IServerConfig config = config; protected readonly ServerLogger logger = logger; protected readonly TcpServerLoader TcpConfig = new(config, procArgs, logger.SysLog); private HttpServiceStack? _serviceStack; /// /// Gets the internal this /// controller is managing /// public HttpServiceStack ServiceStack { get { if (_serviceStack is null) { throw new InvalidOperationException("Service stack has not been configured yet"); } return _serviceStack; } } /// /// Configures the http server for the application so /// its ready to start /// public virtual void Configure() { _serviceStack = ConfiugreServiceStack(); } protected virtual HttpServiceStack ConfiugreServiceStack() { bool loadPluginsConcurrently = !procArgs.HasArgument("--sequential-load"); JsonElement conf = config.GetDocumentRoot(); HttpConfig http = GetHttpConfig(); VirtualHostConfig[] virtualHosts = GetAllVirtualHosts(); PluginStackBuilder? plugins = ConfigurePlugins(); HttpServiceStackBuilder builder = new HttpServiceStackBuilder() .LoadPluginsConcurrently(loadPluginsConcurrently) .WithBuiltInHttp(TcpConfig.ReduceBindingsForGroups, http) .WithManualPlugins(plugins => { }) .WithDomain(domain => { domain.WithServiceGroups(vh => { /* * Must pass the virtual host configuration as the state object * so transport providers can be loaded from a given virtual host */ virtualHosts.ForEach(vhConfig => vh.WithVirtualHost(vhConfig, vhConfig)); }); }); if (plugins != null) { builder.WithPluginStack(plugins.ConfigureStack); } PrintLogicalRouting(virtualHosts); return builder.Build(); } protected abstract VirtualHostConfig[] GetAllVirtualHosts(); protected abstract HttpConfig GetHttpConfig(); protected abstract PluginStackBuilder? ConfigurePlugins(); /// /// Starts the server and returns immediately /// after server start listening /// public void Start() { /* Since this api is uses internally, knowing the order of operations is a bug, not a rumtime accident */ Debug.Assert(Disposed == false, "Server was disposed"); Debug.Assert(_serviceStack != null, "Server was not configured"); //Attempt to load plugins before starting server _serviceStack.LoadPlugins(logger.AppLog); _serviceStack.StartServers(); } /// /// Stops the server and waits for all connections to close and /// servers to fully shut down /// public void Stop() { Debug.Assert(Disposed == false, "Server was disposed"); Debug.Assert(_serviceStack != null, "Server was not configured"); //Stop the server and wait synchronously _serviceStack.StopAndWaitAsync() .GetAwaiter() .GetResult(); } private void PrintLogicalRouting(VirtualHostConfig[] hosts) { const string header =@" =================================================== --- HTTP Service Domain --- {enabledRoutes} routes enabled "; VariableLogFormatter sb = new(logger.AppLog, Utils.Logging.LogLevel.Information); sb.AppendFormat(header, hosts.Length); foreach (VirtualHostConfig host in hosts) { sb.AppendLine(); sb.AppendFormat("Virtual Host: {hostnames}\n", (object)host.Hostnames); sb.AppendFormat(" Root directory {rdir}\n", host.RootDir); sb.AppendLine(); //Print interfaces string[] interfaces = host.Transports .Select(i =>$" - {i.Address}:{i.Port} TLS: {i.Ssl}, Client cert: {i.ClientCertRequired}, OS Ciphers: {i.UseOsCiphers}") .ToArray(); sb.AppendLine(" Interfaces:"); sb.AppendFormat("{interfaces}", string.Join("\n", interfaces)); sb.AppendLine(); sb.AppendLine(" Options:"); sb.AppendFormat(" - Whitelist: {wl}\n", host.WhiteList); sb.AppendFormat(" - Blacklist: {bl}\n", host.BlackList); sb.AppendFormat(" - Path filter: {filter}\n", host.PathFilter); sb.AppendFormat(" - Cache default time: {cache}\n", host.CacheDefault); sb.AppendFormat(" - Cached error files: {files}\n", host.FailureFiles.Select(static p => (int)p.Key)); sb.AppendFormat(" - Downstream servers: {dsServers}\n", host.DownStreamServers); sb.AppendFormat(" - Middlewares loaded {mw}\n", host.CustomMiddleware.Count); sb.AppendLine(); sb.Flush(); } } /// protected override void Free() => _serviceStack?.Dispose(); } }