1 #pragma once 2 3 #include <experimental/filesystem> 4 #include <fstream> 5 #include <string> 6 #include <crow/app.h> 7 #include <crow/http_request.h> 8 #include <crow/http_response.h> 9 #include <crow/routing.h> 10 #include <boost/algorithm/string/replace.hpp> 11 #include <boost/container/flat_set.hpp> 12 13 namespace crow { 14 namespace webassets { 15 16 namespace filesystem = std::experimental::filesystem; 17 18 struct cmp_str { 19 bool operator()(const char* a, const char* b) const { 20 return std::strcmp(a, b) < 0; 21 } 22 }; 23 24 static boost::container::flat_set<std::string> routes; 25 26 template <typename... Middlewares> 27 void request_routes(Crow<Middlewares...>& app) { 28 const static boost::container::flat_map<const char*, const char*, cmp_str> 29 content_types{ 30 {{".css", "text/css;charset=UTF-8"}, 31 {".html", "text/html;charset=UTF-8"}, 32 {".js", "text/html;charset=UTF-8"}, 33 {".png", "image/png;charset=UTF-8"}, 34 {".woff", "application/x-font-woff"}, 35 {".woff2", "application/x-font-woff2"}, 36 {".ttf", "application/x-font-ttf"}, 37 {".svg", "image/svg+xml"}, 38 {".eot", "application/vnd.ms-fontobject"}, 39 {".xml", "application/xml"}, 40 // dev tools don't care about map type, setting to json causes 41 // browser to show as text 42 // https://stackoverflow.com/questions/19911929/what-mime-type-should-i-use-for-javascript-source-map-files 43 {".map", "application/json"}}}; 44 auto rootpath = filesystem::path("/usr/share/www/"); 45 auto dir_iter = filesystem::recursive_directory_iterator(rootpath); 46 for (auto& dir : dir_iter) { 47 auto absolute_path = dir.path(); 48 auto absolute_path_str = dir.path().string(); 49 auto relative_path = filesystem::path( 50 absolute_path.string().substr(rootpath.string().size() - 1)); 51 // make sure we don't recurse into certain directories 52 // note: maybe check for is_directory() here as well... 53 if (filesystem::is_directory(dir)) { 54 // don't recurse into hidden directories or symlinks 55 if (boost::starts_with(dir.path().filename().string(), ".") || 56 filesystem::is_symlink(dir)) { 57 dir_iter.disable_recursion_pending(); 58 } 59 } else if (filesystem::is_regular_file(dir)) { 60 auto webpath = relative_path; 61 bool is_gzip = false; 62 if (relative_path.extension() == ".gz") { 63 webpath = webpath.replace_extension(""); 64 is_gzip = true; 65 } 66 67 if (webpath.filename() == "index.html") { 68 webpath = webpath.parent_path(); 69 if (webpath.string() != "/") { 70 webpath += "/"; 71 } 72 } 73 74 routes.insert(webpath.string()); 75 76 std::string absolute_path_str = absolute_path.string(); 77 const char* content_type = nullptr; 78 auto content_type_it = content_types.find(webpath.extension().c_str()); 79 if (content_type_it != content_types.end()) { 80 content_type = content_type_it->second; 81 } else { 82 if (webpath.string() == "$metadata"){ 83 content_type_it = content_types.find(".xml"); 84 // should always be true 85 if (content_type_it != content_types.end()) { 86 content_type = content_type_it->second; 87 } 88 } 89 } 90 app.route_dynamic(std::string(webpath.string()))( 91 [is_gzip, absolute_path_str, content_type](const crow::request& req, 92 crow::response& res) { 93 static const char* content_type_string = "Content-Type"; 94 // std::string sha1("a576dc96a5c605b28afb032f3103630d61ac1068"); 95 // res.add_header("ETag", sha1); 96 97 // if (req.get_header_value("If-None-Match") == sha1) { 98 // res.code = 304; 99 //} else { 100 // res.code = 200; 101 // TODO, if you have a browser from the dark ages that doesn't 102 // support 103 // gzip, unzip it before sending based on Accept-Encoding header 104 // res.add_header("Content-Encoding", gzip_string); 105 if (content_type != nullptr) { 106 res.add_header(content_type_string, content_type); 107 } 108 109 if (is_gzip) { 110 res.add_header("Content-Encoding", "gzip"); 111 } 112 113 // res.set_header("Cache-Control", "public, max-age=86400"); 114 std::ifstream inf(absolute_path_str); 115 if (!inf) { 116 CROW_LOG_DEBUG << "failed to read file"; 117 res.code = 400; 118 res.end(); 119 return; 120 } 121 122 std::string body{std::istreambuf_iterator<char>(inf), 123 std::istreambuf_iterator<char>()}; 124 125 res.body = body; 126 res.end(); 127 }); 128 } 129 } 130 } 131 } // namespace webassets 132 } // namespace crow