1 /** 2 * Copyright © DiamondMVC 2018 3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module diamond.http.client; 7 8 import diamond.core.apptype; 9 10 static if (isWeb) 11 { 12 /// The name of the language session key. 13 private static __gshared const languageSessionKey = "__D_LANGUAGE"; 14 15 /// Wrapper around the client's request aand response. 16 final class HttpClient 17 { 18 import std.conv : to; 19 20 import vibe.d : HTTPServerRequest, HTTPServerResponse, 21 HTTPStatusException; 22 23 import diamond.authentication; 24 import diamond.http.sessions; 25 import diamond.http.cookies; 26 import diamond.http.method; 27 import diamond.http.status; 28 import diamond.http.routing; 29 import diamond.errors.checks; 30 31 private: 32 /// The request. 33 HTTPServerRequest _request; 34 35 /// The response. 36 HTTPServerResponse _response; 37 38 /// The session. 39 HttpSession _session; 40 41 /// The cookies. 42 HttpCookies _cookies; 43 44 /// The route. 45 Route _route; 46 47 /// The role. 48 Role _role; 49 50 /// The ip address. 51 string _ipAddress; 52 53 /// Boolean determnining whether the client has been redirected or not. 54 bool _redirected; 55 56 /// The data written to the response. 57 ubyte[] _data; 58 59 /// the status code for the response. 60 HttpStatus _statusCode; 61 62 /// The language of the client. 63 string _language; 64 65 /// Boolean determining whether the client's route is the last route to handle. 66 bool _isLastRoute; 67 68 /// The path. 69 string _path; 70 71 final: 72 package(diamond) 73 { 74 /** 75 * Createsa new http client. 76 * Params: 77 * request = The request. 78 * response = The response. 79 */ 80 this(HTTPServerRequest request, HTTPServerResponse response) 81 { 82 _request = enforceInput(request, "Cannot create a client without a request."); 83 _response = enforceInput(response, "Cannot create a client without a response."); 84 85 addContext("__D_RAW_HTTP_CLIENT", this); 86 87 _path = request.requestPath.toString(); 88 } 89 } 90 91 public: 92 @property 93 { 94 /// Gets the raw vibe.d request. 95 package(diamond) HTTPServerRequest rawRequest() { return _request; } 96 97 /// Gets the raw vibe.d response. 98 package(diamond) HTTPServerResponse rawResponse() { return _response; } 99 100 /// Gets the route. 101 Route route() { return _route; } 102 103 /// Sets the route. 104 package(diamond) void route(Route newRoute) 105 { 106 _route = newRoute; 107 } 108 109 /// Gets a boolean determining whether it's the client's last route to handle. 110 package(diamond) bool isLastRoute() { return _isLastRoute; } 111 112 /// Sets a boolean determining whether it's the client's last route to handle. 113 package(diamond) void isLastRoute(bool isLastRouteState) 114 { 115 _isLastRoute = isLastRouteState; 116 } 117 118 /// Gets the method. 119 HttpMethod method() { return cast(HttpMethod)_request.method; } 120 121 /// Gets the session. 122 HttpSession session() 123 { 124 if (_session) 125 { 126 return _session; 127 } 128 129 _session = getSession(this); 130 131 return _session; 132 } 133 134 /// Gets the cookies. 135 HttpCookies cookies() 136 { 137 if (_cookies) 138 { 139 return _cookies; 140 } 141 142 _cookies = new HttpCookies(this); 143 144 return _cookies; 145 } 146 147 /// Gets the ip address. 148 string ipAddress() 149 { 150 if (_ipAddress) 151 { 152 return _ipAddress; 153 } 154 155 _ipAddress = _request.clientAddress.toAddressString(); 156 157 return _ipAddress; 158 } 159 160 /// Gets the raw request stream. 161 auto requestStream() { return _request.bodyReader; } 162 163 /// Gets the raw response stream. 164 auto responseStream() { return _response.bodyWriter; } 165 166 /// Gets a boolean determnining whether the response is connected or not. 167 bool connected() { return _response.connected; } 168 169 /// Gets the path. 170 string path() 171 { 172 return _path; 173 } 174 175 /// Sets the path of the client. 176 package(diamond) void path(string newPath) 177 { 178 _path = newPath; 179 } 180 181 /// Gets the query string. 182 string queryString() { return _request.queryString; } 183 184 /// Gets a mapped query of the query string. 185 auto query() { return _request.query; } 186 187 /// Gets the generic http parameters. 188 auto httpParams() { return _request.params; } 189 190 /// Gets the files from the request. 191 auto files() { return _request.files; } 192 193 /// Gets the form from the request. 194 auto form() { return _request.form; } 195 196 /// Gets the full url from the request. 197 auto fullUrl() { return _request.fullURL; } 198 199 /// Gets the json from the request. 200 auto json() { return _request.json; } 201 202 /// Gets the content type from the request. 203 string contentType() { return _request.contentType; } 204 205 /// Gets the content type parameters from the request. 206 string contentTypeParameters() { return _request.contentTypeParameters; } 207 208 /// Gets the host from the request. 209 string host() { return _request.host; } 210 211 /// Gets the headers from the request. 212 auto headers() { return _request.headers; } 213 214 /// Gets a boolean determnining whether the request was done over a secure tls protocol. 215 bool tls() { return _request.tls; } 216 217 /// Gets the raw client address of the request. 218 auto clientAddress() { return _request.clientAddress; } 219 220 /// Gets the client certificate from the request. 221 auto clientCertificate() { return _request.clientCertificate; } 222 223 /// Boolean determining whether the client has been redirected or not. 224 bool redirected() { return _redirected; } 225 226 /// Gets the role associated with the client. 227 Role role() 228 { 229 if (_role) 230 { 231 return _role; 232 } 233 234 if (!_role) 235 { 236 validateAuthentication(this); 237 } 238 239 _role = getRole(this); 240 241 return _role; 242 } 243 244 /// Gets the status code of the response. 245 HttpStatus statusCode() { return _statusCode; } 246 247 /// Gets the language of the client. 248 string language() 249 { 250 if (_language is null) 251 { 252 _language = session.getValue!string(languageSessionKey, ""); 253 } 254 255 return _language; 256 } 257 258 /// Sets the language of the client. 259 void language(string newLanguage) 260 { 261 _language = newLanguage; 262 session.setValue(languageSessionKey, _language); 263 } 264 } 265 266 /// Gets a model from the request's json. 267 T getModelFromJson(T, CTORARGS...)(CTORARGS args) 268 { 269 import vibe.data.json; 270 271 static if (is(T == struct)) 272 { 273 T value; 274 275 value.deserializeJson(_request.json); 276 277 return value; 278 } 279 else static if (is(T == class)) 280 { 281 auto value = new T(args); 282 283 value.deserializeJson(_request.json); 284 285 return value; 286 } 287 else 288 { 289 static assert(0); 290 } 291 } 292 293 /** 294 * Adds a generic context value to the client. 295 * Params: 296 * name = The name of the value. 297 * value = The value. 298 */ 299 void addContext(T)(string name, T value) 300 { 301 _request.context[name] = value; 302 } 303 304 /** 305 * Gets a value from the client's context. 306 * Params: 307 * name = The name of the value to retrieve. 308 * defaultValue = The default value to retrieve if the value wasn't found in the context. 309 * Returns: 310 * The value if found, defaultValue otherwise. 311 */ 312 T getContext(T)(string name, lazy T defaultValue = T.init) 313 { 314 import std.variant : Variant; 315 Variant value = _request.context.get(name, Variant.init); 316 317 if (!value.hasValue) 318 { 319 return defaultValue; 320 } 321 322 return value.get!T; 323 } 324 325 /** 326 * Checks whether a value is present n the client's context or not. 327 * Params: 328 * name = The name to check for existence. 329 * Returns: 330 * True if the value is present, false otherwise. 331 */ 332 bool hasContext(string name) 333 { 334 import std.variant : Variant; 335 336 return _request.context.get(name, Variant.init).hasValue; 337 } 338 339 /** 340 * Redirects the client. 341 * Params: 342 * url = The url to redirect the client to. 343 * status = The status of the redirection. 344 */ 345 void redirect(string url, HttpStatus status = HttpStatus.found) 346 { 347 _response.redirect(url, status); 348 349 import diamond.core.webconfig; 350 foreach (headerKey,headerValue; webConfig.defaultHeaders.general) 351 { 352 _response.headers[headerKey] = headerValue; 353 } 354 355 _redirected = true; 356 } 357 358 /** 359 * Throws a http status exception. 360 * Params: 361 * status = The status. 362 * Throws: 363 * Always throws HTTPStatusException. 364 */ 365 void error(HttpStatus status) 366 { 367 _statusCode = status; 368 _response.statusCode = _statusCode; 369 370 throw new HTTPStatusException(status); 371 } 372 373 /// Sends a 404 status. 374 void notFound() 375 { 376 error(HttpStatus.notFound); 377 } 378 379 /// Sends an unauthorized error 380 void unauthorized() { 381 error(HttpStatus.unauthorized); 382 } 383 384 /** 385 * Logs the client in. 386 * Params: 387 * loginTime = The time the client should be logged in. 388 * role = The role the client should be after being logged in. 389 */ 390 void login(long loginTime, Role role) 391 { 392 import diamondauth = diamond.authentication; 393 diamondauth.login(this, loginTime, role); 394 } 395 396 /// Logs the client out. 397 void logout() 398 { 399 import diamondauth = diamond.authentication; 400 diamondauth.logout(this); 401 } 402 403 /** 404 * Writes data to the response stream. 405 * Params: 406 * data = The data to write. 407 */ 408 void write(string data) 409 { 410 static if (loggingEnabled) 411 { 412 _data ~= cast(ubyte[])data; 413 } 414 415 _response.bodyWriter.write(data); 416 } 417 418 /** 419 * Writes data to the response stream. 420 * Params: 421 * data = The data to write. 422 */ 423 void write(ubyte[] data) 424 { 425 static if (loggingEnabled) 426 { 427 _data ~= data; 428 } 429 430 _response.bodyWriter.write(data); 431 } 432 433 static if (loggingEnabled) 434 { 435 /// Gets the body data from the response stream. 436 package(diamond) ubyte[] getBody() 437 { 438 return _data; 439 } 440 } 441 } 442 }