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 99 header("Content-Type", "text/plain"); 100 send(); 101 } 102 103 void send(JSONValue json) 104 { 105 _body = json.toString(); 106 107 header("Content-Type", "application/json"); 108 send(); 109 } 110 111 void send() 112 { 113 if (_headersSent) 114 { 115 log.error("Can't set headers after they are sent"); 116 return; 117 } 118 119 if (sendHeader()) 120 sendBody(); 121 } 122 123 HttpResponse json(JSONValue json) 124 { 125 _body = json.toString(); 126 127 header("Content-Type", "application/json"); 128 129 return this; 130 } 131 132 HttpResponse location(HttpStatusCode statusCode, string path) 133 { 134 code(statusCode); 135 location(path); 136 137 return this; 138 } 139 140 HttpResponse location(string path) 141 { 142 redirect(HttpStatusCode.SEE_OTHER, path); 143 header("Location", path); 144 return this; 145 } 146 147 HttpResponse redirect(HttpStatusCode statusCode, string path) 148 { 149 location(statusCode, path); 150 return this; 151 } 152 153 HttpResponse redirect(string path) 154 { 155 redirect(HttpStatusCode.FOUND, path); 156 return this; 157 } 158 159 HttpResponse sendFile(string path, string filename = "") 160 { 161 import std.stdio : File; 162 163 auto file = File(path, "r"); 164 auto fileSize = file.size(); 165 166 if (filename.length == 0) 167 { 168 import std.array : split; 169 import std.string : replace; 170 171 auto parts = path.replace("\\", "/").split("/"); 172 if (parts.length == 1) 173 { 174 filename = path; 175 } 176 else 177 { 178 filename = parts[parts.length - 1]; 179 } 180 } 181 182 header(HttpHeader.CONTENT_DISPOSITION, "attachment; filename=" ~ filename ~ "; size=" ~ fileSize.to!string); 183 header(HttpHeader.CONTENT_LENGTH, fileSize.to!string); 184 185 _httpContext.Write(headerToString()); 186 _headersSent = true; 187 188 auto buf = Nbuff.get(fileSize); 189 file.rawRead(buf.data()); 190 191 _httpContext.Write(NbuffChunk(buf, fileSize)); 192 reset(); 193 194 return this; 195 } 196 197 void end() 198 { 199 reset(); 200 _httpContext.End(); 201 } 202 203 private bool sendHeader() 204 { 205 if (_headersSent) 206 { 207 log.error("Can't set headers after they are sent"); 208 return false; 209 } 210 211 // if (_httpContext.keepAlive() && this.header(HttpHeader.CONTENT_LENGTH).length == 0) 212 header(HttpHeader.CONTENT_LENGTH, _body.length.to!string); 213 214 _httpContext.Write(headerToString()); 215 _headersSent = true; 216 217 return true; 218 } 219 220 private void sendBody() 221 { 222 _httpContext.Write(_body); 223 reset(); 224 } 225 226 void reset() 227 { 228 // clear request object 229 _httpContext.request().reset(); 230 231 _statusCode = HttpStatusCode.OK; 232 _headers = null; 233 _body = null; 234 _buffer = null; 235 _cookies = null; 236 _headersSent = false; 237 } 238 239 string headerToString() 240 { 241 header(HttpHeader.SERVER, "Archttp"); 242 header(HttpHeader.DATE, DateTime.GetTimeAsGMT()); 243 244 auto text = appender!string; 245 text ~= format!"HTTP/1.1 %d %s\r\n"(_statusCode, getHttpStatusMessage(_statusCode)); 246 foreach (name, value; _headers) { 247 text ~= format!"%s: %s\r\n"(name, value); 248 } 249 250 if (_cookies.length) 251 { 252 foreach (cookie ; _cookies) 253 { 254 text ~= format!"Set-Cookie: %s\r\n"(cookie.toString()); 255 } 256 } 257 258 text ~= "\r\n"; 259 260 return text[]; 261 } 262 }