1 /* 2 * Archttp - A highly performant web framework written in D. 3 * 4 * Copyright (C) 2021-2022 Kerisy.com 5 * 6 * Website: https://www.kerisy.com 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module archttp.HttpResponse; 13 14 import archttp.HttpStatusCode; 15 import archttp.HttpContext; 16 import archttp.Cookie; 17 import archttp.HttpHeader; 18 19 import nbuff; 20 21 import geario.util.DateTime; 22 import geario.logging; 23 24 import std.format; 25 import std.array; 26 import std.conv : to; 27 import std.json; 28 29 30 class HttpResponse 31 { 32 private 33 { 34 ushort _statusCode = HttpStatusCode.OK; 35 string[string] _headers; 36 string _body; 37 ubyte[] _buffer; 38 HttpContext _httpContext; 39 Cookie[string] _cookies; 40 41 // for .. 42 bool _headersSent = false; 43 } 44 45 public: 46 /* 47 * Construct an empty response. 48 */ 49 this(HttpContext ctx) 50 { 51 _httpContext = ctx; 52 } 53 54 bool headerSent() 55 { 56 return _headersSent; 57 } 58 59 HttpResponse header(string header, string value) 60 { 61 _headers[header] = value; 62 63 return this; 64 } 65 66 HttpResponse code(HttpStatusCode statusCode) 67 { 68 _statusCode = statusCode; 69 70 return this; 71 } 72 73 ushort code() 74 { 75 return _statusCode; 76 } 77 78 HttpResponse cookie(string name, string value, string path = "/", string domain = "", string expires = "", long maxAge = 604800, bool secure = false, bool httpOnly = false) 79 { 80 _cookies[name] = new Cookie(name, value, path, domain, expires, maxAge, secure, httpOnly); 81 return this; 82 } 83 84 HttpResponse cookie(Cookie cookie) 85 { 86 _cookies[cookie.name()] = cookie; 87 return this; 88 } 89 90 Cookie cookie(string name) 91 { 92 return _cookies.get(name, null); 93 } 94 95 void send(string body) 96 { 97 _body = body; 98 send(); 99 } 100 101 void send(JSONValue json) 102 { 103 _body = json.toString(); 104 105 header("Content-Type", "application/json"); 106 send(); 107 } 108 109 void send() 110 { 111 if (_headersSent) 112 { 113 LogErrorf("Can't set headers after they are sent"); 114 return; 115 } 116 117 if (sendHeader()) 118 sendBody(); 119 } 120 121 HttpResponse json(JSONValue json) 122 { 123 _body = json.toString(); 124 125 header("Content-Type", "application/json"); 126 127 return this; 128 } 129 130 HttpResponse location(HttpStatusCode statusCode, string path) 131 { 132 code(statusCode); 133 location(path); 134 135 return this; 136 } 137 138 HttpResponse location(string path) 139 { 140 redirect(HttpStatusCode.SEE_OTHER, path); 141 header("Location", path); 142 return this; 143 } 144 145 HttpResponse redirect(HttpStatusCode statusCode, string path) 146 { 147 location(statusCode, path); 148 return this; 149 } 150 151 HttpResponse redirect(string path) 152 { 153 redirect(HttpStatusCode.FOUND, path); 154 return this; 155 } 156 157 HttpResponse sendFile(string path, string filename = "") 158 { 159 import std.stdio : File; 160 161 auto file = File(path, "r"); 162 auto fileSize = file.size(); 163 164 if (filename.length == 0) 165 { 166 import std.array : split; 167 import std.string : replace; 168 169 auto parts = path.replace("\\", "/").split("/"); 170 if (parts.length == 1) 171 { 172 filename = path; 173 } 174 else 175 { 176 filename = parts[parts.length - 1]; 177 } 178 } 179 180 header(HttpHeader.CONTENT_DISPOSITION, "attachment; filename=" ~ filename ~ "; size=" ~ fileSize.to!string); 181 header(HttpHeader.CONTENT_LENGTH, fileSize.to!string); 182 183 _httpContext.Write(headerToString()); 184 _headersSent = true; 185 186 auto buf = Nbuff.get(fileSize); 187 file.rawRead(buf.data()); 188 189 _httpContext.Write(NbuffChunk(buf, fileSize)); 190 reset(); 191 192 return this; 193 } 194 195 void end() 196 { 197 reset(); 198 _httpContext.End(); 199 } 200 201 private bool sendHeader() 202 { 203 if (_headersSent) 204 { 205 LogErrorf("Can't set headers after they are sent"); 206 return false; 207 } 208 209 // if (_httpContext.keepAlive() && this.header(HttpHeader.CONTENT_LENGTH).length == 0) 210 header(HttpHeader.CONTENT_LENGTH, _body.length.to!string); 211 212 _httpContext.Write(headerToString()); 213 _headersSent = true; 214 215 return true; 216 } 217 218 private void sendBody() 219 { 220 _httpContext.Write(_body); 221 reset(); 222 } 223 224 void reset() 225 { 226 // clear request object 227 _httpContext.request().reset(); 228 229 _statusCode = HttpStatusCode.OK; 230 _headers = null; 231 _body = null; 232 _buffer = null; 233 _cookies = null; 234 _headersSent = false; 235 } 236 237 string headerToString() 238 { 239 header(HttpHeader.SERVER, "Archttp"); 240 header(HttpHeader.DATE, DateTime.GetTimeAsGMT()); 241 242 auto text = appender!string; 243 text ~= format!"HTTP/1.1 %d %s\r\n"(_statusCode, getHttpStatusMessage(_statusCode)); 244 foreach (name, value; _headers) { 245 text ~= format!"%s: %s\r\n"(name, value); 246 } 247 248 if (_cookies.length) 249 { 250 foreach (cookie ; _cookies) 251 { 252 text ~= format!"Set-Cookie: %s\r\n"(cookie.toString()); 253 } 254 } 255 256 text ~= "\r\n"; 257 258 return text[]; 259 } 260 }