1 /** 2 * Copyright © Yurai Web Framework 2021 3 * License: MIT (https://github.com/YuraiWeb/yurai/blob/main/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module yurai.external.servers.vibed; 7 8 import yurai.core.settings; 9 10 static if (Yurai_UseVibed) 11 { 12 import yurai.external; 13 import yurai.core.ihttprequest; 14 import yurai.core.ihttpresponse; 15 import yurai.core.fileupload; 16 import yurai.core.lazyload; 17 import yurai.services; 18 19 import vibe.d : HTTPFileServerSettings, HTTPServerRequestDelegateS, serveStaticFiles, HTTPServerSettings, URLRouter, listenHTTP, HTTPServerRequest, HTTPServerResponse, runApplication, Cookie, HTTPStatus; 20 21 final class VibedServer : IServer 22 { 23 private: 24 HTTPServerSettings _settings; 25 URLRouter _router; 26 IPreMiddleware[] _preMiddleware; 27 IPostMiddleware[] _postMiddleware; 28 IContentMiddleware[] _contentMiddleware; 29 IMailService _mailService; 30 31 this() 32 { 33 _preMiddleware = []; 34 _postMiddleware = []; 35 _contentMiddleware = []; 36 } 37 38 public: 39 static void register() 40 { 41 registerServer(new VibedServer); 42 } 43 44 final: 45 @property string name() { return "vibe.d"; } 46 47 void setup(string[] ipAddresses, ushort port, bool debugMode, string staticFilesFolder) 48 { 49 _settings = new HTTPServerSettings; 50 _settings.port = port; 51 _settings.bindAddresses = ipAddresses; 52 _settings.accessLogToConsole = debugMode; 53 _settings.maxRequestSize = 4000000; 54 _settings.maxRequestHeaderSize = 8192; 55 56 _router = new URLRouter; 57 58 import std.datetime : seconds; 59 60 auto fsettings = new HTTPFileServerSettings; 61 fsettings.maxAge = 0.seconds(); 62 _router.get("*", serveStaticFiles("./" ~ staticFilesFolder ~ "/", fsettings)); 63 64 _router.any("*", &handleHTTPListen); 65 66 _router.rebuild(); 67 } 68 69 void run() 70 { 71 listenHTTP(_settings, _router); 72 73 runApplication(); 74 } 75 76 IServer registerPreService(IPreMiddleware middleware) 77 { 78 _preMiddleware ~= middleware; 79 return this; 80 } 81 82 IServer registerContentService(IContentMiddleware middleware) 83 { 84 _contentMiddleware ~= middleware; 85 return this; 86 } 87 88 IServer registerPostService(IPostMiddleware middleware) 89 { 90 _postMiddleware ~= middleware; 91 return this; 92 } 93 94 IServer registerMailService(IMailService mailService) 95 { 96 _mailService = mailService; 97 98 return this; 99 } 100 101 @property 102 { 103 IPreMiddleware[] preServices() 104 { 105 return _preMiddleware; 106 } 107 108 IContentMiddleware[] contentServices() 109 { 110 return _contentMiddleware; 111 } 112 113 IPostMiddleware[] postServices() 114 { 115 return _postMiddleware; 116 } 117 118 IMailService mailService() 119 { 120 return _mailService; 121 } 122 } 123 124 private: 125 void handleHTTPListen(HTTPServerRequest request, HTTPServerResponse response) 126 { 127 import yurai.core.server; 128 129 handleServer(this, new VibedHttpRequest(this, request), new VibedHttpResponse(this, response)); 130 } 131 } 132 133 final class VibedHttpRequest : IHttpRequest 134 { 135 private: 136 HTTPServerRequest _request; 137 string[] _path; 138 string _ipAddress; 139 string _body; 140 string _method; 141 LazyLoad!(string[string]) _headers; 142 LazyLoad!(string[string]) _query; 143 LazyLoad!(string[string]) _form; 144 LazyLoad!(FileUpload[]) _files; 145 IServer _server; 146 147 final: 148 this(IServer server, HTTPServerRequest request) 149 { 150 import std.array : split, array; 151 import std.conv : to; 152 import std.string : toLower, strip; 153 import std.algorithm : map; 154 155 _server = server; 156 _request = request; 157 158 auto requestPath = _request.requestPath.toString().strip; 159 160 if (requestPath.length <= 1) 161 { 162 _path = ["/"]; 163 } 164 else 165 { 166 if (requestPath[0] == '/') 167 { 168 requestPath = requestPath[1 .. $]; 169 } 170 171 _path = requestPath.split("/").map!(r => r.toLower).array; 172 } 173 174 _method = to!string(_request.method); 175 176 _headers = new LazyLoad!(string[string])({ 177 string[string] headers; 178 179 if (_request.headers.length) 180 { 181 foreach (k,v; _request.headers.byKeyValue) 182 { 183 headers[k] = v; 184 } 185 } 186 187 return headers; 188 }); 189 190 _query = new LazyLoad!(string[string])({ 191 string[string] query; 192 193 if (_request.query.length) 194 { 195 foreach (k,v; _request.query.byKeyValue) 196 { 197 query[k] = v; 198 } 199 } 200 201 return query; 202 }); 203 204 _form = new LazyLoad!(string[string])({ 205 string[string] form; 206 207 if (_request.form.length) 208 { 209 foreach (k,v; _request.form.byKeyValue) 210 { 211 form[k] = v; 212 } 213 } 214 215 return form; 216 }); 217 218 _files = new LazyLoad!(FileUpload[])({ 219 FileUpload[] files; 220 221 if (_request.files.length) 222 { 223 foreach (k,v; _request.files.byKeyValue) 224 { 225 files ~= FileUpload(k, v.tempPath.toString()); 226 } 227 } 228 229 return files; 230 }); 231 } 232 233 public: 234 @property 235 { 236 string[] path() 237 { 238 return _path; 239 } 240 241 FileUpload[] files() 242 { 243 return _files.value; 244 } 245 246 string contentType() 247 { 248 return _request.contentType; 249 } 250 251 string ipAddress() 252 { 253 if (!_ipAddress) 254 { 255 auto ip = _request.headers.get("X-Real-IP", null); 256 257 if (!ip || !ip.length) 258 { 259 ip = _request.headers.get("X-Forwarded-For", null); 260 } 261 262 _ipAddress = ip && ip.length ? ip : _request.clientAddress.toAddressString(); 263 } 264 265 return _ipAddress; 266 } 267 268 string textBody() 269 { 270 if (!_body) 271 { 272 import vibe.stream.operations : readAllUTF8; 273 274 _body = _request.bodyReader.readAllUTF8(); 275 } 276 277 return _body; 278 } 279 280 string method() 281 { 282 return _method; 283 } 284 285 string host() 286 { 287 return _request.host; 288 } 289 290 IServer server() { return _server; } 291 } 292 293 string getHeader(string key) 294 { 295 return _headers.value.get(key, null); 296 } 297 298 bool hasHeader(string key) 299 { 300 return cast(bool)(key in _headers.value); 301 } 302 303 string getQuery(string key) 304 { 305 return _query.value.get(key, null); 306 } 307 308 bool hasQuery(string key) 309 { 310 return cast(bool)(key in _query.value); 311 } 312 313 string getForm(string key) 314 { 315 return _form.value.get(key, null); 316 } 317 318 bool hasForm(string key) 319 { 320 return cast(bool)(key in _form.value); 321 } 322 323 string getCookie(string key) 324 { 325 return _request.cookies[key]; 326 } 327 328 bool hasCookie(string key) 329 { 330 return getCookie(key) !is null; 331 } 332 } 333 334 final class VibedHttpResponse : IHttpResponse 335 { 336 private: 337 HTTPServerResponse _response; 338 bool _writeDisabled; 339 bool _redirected; 340 string _redirectedUrl; 341 bool _shouldFlush; 342 bool _hasBodyContent; 343 IServer _server; 344 345 final: 346 this(IServer server, HTTPServerResponse response) 347 { 348 _server = server; 349 _response = response; 350 _writeDisabled = false; 351 } 352 353 public: 354 @property 355 { 356 string contentType() 357 { 358 return getHeader("Content-Type"); 359 } 360 361 void contentType(string value) 362 { 363 addHeader("Content-Type", value); 364 } 365 366 bool hasRedirected() { return _redirected; } 367 368 string redirectedUrl() { return _redirectedUrl; } 369 370 bool hasBodyContent() { return _hasBodyContent; } 371 372 int statusCode() { return _response.statusCode; } 373 void statusCode(int status) 374 { 375 _response.statusCode = status; 376 } 377 378 IServer server() { return _server; } 379 } 380 381 void addHeader(string key, string value) 382 { 383 _response.headers[key] = value; 384 } 385 386 string getHeader(string key) 387 { 388 return _response.headers[key]; 389 } 390 391 void emptyBody() 392 { 393 if (_writeDisabled || _redirected) return; 394 _writeDisabled = true; 395 _shouldFlush = false; 396 _hasBodyContent = true; 397 398 _response.writeBody("\n"); 399 } 400 401 void writeBody(char[] value) 402 { 403 writeBody(value.idup); 404 } 405 406 void writeBody(string value) 407 { 408 if (_writeDisabled || _redirected) return; 409 _writeDisabled = true; 410 _shouldFlush = false; 411 _hasBodyContent = true; 412 413 _response.writeBody(value); 414 } 415 416 void appendBody(char[] value) 417 { 418 appendBody(value.idup); 419 } 420 421 void appendBody(string value) 422 { 423 if (_writeDisabled || _redirected) return; 424 425 _shouldFlush = true; 426 _hasBodyContent = true; 427 428 _response.bodyWriter.write(value); 429 } 430 431 void appendBody(ubyte[] buffer) 432 { 433 if (_writeDisabled || _redirected) return; 434 435 _shouldFlush = true; 436 _hasBodyContent = true; 437 438 _response.bodyWriter.write(buffer); 439 } 440 441 void flush() 442 { 443 if (_shouldFlush) 444 { 445 _shouldFlush = false; 446 _response.bodyWriter.flush(); 447 } 448 } 449 450 void redirect(string url, int status) 451 { 452 redirect(url); 453 } 454 455 void redirect(string url) 456 { 457 _redirected = true; 458 459 _redirectedUrl = url; 460 _response.redirect(url, HTTPStatus.found); 461 } 462 463 void addCookie(string key, string value, long maxAge, string path) 464 { 465 auto cookie = new Cookie; 466 cookie.path = path ? path : "/"; 467 cookie.maxAge = maxAge > 0 ? maxAge : 1; 468 cookie.setValue(value, Cookie.Encoding.none); 469 470 _response.cookies[key] = cookie; 471 } 472 473 void removeCookie(string key, string path) 474 { 475 addCookie(key, null, 1, path); 476 } 477 } 478 }