1 module handy_httpd.handlers.file_resolving_handler; 2 3 import std.stdio; 4 5 import handy_httpd.handler; 6 import handy_httpd.request; 7 import handy_httpd.response; 8 import handy_httpd.responses; 9 10 /** 11 * Request handler that resolves files within a given base path. 12 */ 13 class FileResolvingHandler : HttpRequestHandler { 14 /** 15 * The base path within which to resolve files. 16 */ 17 private string basePath; 18 19 /** 20 * Associative array containing mime type mappings for file extensions. 21 */ 22 private string[string] mimeTypes; 23 24 /** 25 * Constructs the request handler. 26 * Params: 27 * basePath = The path to use to resolve files in. 28 */ 29 this(string basePath = ".") { 30 this.basePath = basePath; 31 this.mimeTypes = [ 32 ".html": "text/html", 33 ".js": "text/javascript", 34 ".css": "text/css", 35 ".json": "application/json", 36 ".png": "image/png", 37 ".jpg": "image/jpg", 38 ".gif": "image/gif", 39 ".webp": "image/webp", 40 ".wav": "audio/wav", 41 ".ogg": "audio/ogg", 42 ".mp3": "audio/mpeg", 43 ".mp4": "video/mp4", 44 ".woff": "application/font-woff", 45 ".ttf": "application/font-ttf", 46 ".eot": "application/vnd.ms-fontobject", 47 ".otf": "application/font-otf", 48 ".svg": "application/image/svg+xml", 49 ".wasm": "application/wasm", 50 ".pdf": "application/pdf", 51 ".txt": "text/plain", 52 ".xml": "application/xml" 53 ]; 54 } 55 56 void handle(ref HttpRequest request, ref HttpResponse response) { 57 if (request.server.isVerbose()) { 58 writefln!"Resolving file for url %s..."(request.url); 59 } 60 string path = sanitizeRequestPath(request.url); 61 if (path != null) { 62 response.fileResponse(path, getMimeType(path)); 63 } else { 64 if (request.server.isVerbose()) { 65 writefln!"Could not resolve file for url %s. Maybe it doesn't exist?"(request.url); 66 } 67 response.notFound(); 68 } 69 } 70 71 /** 72 * Registers a new mime type for this handler. 73 * Params: 74 * fileExtension = The file extension to use, including the '.' separator. 75 * mimeType = The mime type that will be assigned to the given file extension. 76 * Returns: The handler, for method chaining. 77 */ 78 public FileResolvingHandler registerMimeType(string fileExtension, string mimeType) { 79 mimeTypes[fileExtension] = mimeType; 80 return this; 81 } 82 83 /** 84 * Sanitizes a request url such that it points to a file within the 85 * configured base path for this handler. 86 * Params: 87 * url = The url to sanitize. 88 * Returns: A string representing the file pointed to by the given url, 89 * or null if no valid file could be found. 90 */ 91 private string sanitizeRequestPath(string url) { 92 import std.path : buildNormalizedPath; 93 import std.file : exists, isDir; 94 import std.regex; 95 if (url.length == 0 || url == "/") return this.basePath ~ "/index.html"; 96 string normalized = this.basePath ~ "/" ~ buildNormalizedPath(url[1 .. $]); 97 98 if (!exists(normalized)) return null; 99 // If the user has requested a directory, try and serve "index.html" from it. 100 if (isDir(normalized)) { 101 normalized ~= "/index.html"; 102 if (!exists(normalized)) return null; 103 } 104 return normalized; 105 } 106 107 /** 108 * Tries to determine the mime type of a file. Defaults to "text/html" for 109 * files of an unknown type. 110 * Params: 111 * filename = The name of the file to determine mime type for. 112 * Returns: A mime type string. 113 */ 114 private string getMimeType(string filename) { 115 import std.string : lastIndexOf; 116 import std.uni : toLower; 117 auto p = filename.lastIndexOf('.'); 118 if (p == -1) return "text/html"; 119 string extension = filename[p..$].toLower(); 120 if (extension !in this.mimeTypes) { 121 writefln!"Warning: Unknown mime type for file extension %s"(extension); 122 return "text/plain"; 123 } 124 return this.mimeTypes[extension]; 125 } 126 }