1 module hunt.http.routing.handler.Util; 2 3 import hunt.http.routing.RoutingContext; 4 import hunt.http.server.HttpServerResponse; 5 import hunt.http.server.HttpServerRequest; 6 7 import hunt.http.HttpHeader; 8 import hunt.http.HttpStatus; 9 10 import hunt.logging; 11 import hunt.Exceptions; 12 import hunt.util.DateTime : TimeUnit; 13 import hunt.util.AcceptMimeType; 14 import hunt.util.MimeTypeUtils; 15 import hunt.util.MimeType; 16 17 import std.algorithm; 18 import std.array; 19 import std.conv; 20 import std.datetime; 21 import std.file; 22 import std.path; 23 import std.format; 24 import std.stdio; 25 import std.string; 26 27 /** 28 * 29 */ 30 struct RoutingHandlerUtils { 31 32 static void downloadFile(RoutingContext context, string requestFile) { 33 HttpServerRequest request = context.getRequest(); 34 HttpServerResponse response = context.getResponse(); 35 DirEntry fileInfo = DirEntry(requestFile); 36 response.header(HttpHeader.ACCEPT_RANGES, "bytes"); 37 38 ulong rangeStart = 0; 39 ulong rangeEnd = 0; 40 41 ulong fileSize = fileInfo.size(); 42 43 if (request.headerExists(HttpHeader.RANGE)) 44 { 45 // https://tools.ietf.org/html/rfc7233 46 // Range can be in form "-\d", "\d-" or "\d-\d" 47 auto range = request.header(HttpHeader.RANGE.asString()).chompPrefix("bytes="); 48 if (range.canFind(',')) 49 { 50 response.setStatus(HttpStatus.NOT_IMPLEMENTED_501); 51 return; 52 } 53 auto s = range.split("-"); 54 55 if (s.length != 2) { 56 response.setStatus(HttpStatus.BAD_REQUEST_400); 57 return; 58 } 59 60 try { 61 if (s[0].length) { 62 rangeStart = s[0].to!ulong; 63 rangeEnd = s[1].length ? s[1].to!ulong : fileSize; 64 } else if (s[1].length) { 65 rangeEnd = fileSize; 66 auto len = s[1].to!ulong; 67 68 if (len >= rangeEnd) 69 rangeStart = 0; 70 else 71 rangeStart = rangeEnd - len; 72 } else { 73 response.setStatus(HttpStatus.BAD_REQUEST_400); 74 return; 75 } 76 } catch (ConvException e) { 77 warning(e.msg); 78 version(HUNT_DEBUG) warning(e); 79 response.setStatus(HttpStatus.BAD_REQUEST_400); 80 } 81 82 if (rangeEnd > fileSize) 83 rangeEnd = fileSize; 84 85 if (rangeStart > rangeEnd) 86 rangeStart = rangeEnd; 87 88 if (rangeEnd) 89 rangeEnd--; // End is inclusive, so one less than length 90 // potential integer overflow with rangeEnd - rangeStart == size_t.max is intended. This only happens with empty files, the + 1 will then put it back to 0 91 92 response.header(HttpHeader.CONTENT_LENGTH, to!string(rangeEnd - rangeStart + 1)); 93 response.header(HttpHeader.CONTENT_RANGE, "bytes %s-%s/%s".format(rangeStart < rangeEnd ? 94 rangeStart : rangeEnd, rangeEnd, fileSize)); 95 response.setStatus(HttpStatus.PARTIAL_CONTENT_206); 96 } 97 else 98 { 99 rangeEnd = fileSize - 1; 100 response.header(HttpHeader.CONTENT_LENGTH, fileSize.to!string); 101 } 102 103 // write out the file contents 104 auto f = std.stdio.File(requestFile, "r"); 105 scope(exit) f.close(); 106 107 f.seek(rangeStart); 108 int remainingSize = rangeEnd.to!uint - rangeStart.to!uint + 1; 109 if(remainingSize <= 0) { 110 warningf("actualSize:%d, remainingSize=%d", fileSize, remainingSize); 111 } else { 112 auto buf = f.rawRead(new ubyte[remainingSize]); 113 // response.setContent(buf); 114 context.write(cast(byte[])buf); 115 } 116 } 117 118 static void renderFileContent(RoutingContext context, string requestFile, size_t bufferSize = 8*1024) { 119 120 string title = "Content for: " ~ requestFile; 121 122 ubyte[] buffer; 123 File f = File(requestFile, "r"); 124 ulong total = f.size(); 125 126 scope(exit) f.close(); 127 128 ulong remaining = total; 129 while(remaining > 0 && !f.eof()) { 130 131 if(remaining > bufferSize) 132 buffer = new ubyte[bufferSize]; 133 else 134 buffer = new ubyte[cast(size_t)remaining]; 135 ubyte[] data = f.rawRead(buffer); 136 137 if(data.length > 0) { 138 context.write(cast(byte[])data); 139 remaining -= data.length; 140 } 141 version(HUNT_HTTP_DEBUG_MORE) { 142 tracef("read: %s, remaining: %d, eof: %s", 143 data.length, remaining, f.eof()); 144 } 145 } 146 } 147 148 }