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 std.json;
14 import std.file;
15 import std.path;
16 
17 void loadRouteSpecs(string settings, URLRouter router, shared ILogger logger) {
18     auto json = parseJSON(settings);
19     if (json.type != JSON_TYPE.OBJECT) throw new Error("Settings file must contain an object");
20     auto routes = json["routes"].array;
21     foreach(route; routes)
22     {
23         if (route.type != JSON_TYPE.OBJECT) throw new Error("Settings file: each route must be an object");
24         auto path = route["path"].str;
25         auto type = route["type"].str;
26         auto arg = route["arg"].str;
27 
28         if(type == "path") 
29 		{
30             auto targetPath = thisExePath.dirName.buildPath(arg);
31 			router.get(path, serveStaticFiles(targetPath)); 
32 			logger.logInfo("Mapping path: " ~ path ~ " with " ~ targetPath);
33 		}
34         else if(type == "file") 
35 		{
36             auto targetPath = thisExePath.dirName.buildPath(arg);
37 			router.get(path, serveStaticFile(targetPath));
38 			logger.logInfo("Mapping path: " ~ path ~ " with " ~ targetPath);
39 		}
40         else throw new Error("Invalid type: " ~ type);
41     }
42 }
43 
44 void setupServer(shared ILogger logger, bool function() shouldExit)
45 {
46     // Default vibe initialization
47     auto settings = new HTTPServerSettings;
48     settings.port = 8080;
49     settings.useCompressionIfPossible = true;
50     settings.bindAddresses = ["::1", "127.0.0.1"];
51 
52     auto router = new URLRouter;
53     auto settingsFileName = thisExePath.dirName.buildPath("web.json");
54     logger.logInfo("Loading settings file: " ~ settingsFileName);
55     auto settingsFileContents = readText(settingsFileName);
56     loadRouteSpecs(settingsFileContents, router, logger);
57 
58     listenHTTP(settings, router);
59 }
60 
61 // Simple daemon description
62 alias daemon = Daemon!(
63     "lt-web-server", // unique name
64     KeyValueList!(
65         Composition!(Signal.Terminate, Signal.Quit, Signal.Shutdown, Signal.Stop), (logger)
66         {
67             logger.logInfo("Exiting...");
68             
69             // No need to force exit here
70             // main will stop after the call 
71             exitEventLoop(true);
72             return false; 
73         },
74         Signal.HangUp, (logger)
75         {
76             logger.logInfo("Hang up");
77             return true;
78         }
79     ),
80     
81     (logger, shouldExit) {
82         setupServer(logger, shouldExit);
83 
84         // All exceptions are caught by daemonize
85         return runEventLoop();
86     }
87 );
88 
89 int main(string[] args)
90 {
91     bool noService = args.length > 1 && args[1] == "--no-svc";
92 
93     // Setting vibe logger 
94     // daemon closes stdout/stderr and vibe logger will crash
95     // if not suppress printing to console
96     version(Windows) auto vibeLogName = (noService ? "" : "C:\\" ) ~ "server-access.log";
97     else enum vibeLogName = "server-access.log";
98 
99     // no stdout/stderr output
100     version(Windows) {}
101     else setLogLevel(VibeLogLevel.none);
102 
103     setLogFile(vibeLogName, VibeLogLevel.info);
104 
105     version(Windows) auto logFileName = (noService ? "" : "C:\\" ) ~ "logfile.log";
106     else enum logFileName = "logfile.log";
107 
108     auto logger = new shared StrictLogger(logFileName);
109     logger.minOutputLevel = LoggingLevel.Debug;
110 
111     if (noService) {
112         setupServer(logger, null);
113         return runEventLoop();
114     }
115 
116     return buildDaemon!daemon.run(logger); 
117 }