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 }