1 /+ 2 This file is part of Reloaded Vibes. 3 Copyright (c) 2019 0xEAB 4 5 Distributed under the Boost Software License, Version 1.0. 6 (See accompanying file LICENSE_1_0.txt or copy at 7 https://www.boost.org/LICENSE_1_0.txt) 8 +/ 9 module reloadedvibes.server; 10 11 import std.file : exists; 12 import std.datetime : dur; 13 import std.stdio : File; 14 import std.string : endsWith; 15 16 import vibe.core.core; 17 import vibe.core.path; 18 import vibe.core.sync; 19 import vibe.http.fileserver; 20 import vibe.http.router; 21 import vibe.http.server; 22 import vibe.http.websockets; 23 24 import reloadedvibes.script; 25 import reloadedvibes.utils; 26 import reloadedvibes.watcher; 27 28 static TaskMutex tm; 29 30 static this() 31 { 32 tm = new TaskMutex(); 33 } 34 35 HTTPListener registerService(Socket s, Watcher w) 36 { 37 void index(scope HTTPServerRequest, scope HTTPServerResponse res) @safe 38 { 39 immutable scriptTag = s.buildScriptLoaderHTML(); 40 render!("index.dt", scriptTag)(res); 41 } 42 43 void script(scope HTTPServerRequest req, scope HTTPServerResponse res) 44 { 45 immutable disableMsg = (("quiet" in req.query()) !is null); 46 res.writeBody(s.buildScript(disableMsg), 200, "application/javascript; charset=utf-8"); 47 } 48 49 void test(scope HTTPServerRequest, scope HTTPServerResponse res) 50 { 51 immutable scriptURL = s.buildScriptURL(); 52 render!("test.dt", scriptURL)(res); 53 } 54 55 void webSocket(scope WebSocket ws) 56 { 57 if (ws.connected) 58 { 59 auto msg = ws.receiveText(); 60 if (msg != "ReloadedVibes::Init;") 61 { 62 ws.close(WebSocketCloseReason.unsupportedData); 63 return; 64 } 65 ws.send(msg); 66 } 67 68 WatcherClient wcl = new WatcherClient(w); 69 70 do 71 { 72 synchronized (tm) 73 { 74 if (wcl.query()) 75 { 76 ws.send("ReloadedVibes::Trigger;"); 77 } 78 } 79 sleep(dur!"msecs"(200)); 80 } 81 while (ws.connected); 82 83 wcl.unregister(); 84 } 85 86 auto router = new URLRouter(); 87 router.get("/", &index); 88 router.get("/test", &test); 89 router.get("/reloaded-vibes.js", &script); 90 router.get("/reloaded-vibes.ws", handleWebSockets(&webSocket)); 91 92 auto settings = new HTTPServerSettings(); 93 settings.port = s.port; 94 settings.bindAddresses = [s.address]; 95 return listenHTTP(settings, router); 96 } 97 98 HTTPListener registerStaticWebserver(Socket s, string docroot) @safe 99 { 100 auto settings = new HTTPServerSettings(); 101 settings.port = s.port; 102 settings.bindAddresses = [s.address]; 103 return listenHTTP(settings, serveStaticFiles(docroot)); 104 } 105 106 HTTPListener registerStaticWebserver(Socket s, string docroot, Socket nfService) @safe 107 { 108 immutable htd = NativePath(docroot); 109 110 void injectingServer(scope HTTPServerRequest req, scope HTTPServerResponse res) @trusted 111 { 112 auto p = req.requestPath; 113 string pString = p.toString; 114 115 if (pString.endsWith("/")) 116 { 117 p = InetPath(pString[1 .. $] ~ "index.html"); 118 } 119 else 120 { 121 p = InetPath(pString[1 .. $]); 122 } 123 124 try 125 { 126 p.normalize(); 127 } 128 catch (Exception) 129 { 130 res.statusCode = 400; 131 } 132 133 if (p.absolute) 134 { 135 res.statusCode = 500; 136 return; 137 } 138 139 if (!p.empty && p.bySegment.front.name == "..") 140 { 141 res.statusCode = 400; 142 return; 143 } 144 145 NativePath file = (htd ~ p); 146 immutable fileS = file.toString; 147 if (fileS.exists && fileS.endsWith(".html")) 148 { 149 res.headers["Content-Type"] = "text/html"; 150 auto b = res.bodyWriter; 151 152 auto f = File(file.toString, "r"); 153 ubyte[1] buffer; 154 155 Outer: while (!f.eof) 156 { 157 if (f.rawRead(buffer).length == 0) 158 { 159 break; 160 } 161 if (buffer[0] != '<') 162 { 163 b.write(buffer); 164 continue; 165 } 166 167 static immutable chars = "/body>"; 168 ubyte[6] buffer2; 169 170 static foreach (i, c; chars) 171 { 172 if (f.rawRead(buffer2[i .. (i + 1)]).length == 0) 173 { 174 b.write(buffer); 175 b.write(buffer2[0 .. i]); 176 break Outer; 177 } 178 if (buffer2[i] != c) 179 { 180 b.write(buffer); 181 b.write(buffer2[0 .. (i + 1)]); 182 continue Outer; 183 } 184 } 185 186 // inject 187 b.write(cast(ubyte[])(nfService.buildScriptLoaderHTML())); 188 b.write(buffer); 189 b.write(buffer2); 190 } 191 192 return; 193 } 194 195 sendFile(req, res, file); 196 } 197 198 auto settings = new HTTPServerSettings(); 199 settings.port = s.port; 200 settings.bindAddresses = [s.address]; 201 202 return listenHTTP(settings, &injectingServer); 203 } 204 205 void run(HTTPListeners)(HTTPListeners listeners) 206 { 207 scope (exit) 208 { 209 foreach (HTTPListener h; listeners) 210 { 211 h.stopListening(); 212 } 213 } 214 runEventLoop(); 215 }