1 module vibeauth.router.resources.routes; 2 3 import std.socket; 4 import std.net.curl; 5 import std.file; 6 import std.path; 7 import std.digest.md; 8 import std.algorithm; 9 import std.algorithm.mutation : copy; 10 11 import core.time; 12 13 import vibe.core.log; 14 import vibe.core.file; 15 import vibe.http.server; 16 import vibe.stream.operations; 17 import vibeauth.configuration; 18 19 version(unittest) { 20 import vibe.http.router; 21 import fluentasserts.vibe.request; 22 import fluentasserts.vibe.json; 23 import fluent.asserts; 24 } 25 26 enum bootstrapStyleUrl = "https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"; 27 enum localBootstrapStyle = "tmp/assets/bootstrap.min.css"; 28 29 enum bootstrapJsUrl = "https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js"; 30 enum localBootstrapJs = "tmp/assets/bootstrap.min.js"; 31 32 enum jqueryUrl = "https://code.jquery.com/jquery-3.3.1.slim.min.js"; 33 enum localJquery = "tmp/assets/jquery.min.js"; 34 35 shared static this() { 36 if(!localBootstrapStyle.exists) { 37 mkdirRecurse(localBootstrapStyle.dirName); 38 download(bootstrapStyleUrl, localBootstrapStyle); 39 } 40 41 42 if(!localBootstrapJs.exists) { 43 mkdirRecurse(localBootstrapJs.dirName); 44 download(bootstrapJsUrl, localBootstrapJs); 45 } 46 47 if(!localJquery.exists) { 48 mkdirRecurse(localJquery.dirName); 49 download(jqueryUrl, localJquery); 50 } 51 52 version(unittest) { 53 std.file.write("tmp/test.css", "data"); 54 std.file.write("tmp/test.js", "data"); 55 } 56 } 57 58 /// Static resource 59 struct Resource(string path) { 60 static immutable { 61 string etag; 62 string data; 63 string mime; 64 } 65 66 /// 67 static this() { 68 auto ctx = makeDigest!MD5(); 69 70 data = readText(path); 71 copy(data, &ctx); //Note: You must pass a pointer to copy! 72 etag = ctx.finish().toHexString.idup; 73 74 if(path.endsWith(".css")) { 75 mime = "text/css"; 76 } 77 78 if(path.endsWith(".js")) { 79 mime = "text/javascript"; 80 } 81 } 82 83 /// 84 void handler(HTTPServerRequest req, HTTPServerResponse res) { 85 res.headers["Cache-Control"] = "max-age=86400"; 86 res.headers["ETag"] = etag; 87 88 if("If-None-Match" in req.headers && req.headers["If-None-Match"] == etag) { 89 res.statusCode = 304; 90 res.writeVoidBody; 91 return; 92 } 93 94 res.statusCode = 200; 95 res.writeBody(data, mime); 96 } 97 } 98 99 /// It should store the the valid properties 100 unittest { 101 Resource!"tmp/test.css" resource; 102 resource.data.should.equal("data"); 103 resource.mime.should.equal("text/css"); 104 resource.etag.should.equal("8D777F385D3DFEC8815D20F7496026DC"); 105 106 Resource!"tmp/test.js" resourceJs; 107 resourceJs.mime.should.equal("text/javascript"); 108 } 109 110 /// It should get a resource with the right headers 111 unittest { 112 Resource!"tmp/test.css" resource; 113 114 auto router = new URLRouter(); 115 router.get("/resource", &resource.handler); 116 117 router 118 .request 119 .get("/resource") 120 .expectHeader("ETag", resource.etag) 121 .expectHeader("Content-Type", "text/css") 122 .expectStatusCode(200) 123 .end((Response response) => { 124 response.bodyString.should.equal("data"); 125 }); 126 } 127 128 /// It should not get a resource with the etag matches 129 unittest { 130 Resource!"tmp/test.css" resource; 131 132 auto router = new URLRouter(); 133 router.get("/resource", &resource.handler); 134 135 router 136 .request 137 .get("/resource") 138 .header("If-None-Match", resource.etag) 139 .expectHeader("ETag", resource.etag) 140 .expectStatusCode(304) 141 .end((Response response) => { 142 response.bodyString.should.equal(""); 143 }); 144 } 145 146 /// Handle resources served by vibe auth 147 class ResourceRoutes { 148 149 private { 150 const { 151 ServiceConfiguration configuration; 152 } 153 154 Resource!localBootstrapStyle _bootstrapStyle; 155 Resource!localBootstrapJs _bootstrapJs; 156 Resource!localJquery _jquery; 157 } 158 159 /// 160 this(const ServiceConfiguration configuration) { 161 this.configuration = configuration; 162 _bootstrapStyle = Resource!localBootstrapStyle(); 163 _bootstrapJs = Resource!localBootstrapJs(); 164 _jquery = Resource!localJquery(); 165 } 166 167 /// Handle the requests 168 void handler(HTTPServerRequest req, HTTPServerResponse res) { 169 if(req.method == HTTPMethod.GET && req.path == configuration.paths.resources.bootstrapStyle) { 170 _bootstrapStyle.handler(req, res); 171 } 172 173 if(req.method == HTTPMethod.GET && req.path == configuration.paths.resources.bootstrapJs) { 174 _bootstrapJs.handler(req, res); 175 } 176 177 if(req.method == HTTPMethod.GET && req.path == configuration.paths.resources.jquery) { 178 _jquery.handler(req, res); 179 } 180 } 181 } 182