1 module app; 2 3 import dlogg.strict; 4 import daemonize.d; 5 6 // cannot use vibe.d due symbol clash for logging 7 import vibe.core.core; 8 import vibe.core.log : setLogLevel, setLogFile, VibeLogLevel = LogLevel; 9 import vibe.http.server; 10 import vibe.http.router; 11 import vibe.http.fileserver; 12 13 import jsonizer; 14 import std.json; 15 import std.file; 16 import std.path; 17 import std.getopt; 18 19 immutable string settingsFileName = "web.json"; 20 21 void loadRouteSpecs(RouteSettings[] routes, string basePath, URLRouter router, shared ILogger logger) 22 { 23 foreach(route; routes) 24 { 25 auto targetPath = route.arg.absolutePath(basePath); 26 if(route.type == "path") 27 { 28 router.get(route.path, serveStaticFiles(targetPath)); 29 } 30 else if(route.type == "file") 31 { 32 router.get(route.path, serveStaticFile(targetPath)); 33 } 34 else throw new Error("Invalid type: " ~ route.type); 35 logger.logInfo("Mapping path: " ~ route.path ~ " with " ~ targetPath); 36 } 37 } 38 39 int runServer(string settingsFileName) 40 { 41 settingsFileName = settingsFileName.absolutePath(thisExePath.dirName); 42 auto basePath = settingsFileName.dirName; 43 44 // Load settings from file 45 auto settingsFileContents = readText(settingsFileName); 46 auto json = parseJSON(settingsFileContents); 47 auto settings = json.fromJSON!Settings; 48 49 settings.logFileName = settings.logFileName.absolutePath(basePath); 50 settings.vibeLogFileName = settings.vibeLogFileName.absolutePath(basePath); 51 52 // Setup loggers 53 setLogFile(settings.vibeLogFileName, settings.vibeLogLevel); 54 auto logger = new shared StrictLogger(settings.logFileName); 55 logger.minOutputLevel = settings.logLevel; 56 57 // Default vibe initialization 58 auto svrSettings = new HTTPServerSettings; 59 svrSettings.port = cast(ushort)settings.port; 60 svrSettings.useCompressionIfPossible = settings.useCompressionIfPossible; 61 svrSettings.bindAddresses = settings.bindAddresses; 62 63 auto router = new URLRouter; 64 logger.logInfo("Loading routes from settings"); 65 loadRouteSpecs(settings.routes, basePath, router, logger); 66 67 listenHTTP(svrSettings, router); 68 69 return runEventLoop(); 70 } 71 72 // Simple daemon description 73 alias daemon = Daemon!( 74 "lt-web-server", // unique name 75 KeyValueList!( 76 Composition!(Signal.Terminate, Signal.Quit, Signal.Shutdown, Signal.Stop), (logger) 77 { 78 logger.logInfo("Exiting..."); 79 exitEventLoop(true); 80 return false; 81 }, 82 Signal.HangUp, (logger) 83 { 84 logger.logInfo("Hang up"); 85 return true; 86 } 87 ), 88 (logger, shouldExit) { return runServer(settingsFileName.dup); } 89 ); 90 91 struct RouteSettings 92 { 93 mixin JsonizeMe; 94 95 @jsonize string type; 96 @jsonize string path; 97 @jsonize string arg; 98 } 99 100 struct Settings 101 { 102 mixin JsonizeMe; 103 104 @jsonize(JsonizeOptional.yes) int port = 3000; 105 @jsonize(JsonizeOptional.yes) string[] bindAddresses = ["::1", "127.0.0.1"]; 106 @jsonize(JsonizeOptional.yes) bool useCompressionIfPossible = true; 107 @jsonize(JsonizeOptional.yes) string logFileName = "general.log"; 108 @jsonize(JsonizeOptional.yes) string vibeLogFileName = "vibe.log"; 109 @jsonize(JsonizeOptional.yes) LoggingLevel logLevel = LoggingLevel.Debug; 110 @jsonize(JsonizeOptional.yes) VibeLogLevel vibeLogLevel = VibeLogLevel.info; 111 @jsonize RouteSettings[] routes; 112 } 113 114 int main(string[] args) 115 { 116 bool noExecAsService = false; 117 string settingsFileNameOpt = settingsFileName; 118 auto helpInformation = getopt(args, 119 "no-service", "No use as system service", &noExecAsService, 120 "settings", "JSON settings file name.", &settingsFileNameOpt); 121 122 if (helpInformation.helpWanted) 123 { 124 defaultGetoptPrinter( 125 "Help about this program. " 126 "Settings file path is relative to this executable", 127 helpInformation.options); 128 return 0; 129 } 130 131 if(!noExecAsService) 132 { 133 auto logger = new shared StrictLogger(thisExePath.dirName.buildPath("lt-web-server-service.log")); 134 return buildDaemon!daemon.run(logger); 135 } 136 137 return runServer(settingsFileNameOpt); 138 }