1 /// Written in the D programming language. 2 3 module semitwistWeb.init; 4 5 import std.getopt; 6 import std.stdio; 7 import core.memory; 8 9 import vibe.vibe; 10 import vibe.core.args; 11 import vibe.core.connectionpool; 12 import mysql.db; 13 import sdlang.exception; 14 15 import semitwist.util.all; 16 17 import semitwistWeb.conf; 18 import semitwistWeb.db; 19 import semitwistWeb.doc; 20 import semitwistWeb.handler; 21 import semitwistWeb.session; 22 import semitwistWeb.util; 23 24 bool initDB = false; 25 bool clearSessions = false; 26 bool noCacheStatic = false; 27 bool noStatic = false; 28 ushort port = 8080; 29 string[] bindAddresses; 30 string logFile = ""; 31 32 //TODO: Find/create a tool to monitor the logfile and send emails for each new entry. 33 34 alias int function(ref HTTPServerSettings, ref URLRouter) CustomPostInit; 35 36 // This is a modification of vibe.d's built-in main(). 37 int semitwistWebMain(CustomSession, CustomHandler, UserDBOTypes...) 38 (string[] args, CustomPostInit customPostInit, LockedConnection!Connection delegate() openDB) 39 { 40 debug runDocHelperUnittest(); 41 42 dbHelperOpenDB = openDB; 43 BaseHandler.addAppContextCallback = &CustomHandler.addAppContext; 44 45 if(auto errlvl = processCustomCmdLine(args) != -1) 46 return errlvl; 47 48 try 49 loadConf(); 50 catch(SDLangException e) 51 { 52 stderr.writeln(e.msg); 53 return 1; 54 } 55 56 if(initDB) 57 { 58 initializeDB(); 59 return 0; 60 } 61 62 if(auto errlvl = init!(CustomSession, CustomHandler, UserDBOTypes)(customPostInit) != -1) 63 return errlvl; 64 65 stLogInfo("Running event loop..."); 66 try { 67 return runEventLoop(); 68 } catch( Throwable th ){ 69 stLogError("Unhandled exception in event loop: ", th); 70 return 1; 71 } 72 } 73 74 /// Returns: -1 normally, or else errorlevel to exit with 75 private int processCustomCmdLine(ref string[] args) 76 { 77 readOption("init-db", &initDB, "Init the DB and exit (THIS WILL DESTROY ALL DATA!)"); 78 readOption("clear-sessions", &clearSessions, "Upon startup, clear sessions in DB insetad of resuming them."); 79 readOption("port", &port, "Port to bind."); 80 string bindAddress; 81 while(readOption("ip", &bindAddress, "IP address to bind. (Can be specified multiple times)")) 82 bindAddresses ~= bindAddress; 83 readOption("no-cache", &BaseHandler.noCache, "Disable internal page caching. (Useful during development)"); 84 readOption("no-cache-static", &noCacheStatic, "Set HTTP headers on static files to disable caching. (Useful during development)"); 85 readOption("no-static", &noStatic, "Disable serving of static files."); 86 readOption("log", &logFile, "Set logfile."); 87 88 readOption("insecure", &BaseHandler.allowInsecure, "Allow non-HTTPS requests. Implies --insecure-cookies"); 89 readOption("insecure-cookies", &useInsecureCookies, "Don't set SECURE attribute on session cookies."); 90 readOption("public-debug-info", &BaseHandler.publicDebugInfo, "Display uncaught exceptions and stack traces to user. (Useful during development)"); 91 readOption("log-sql", &dbHelperLogSql, "Log all SQL statements executed. (Useful during development)"); 92 93 try 94 { 95 if(!finalizeCommandLineOptions()) 96 return 0; 97 } 98 catch(Exception e) 99 { 100 stLogError("Error processing command line: ", e.msg); 101 return 1; 102 } 103 104 if(bindAddresses.length == 0) 105 bindAddresses = ["0.0.0.0", "::"]; 106 107 if(BaseHandler.allowInsecure) 108 useInsecureCookies = true; 109 110 setLogFormat(FileLogger.Format.threadTime); 111 if(logFile != "") 112 setLogFile(logFile, LogLevel.info); 113 114 return -1; 115 } 116 117 /// Returns: -1 normally, or else errorlevel to exit with 118 private int init(CustomSession, CustomHandler, UserDBOTypes...) 119 (CustomPostInit customPostInit) 120 { 121 version(RequireSecure) 122 { 123 if(Handler.allowInsecure || useInsecureCookies || Handler.publicDebugInfo) 124 { 125 stLogError("This was compiled with -version=RequireSecure, therefore the following flags are disabled: --insecure --insecure-cookies --public-debug-info"); 126 return 1; 127 } 128 } 129 130 // Warn about --insecure-cookies 131 if(useInsecureCookies) 132 stLogWarn("Used --insecure-cookies: INSECURE cookies are ON! Session cookies will not use the Secure attribute!"); 133 134 // Warn about --insecure 135 if(BaseHandler.allowInsecure) 136 stLogWarn("Used --insecure: INSECURE mode is ON! HTTPS will NOT be forced!"); 137 138 // Warn about HTTP 139 if(conf.host.toLower().startsWith("http://")) 140 { 141 if(BaseHandler.allowInsecure) 142 stLogWarn( 143 "Non-relative URLs are set to HTTP, not HTTPS! ", 144 "If you did not intend this, change conf.host and recompile." 145 ); 146 else 147 { 148 // Require --insecure for non-HTTPS 149 stLogError( 150 "conf.host is HTTP instead of HTTPS. THIS IS NOT RECOMMENDED. ", 151 "If you wish to allow this anyway, you must use the --insecure flag. ", 152 "Note that this will cause non-relative application URLs to be HTTP instead of HTTPS." 153 ); 154 return 1; 155 } 156 } 157 158 { 159 scope(failure) 160 { 161 stLogError( 162 "There was an error building the basic pages.\n", 163 import("dbTroubleshootMsg.txt") 164 ); 165 } 166 167 auto dbConn = dbHelperOpenDB(); 168 169 stLogInfo("Preloading db cache..."); 170 rebuildDBCache!UserDBOTypes(dbConn); 171 172 if(clearSessions) 173 { 174 stLogInfo("Clearing persistent sessions..."); 175 SessionDB.dbDeleteAll(dbConn); 176 return 0; 177 } 178 179 stLogInfo("Restoring sessions..."); 180 sessionStore = new MemorySessionStore(); 181 restoreSessions!CustomSession(dbConn); 182 } 183 184 stLogInfo("Initing HTTP server settings..."); 185 alias handlerDispatchError!CustomHandler customHandlerDispatchError; 186 auto httpServerSettings = new HTTPServerSettings(); 187 httpServerSettings.port = port; 188 httpServerSettings.bindAddresses = bindAddresses; 189 httpServerSettings.sessionStore = sessionStore; 190 httpServerSettings.errorPageHandler = 191 (req, res, err) => customHandlerDispatchError!"errorHandler"(req, res, err); 192 193 stLogInfo("Initing URL router..."); 194 auto router = initRouter!CustomHandler(); 195 196 if(customPostInit !is null) 197 { 198 stLogInfo("Running customPostInit..."); 199 if(auto errlvl = customPostInit(httpServerSettings, router) != -1) 200 return errlvl; 201 } 202 203 stLogInfo("Forcing GC cycle..."); 204 GC.collect(); 205 GC.minimize(); 206 207 stLogInfo("Done initing SemiTwist Web Framework"); 208 listenHTTP(httpServerSettings, router); 209 return -1; 210 } 211 212 private URLRouter initRouter(CustomHandler)() 213 { 214 auto router = new URLRouter(); 215 216 foreach(pageName; PageBase.getNames()) 217 router.addPage( PageBase.get(pageName) ); 218 219 /// If you're serving the static files directly (for example, through a 220 /// reverse proxy), you can prevent this application from serving them 221 /// with --no-static 222 if(!noStatic) 223 { 224 auto localPath = getExecPath ~ conf.staticsRealPath; 225 226 auto fss = new HTTPFileServerSettings(); 227 //fss.failIfNotFound = true; // Setting isn't in latest vibe.d 228 fss.serverPathPrefix = conf.staticsUrl; 229 230 if(noCacheStatic) 231 { 232 fss.maxAge = seconds(1); 233 fss.preWriteCallback = (scope req, scope res, ref physicalPath) { 234 res.headers.remove("Etag"); 235 res.headers["Cache-Control"] = "no-store"; 236 }; 237 } 238 else 239 fss.maxAge = hours(24); 240 241 router.get(conf.staticsUrl~"*", serveStaticFiles(localPath, fss)); 242 } 243 244 alias handlerDispatch!CustomHandler customHandlerDispatch; 245 router.get ("*", &customHandlerDispatch!"notFound"); 246 router.post("*", &customHandlerDispatch!"notFound"); 247 248 return router; 249 }