1 /* 2 * Hunt - A high-level D Programming Language Web framework that encourages rapid development and clean, pragmatic design. 3 * 4 * Copyright (C) 2015-2019, HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.framework.controller.Controller; 13 14 import hunt.framework.application.Application; 15 import hunt.framework.auth; 16 import hunt.framework.breadcrumb.BreadcrumbsManager; 17 import hunt.framework.controller.RestController; 18 import hunt.framework.middleware.AuthMiddleware; 19 import hunt.framework.middleware.Middleware; 20 import hunt.framework.middleware.MiddlewareInfo; 21 import hunt.framework.middleware.MiddlewareInterface; 22 23 import hunt.framework.http.Request; 24 import hunt.framework.http.Form; 25 import hunt.framework.i18n.I18n; 26 import hunt.framework.provider; 27 import hunt.framework.Simplify; 28 import hunt.framework.view; 29 30 public import hunt.framework.http.Response; 31 public import hunt.http.server; 32 public import hunt.http.routing; 33 import hunt.http.HttpConnection; 34 35 import hunt.cache; 36 import hunt.entity.EntityManagerFactory; 37 import hunt.logging.Logger; 38 import hunt.redis.Redis; 39 import hunt.redis.RedisPool; 40 import hunt.validation; 41 42 import poodinis; 43 44 import core.memory; 45 import core.thread; 46 47 import std.algorithm; 48 import std.exception; 49 import std.string; 50 import std.traits; 51 import std.variant; 52 import std.meta : Filter; 53 54 struct Action { 55 } 56 57 private enum string TempVarName = "__var"; 58 // private enum string IndentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; // 16 tabs 59 private enum string IndentString = " "; // 32 spaces 60 61 string indent(size_t number) { 62 assert(number>0 && IndentString.length, "Out of range"); 63 return IndentString[0..number]; 64 } 65 66 class ControllerBase(T) : Controller { 67 // mixin MakeController; 68 mixin HuntDynamicCallFun!(T, moduleName!T); 69 } 70 71 /** 72 * 73 */ 74 abstract class Controller 75 { 76 private Request _request; 77 private Response _response; 78 79 private MiddlewareInfo[] _allowedMiddlewares; 80 private MiddlewareInfo[] _skippedMiddlewares; 81 82 protected 83 { 84 RoutingContext _routingContext; 85 View _view; 86 ///called before all actions 87 MiddlewareInterface[string] middlewares; 88 } 89 90 this() { 91 } 92 93 RoutingContext routingContext() { 94 if(_routingContext is null) { 95 throw new Exception("Can't call this method in the constructor."); 96 } 97 return _routingContext; 98 } 99 100 Request request() { 101 return createRequest(false); 102 } 103 104 protected Request createRequest(bool isRestful = false) { 105 if(_request is null) { 106 RoutingContext context = routingContext(); 107 108 Variant itemAtt = context.getAttribute(RouterContex.stringof); 109 RouterContex routeContex; 110 if(itemAtt.hasValue()) { 111 routeContex = cast(RouterContex)itemAtt.get!(Object); 112 } 113 114 HttpConnection httpConnection = context.httpConnection(); 115 116 _request = new Request(context.getRequest(), 117 httpConnection.getRemoteAddress(), 118 routeContex); 119 120 _request.isRestful = isRestful; 121 } 122 return _request; 123 } 124 125 final @property Response response() { 126 if(_response is null) { 127 _response = new Response(routingContext().getResponse()); 128 } 129 return _response; 130 } 131 132 // reset to a new response 133 @property void response(Response r) { 134 assert(r !is null, "The response can't be null"); 135 _response = r; 136 routingContext().response = r.httpResponse; 137 } 138 139 /** 140 * Handle the auth status 141 */ 142 Auth auth() { 143 return this.request().auth(); 144 } 145 146 /** 147 * Get the currently authenticated user. 148 */ 149 Identity user() { 150 return this.request().auth().user(); 151 } 152 153 @property View view() 154 { 155 if (_view is null) 156 { 157 Request req = this.request(); 158 _view = serviceContainer.resolve!View(); 159 _view.setRouteGroup(routingContext().groupName()); 160 _view.setLocale(req.locale()); 161 _view.env().request = req; 162 163 // _view.assign("input", req.input()); 164 } 165 166 return _view; 167 } 168 169 private RouteConfigManager routeManager() { 170 return serviceContainer.resolve!(RouteConfigManager); 171 } 172 173 174 /// called before action return true is continue false is finish 175 bool before() 176 { 177 return true; 178 } 179 180 /// called after action return true is continue false is finish 181 bool after() 182 { 183 return true; 184 } 185 186 ///add middleware 187 ///return true is ok, the named middleware is already exist return false 188 bool addMiddleware(MiddlewareInterface m) 189 { 190 if(m is null || this.middlewares.get(m.name(), null) !is null) 191 { 192 return false; 193 } 194 195 this.middlewares[m.name()]= m; 196 return true; 197 } 198 199 /** 200 * 201 */ 202 void addAcceptedMiddleware(string fullName, string actionName, string controllerName, string moduleName) { 203 MiddlewareInfo info = new MiddlewareInfo(fullName, actionName, controllerName, moduleName); 204 _allowedMiddlewares ~= info; 205 } 206 207 /** 208 * 209 */ 210 void addSkippedMiddleware(string fullName, string actionName, string controllerName, string moduleName) { 211 MiddlewareInfo info = new MiddlewareInfo(fullName, actionName, controllerName, moduleName); 212 _skippedMiddlewares ~= info; 213 } 214 215 // All the middlewares defined in the route group 216 protected MiddlewareInterface[] getAcceptedMiddlewaresInRouteGroup(string routeGroup) { 217 MiddlewareInterface[] result; 218 219 TypeInfo_Class[] routeMiddlewares; 220 routeMiddlewares = routeManager().group(routeGroup).allowedMiddlewares(); 221 foreach(TypeInfo_Class info; routeMiddlewares) { 222 version(HUNT_AUTH_DEBUG) tracef("routeGroup: %s, fullName: %s", routeGroup, info.name); 223 224 MiddlewareInterface middleware = cast(MiddlewareInterface)info.create(); 225 if(middleware is null) { 226 warningf("%s is not a MiddlewareInterface", info.name); 227 } else { 228 result ~= middleware; 229 } 230 } 231 232 return result; 233 } 234 235 // All the middlewares defined in the route item 236 protected MiddlewareInterface[] getAcceptedMiddlewaresInRouteItem(string routeGroup, string actionId) { 237 MiddlewareInterface[] result; 238 TypeInfo_Class[] routeMiddlewares; 239 240 RouteItem routeItem = routeManager().get(routeGroup, actionId); 241 if(routeItem !is null) { 242 routeMiddlewares = routeItem.allowedMiddlewares(); 243 foreach(TypeInfo_Class info; routeMiddlewares) { 244 warningf("actionId: %s, fullName: %s", actionId, info.name); 245 246 MiddlewareInterface middleware = cast(MiddlewareInterface)info.create(); 247 if(middleware is null) { 248 warningf("%s is not a MiddlewareInterface", info.name); 249 } else { 250 result ~= middleware; 251 } 252 } 253 } 254 255 return result; 256 } 257 258 // All the middlewares defined this Controller's action 259 protected MiddlewareInterface[] getAcceptedMiddlewaresInController(string actionName) { 260 MiddlewareInterface[] result; 261 262 auto middlewares = _allowedMiddlewares.filter!( m => m.action == actionName); 263 // auto middlewares = _allowedMiddlewares.filter!( (m) { 264 // trace(m.action, " == ", name); 265 // return m.action == name; 266 // }); 267 268 foreach(MiddlewareInfo info; middlewares) { 269 // warningf("fullName: %s, action: %s", info.fullName, info.action); 270 271 MiddlewareInterface middleware = cast(MiddlewareInterface)Object.factory(info.fullName); 272 if(middleware is null) { 273 warningf("%s is not a MiddlewareInterface", info.fullName); 274 } else { 275 result ~= middleware; 276 } 277 } 278 279 return result; 280 } 281 282 // All the middlewares defined in the route group 283 protected bool isSkippedMiddlewareInRouteGroup(string fullName, string routeGroup) { 284 TypeInfo_Class[] routeMiddlewares = routeManager().group(routeGroup).skippedMiddlewares(); 285 foreach(TypeInfo_Class typeInfo; routeMiddlewares) { 286 if(typeInfo.name == fullName) return true; 287 } 288 289 return false; 290 } 291 292 // All the middlewares defined in the route item 293 protected bool isSkippedMiddlewareInRouteItem(string fullName, string routeGroup, string actionId) { 294 RouteItem routeItem = routeManager().getRoute(routeGroup, actionId); 295 if(routeItem !is null) { 296 TypeInfo_Class[] routeMiddlewares = routeItem.skippedMiddlewares(); 297 foreach(TypeInfo_Class typeInfo; routeMiddlewares) { 298 if(typeInfo.name == fullName) return true; 299 } 300 } 301 302 return false; 303 } 304 305 // All the middlewares defined this Controller's action 306 protected bool isSkippedMiddlewareInControllerAction(string actionName, string middlewareName) { 307 bool r = _skippedMiddlewares.canFind!(m => m.fullName == middlewareName && m.action == actionName); 308 return r; 309 } 310 311 // get all middleware 312 MiddlewareInterface[string] getMiddlewares() 313 { 314 return this.middlewares; 315 } 316 317 protected final Response handleMiddlewares(string actionName) { 318 Request req = this.request(); 319 string actionId = req.actionId(); 320 string routeGroup = req.routeGroup(); 321 322 version (HUNT_DEBUG) { 323 infof("middlware: routeGroup=%s, path=%s, method=%s, actionId=%s, actionName=%s", 324 routeGroup, req.path(), req.method, actionId, actionName); 325 } 326 327 ///////////// 328 // Checking all the allowed middlewares. 329 ///////////// 330 331 // Allowed middlewares in Controller's Action 332 MiddlewareInterface[] allowedMiddlewares = getAcceptedMiddlewaresInController(actionName); 333 334 // Allowed middlewares in RouteItem 335 allowedMiddlewares ~= getAcceptedMiddlewaresInRouteItem(routeGroup, actionId); 336 337 foreach(MiddlewareInterface m; allowedMiddlewares) { 338 string name = m.name(); 339 version (HUNT_DEBUG) tracef("The %s is processing ...", name); 340 341 auto response = m.onProcess(req, this.response); 342 if (response is null) { 343 continue; 344 } 345 346 version (HUNT_DEBUG) infof("The access is blocked by %s.", name); 347 return response; 348 } 349 350 // Allowed middlewares in RouteGroup 351 allowedMiddlewares = getAcceptedMiddlewaresInRouteGroup(routeGroup); 352 foreach(MiddlewareInterface m; allowedMiddlewares) { 353 string name = m.name(); 354 version (HUNT_DEBUG) tracef("The %s is processing ...", name); 355 356 if(isSkippedMiddlewareInControllerAction(actionName, name)) { 357 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name); 358 return null; 359 } 360 361 if(isSkippedMiddlewareInRouteItem(name, routeGroup, actionId)) { 362 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name); 363 return null; 364 } 365 366 auto response = m.onProcess(req, this.response); 367 if (response is null) { 368 continue; 369 } 370 371 version (HUNT_DEBUG) infof("The access is blocked by %s.", m.name); 372 return response; 373 } 374 375 ///////////// 376 // Checking all the directly registed middlewares in Controller. 377 ///////////// 378 379 foreach (m; middlewares) { 380 string name = m.name(); 381 version (HUNT_DEBUG) tracef("The %s is processing ...", name); 382 if(isSkippedMiddlewareInControllerAction(actionName, name)) { 383 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name); 384 return null; 385 } 386 387 if(isSkippedMiddlewareInRouteItem(name, routeGroup, actionId)) { 388 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name); 389 return null; 390 } 391 392 auto response = m.onProcess(req, this.response); 393 if (response is null) { 394 continue; 395 } 396 397 version (HUNT_DEBUG) tracef("The access is blocked by %s.", name); 398 return response; 399 } 400 401 return null; 402 } 403 404 Response processResponse(Response res) 405 { 406 // TODO: Tasks pending completion -@zhangxueping at 2020-01-06T14:01:43+08:00 407 // 408 // have ResponseHandler binding? 409 // if (res.httpResponse() is null) 410 // { 411 // res.setHttpResponse(request.responseHandler()); 412 // } 413 414 return res; 415 } 416 417 ConstraintValidatorContext validate() { 418 if(_context is null) { 419 // assert(!_currentActionName.empty(), "No currentActionName found!"); 420 _context = new DefaultConstraintValidatorContext(); 421 422 auto itemPtr = _currentActionName in _actionValidators; 423 // assert(itemPtr !is null, format("No handler found for action: %s!", _currentActionName)); 424 if(itemPtr is null) { 425 warning(format("No validator found for action: %s.", _currentActionName)); 426 } else { 427 try { 428 (*itemPtr)(_context); 429 } catch(Exception ex) { 430 warning(ex.msg); 431 version(HUNT_DEBUG) warning(ex); 432 } 433 } 434 } 435 436 return _context; 437 } 438 private ConstraintValidatorContext _context; 439 protected string _currentActionName; 440 protected QueryParameterValidator[string] _actionValidators; 441 442 protected void raiseError(Response response) { 443 this.response = onError(response); 444 } 445 446 protected Response onError(Response response) { 447 return response; 448 } 449 450 protected void done() { 451 Request req = request(); 452 Response resp = response(); 453 HttpSession session = req.session(false); 454 if (session !is null ) // && session.isNewSession() 455 { 456 resp.withCookie(new Cookie(DefaultSessionIdName, session.getId(), session.getMaxInactiveInterval(), 457 "/", null, false, false)); 458 459 // session.reflash(); 460 session.save(); 461 } 462 req.flush(); // assure the sessiondata flushed; 463 464 resp.header("Date", date("Y-m-d H:i:s")); 465 resp.header(HttpHeader.X_POWERED_BY, HUNT_X_POWERED_BY); 466 resp.header(HttpHeader.SERVER, HUNT_FRAMEWORK_SERVER); 467 468 if(!resp.getFields().contains(HttpHeader.CONTENT_TYPE)) { 469 resp.header(HttpHeader.CONTENT_TYPE, MimeType.TEXT_HTML_VALUE); 470 } 471 472 handleCors(); 473 handleAuthResponse(); 474 } 475 476 protected void handleCors() { 477 /** 478 CORS support 479 http://www.cnblogs.com/feihong84/p/5678895.html 480 https://stackoverflow.com/questions/10093053/add-header-in-ajax-request-with-jquery 481 */ 482 ApplicationConfig.HttpConf httpConf = config().http; 483 if(httpConf.enableCors) { 484 response.setHeader("Access-Control-Allow-Origin", httpConf.allowOrigin); 485 response.setHeader("Access-Control-Allow-Methods", httpConf.allowMethods); 486 response.setHeader("Access-Control-Allow-Headers", httpConf.allowHeaders); 487 } 488 } 489 490 protected void handleAuthResponse() { 491 Request req = request(); 492 Auth auth = req.auth(); 493 494 version(HUNT_AUTH_DEBUG) { 495 tracef("Path: %s, isAuthEnabled: %s", 496 req.path, auth.isEnabled()); 497 } 498 499 if(!auth.isEnabled()) 500 return; 501 502 AuthenticationScheme authScheme = auth.scheme(); 503 string tokenCookieName = auth.tokenCookieName; 504 version(HUNT_AUTH_DEBUG) { 505 warningf("tokenCookieName: %s, authScheme: %s, isAuthenticated: %s, isLogout: %s", 506 tokenCookieName, authScheme, auth.user().isAuthenticated, auth.isLogout()); 507 } 508 509 Cookie tokenCookie; 510 511 if(auth.canRememberMe() || auth.isTokenRefreshed()) { 512 ApplicationConfig appConfig = app().config(); 513 int tokenExpiration = appConfig.auth.tokenExpiration; 514 515 if(authScheme != AuthenticationScheme.None) { 516 string authToken = auth.token(); 517 tokenCookie = new Cookie(tokenCookieName, authToken, tokenExpiration); 518 auth.touchSession(); 519 } 520 521 } else if(auth.isLogout()) { 522 if(authScheme != AuthenticationScheme.None) { 523 tokenCookie = new Cookie(tokenCookieName, "", 0); 524 } 525 } else if(authScheme != AuthenticationScheme.None) { 526 ApplicationConfig appConfig = app().config(); 527 int tokenExpiration = appConfig.auth.tokenExpiration; 528 string authToken = auth.token(); 529 if(authToken.empty()) { 530 version(HUNT_AUTH_DEBUG) warning("The auth token is empty!"); 531 } else { 532 tokenCookie = new Cookie(tokenCookieName, authToken, tokenExpiration); 533 auth.touchSession(); 534 } 535 } 536 537 if(tokenCookie !is null) { 538 response().withCookie(tokenCookie); 539 } 540 } 541 542 void dispose() { 543 version(HUNT_FM_DEBUG) trace("Do nothing"); 544 } 545 } 546 547 mixin template MakeController(string moduleName = __MODULE__) 548 { 549 mixin HuntDynamicCallFun!(typeof(this), moduleName); 550 } 551 552 mixin template HuntDynamicCallFun(T, string moduleName) // if(is(T : Controller)) 553 { 554 public: 555 556 // Middleware 557 // pragma(msg, handleMiddlewareAnnotation!(T, moduleName)); 558 559 mixin(handleMiddlewareAnnotation!(T, moduleName)); 560 561 // Actions 562 // enum allActions = __createCallActionMethod!(T, moduleName); 563 // version (HUNT_DEBUG) 564 // pragma(msg, __createCallActionMethod!(T, moduleName)); 565 566 mixin(__createCallActionMethod!(T, moduleName)); 567 568 shared static this() 569 { 570 enum routemap = __createRouteMap!(T, moduleName); 571 // pragma(msg, routemap); 572 mixin(routemap); 573 } 574 } 575 576 private 577 { 578 // Predefined characteristic name for a default Action method. 579 enum actionName = "Action"; 580 enum actionNameLength = actionName.length; 581 582 bool isActionMember(string name) 583 { 584 return name.length > actionNameLength && name[$ - actionNameLength .. $] == actionName; 585 } 586 } 587 588 589 /// 590 string handleMiddlewareAnnotation(T, string moduleName)() { 591 import std.traits; 592 import std.format; 593 import std.string; 594 import std.conv; 595 import hunt.framework.middleware.MiddlewareInterface; 596 597 string str = ` 598 void initializeMiddlewares() { 599 `; 600 601 foreach (memberName; __traits(allMembers, T)) { 602 alias currentMember = __traits(getMember, T, memberName); 603 enum _isActionMember = isActionMember(memberName); 604 605 static if(isFunction!(currentMember)) { 606 607 static if (hasUDA!(currentMember, Action) || _isActionMember) { 608 static if(hasUDA!(currentMember, Middleware)) { 609 enum middlewareUDAs = getUDAs!(currentMember, Middleware); 610 611 foreach(uda; middlewareUDAs) { 612 foreach(middlewareName; uda.names) { 613 str ~= indent(4) ~ generateAcceptedMiddleware(middlewareName, memberName, T.stringof, moduleName); 614 // str ~= indent(4) ~ format(`this.addAcceptedMiddleware("%s", "%s", "%s", "%s");`, 615 // middlewareName, memberName, T.stringof, moduleName) ~ "\n"; 616 } 617 } 618 } 619 620 static if(hasUDA!(currentMember, WithoutMiddleware)) { 621 enum skippedMiddlewareUDAs = getUDAs!(currentMember, WithoutMiddleware); 622 foreach(uda; skippedMiddlewareUDAs) { 623 foreach(middlewareName; uda.names) { 624 str ~= indent(4) ~ generateAddSkippedMiddleware(middlewareName, memberName, T.stringof, moduleName); 625 // str ~= indent(4) ~ format(`this.addSkippedMiddleware("%s", "%s", "%s", "%s");`, 626 // middlewareName, memberName, T.stringof, moduleName) ~ "\n"; 627 } 628 } 629 } 630 } 631 } 632 } 633 634 str ~= ` 635 } 636 `; 637 638 return str; 639 } 640 641 private string generateAcceptedMiddleware(string name, string actionName, string controllerName, string moduleName) { 642 string str; 643 644 str = ` 645 try { 646 TypeInfo_Class typeInfo = MiddlewareInterface.get("%s"); 647 string fullName = typeInfo.name; 648 this.addAcceptedMiddleware(fullName, "%s", "%s", "%s"); 649 } catch(Exception ex) { 650 warning(ex.msg); 651 } 652 `; 653 654 str = format(str, name, actionName, controllerName, moduleName); 655 return str; 656 } 657 658 private string generateAddSkippedMiddleware(string name, string actionName, string controllerName, string moduleName) { 659 string str; 660 661 str = ` 662 try { 663 TypeInfo_Class typeInfo = MiddlewareInterface.get("%s"); 664 string fullName = typeInfo.name; 665 this.addSkippedMiddleware(fullName, "%s", "%s", "%s"); 666 } catch(Exception ex) { 667 warning(ex.msg); 668 } 669 `; 670 671 str = format(str, name, actionName, controllerName, moduleName); 672 return str; 673 } 674 675 string __createCallActionMethod(T, string moduleName)() 676 { 677 import std.traits; 678 import std.format; 679 import std.string; 680 import std.conv; 681 682 683 string str = ` 684 import hunt.http.server.HttpServerRequest; 685 import hunt.http.server.HttpServerResponse; 686 import hunt.http.routing.RoutingContext; 687 import hunt.http.HttpBody; 688 import hunt.logging; 689 import hunt.validation.ConstraintValidatorContext; 690 import hunt.framework.middleware.MiddlewareInterface; 691 import std.demangle; 692 693 void callActionMethod(string methodName, RoutingContext context) { 694 _routingContext = context; 695 HttpBody rb; 696 version (HUNT_FM_DEBUG) logDebug("methodName=", methodName); 697 import std.conv; 698 699 switch(methodName){ 700 `; 701 702 foreach (memberName; __traits(allMembers, T)) 703 { 704 // TODO: Tasks pending completion -@zhangxueping at 2019-09-24T11:47:45+08:00 705 // Can't detect the error: void test(error); 706 // pragma(msg, "memberName: ", memberName); 707 static if (is(typeof(__traits(getMember, T, memberName)) == function)) 708 { 709 // pragma(msg, "got: ", memberName); 710 711 enum _isActionMember = isActionMember(memberName); 712 static foreach (currentMethod; __traits(getOverloads, T, memberName)) 713 { 714 // alias RT = ReturnType!(t); 715 716 //alias pars = ParameterTypeTuple!(t); 717 static if (hasUDA!(currentMethod, Action) || _isActionMember) { 718 str ~= indent(2) ~ "case \"" ~ memberName ~ "\": {\n"; 719 str ~= indent(4) ~ "_currentActionName = \"" ~ currentMethod.mangleof ~ "\";"; 720 721 // middleware 722 str ~= `auto middleResponse = this.handleMiddlewares("`~ memberName ~ `");`; 723 724 //before 725 str ~= q{ 726 if (middleResponse !is null) { 727 // _routingContext.response = response.httpResponse; 728 response = middleResponse; 729 return; 730 } 731 732 if (!this.before()) { 733 // _routingContext.response = response.httpResponse; 734 // response = middleResponse; 735 return; 736 } 737 }; 738 739 // Action parameters 740 auto params = ParameterIdentifierTuple!currentMethod; 741 string paramString = ""; 742 743 static if (params.length > 0) { 744 import std.conv : to; 745 746 string varName = ""; 747 alias paramsType = Parameters!currentMethod; 748 alias paramsDefaults = ParameterDefaults!currentMethod; 749 750 static foreach (int i; 0..params.length) 751 {{ 752 varName = TempVarName ~ i.to!string; 753 enum currentParamType = paramsType[i].stringof; 754 755 alias paramsUDAs = ParameterUDAs!(currentMethod, AliasField, i); 756 static if(paramsUDAs.length > 0) { 757 enum actualParameter = paramsUDAs[0].name; 758 } else { 759 alias actualParameter = params[i]; 760 } 761 762 static if (is(paramsType[i] == string)) { 763 static if(is(paramsDefaults[i] == void)) { 764 str ~= indent(3) ~ "string " ~ varName ~ " = request.get(\"" ~ actualParameter ~ "\");\n"; 765 } else { 766 str ~= indent(3) ~ "string " ~ varName ~ " = request.get(\"" ~ actualParameter ~ 767 "\", " ~ paramsDefaults[i].stringof ~ ");\n"; 768 } 769 } else static if(is(paramsType[i] : Form)) { 770 str ~= indent(3) ~ "auto " ~ varName ~ " = request.bindForm!" ~ currentParamType ~ "();\n"; 771 } else { 772 static if(is(paramsDefaults[i] == void)) { 773 str ~= indent(3) ~ "auto " ~ varName ~ " = request.get!(" ~ 774 currentParamType ~ ")(\"" ~ actualParameter ~ "\");\n"; 775 } else { 776 str ~= indent(3) ~ "auto " ~ varName ~ " = request.get!(" ~ 777 currentParamType ~ ")(\"" ~ actualParameter ~ 778 "\", " ~ paramsDefaults[i].stringof ~ ");\n"; 779 } 780 } 781 782 paramString ~= i == 0 ? varName : ", " ~ varName; 783 // varName = ""; 784 }} 785 } 786 787 // Parameters validation 788 // https://forum.dlang.org/post/bbgwqvvausncrkukzpui@forum.dlang.org 789 str ~= indent(3) ~ `_actionValidators["` ~ currentMethod.mangleof ~ 790 `"] = (ConstraintValidatorContext context) {` ~ "\n"; 791 792 static if(is(typeof(currentMethod) allParams == __parameters)) { 793 str ~= indent(4) ~ "version(HUNT_DEBUG) info(`Validating in " ~ memberName ~ 794 ", the prototype is " ~ typeof(currentMethod).stringof ~ ". `); " ~ "\n"; 795 // str ~= indent(4) ~ `version(HUNT_DEBUG) infof("Validating in %s", demangle(_currentActionName)); ` ~ "\n"; 796 797 static foreach(i, _; allParams) {{ 798 alias thisParameter = allParams[i .. i + 1]; 799 alias udas = __traits(getAttributes, thisParameter); 800 // enum ident = __traits(identifier, thisParameter); 801 802 alias paramsUDAs = Filter!(isDesiredUDA!AliasField, udas); 803 static if(paramsUDAs.length > 0) { 804 enum actualParameter = paramsUDAs[0].name; 805 } else { 806 enum actualParameter = __traits(identifier, thisParameter); 807 } 808 809 str ~= "\n" ~ makeParameterValidation!(TempVarName ~ i.to!string, actualParameter, 810 thisParameter, udas) ~ "\n"; 811 }} 812 } 813 814 str ~= indent(3) ~ "};\n"; 815 816 // Call the Action 817 static if (is(ReturnType!currentMethod == void)) { 818 str ~= "\t\tthis." ~ memberName ~ "(" ~ paramString ~ ");\n"; 819 } else { 820 str ~= "\t\t" ~ ReturnType!currentMethod.stringof ~ " result = this." ~ 821 memberName ~ "(" ~ paramString ~ ");\n"; 822 823 static if (is(ReturnType!currentMethod : Response)) { 824 str ~= "\t\t this.response = result;\n"; 825 } else { 826 static if(is(T : RestController)) { 827 str ~="\t\tthis.response.setRestContent(result);"; 828 } else { 829 str ~="\t\tthis.response.setContent(result);"; 830 } 831 } 832 } 833 834 static if(hasUDA!(currentMethod, Action) || _isActionMember) { 835 str ~= "\n\t\tthis.after();\n"; 836 } 837 838 str ~= "\n\t\tbreak;\n\t}\n"; 839 } 840 } 841 } 842 } 843 844 str ~= "\tdefault:\n\tbreak;\n\t}\n\n"; 845 str ~= "}"; 846 return str; 847 } 848 849 850 template isDesiredUDA(alias attribute) 851 { 852 template isDesiredUDA(alias toCheck) 853 { 854 static if (is(typeof(attribute)) && !__traits(isTemplate, attribute)) 855 { 856 static if (__traits(compiles, toCheck == attribute)) 857 enum isDesiredUDA = toCheck == attribute; 858 else 859 enum isDesiredUDA = false; 860 } 861 else static if (is(typeof(toCheck))) 862 { 863 static if (__traits(isTemplate, attribute)) 864 enum isDesiredUDA = isInstanceOf!(attribute, typeof(toCheck)); 865 else 866 enum isDesiredUDA = is(typeof(toCheck) == attribute); 867 } 868 else static if (__traits(isTemplate, attribute)) 869 enum isDesiredUDA = isInstanceOf!(attribute, toCheck); 870 else 871 enum isDesiredUDA = is(toCheck == attribute); 872 } 873 } 874 875 876 template ParameterUDAs(alias func, A, int i=0) { 877 static if(is(FunctionTypeOf!func PT == __parameters)) { 878 alias thisParameter = PT[i .. i + 1]; 879 alias udas = __traits(getAttributes, thisParameter); 880 881 alias ParameterUDAs = Filter!(isDesiredUDA!A, udas); 882 } else { 883 static assert(0, func.stringof ~ " is not a function"); 884 } 885 } 886 887 888 string makeParameterValidation(string varName, string paraName, paraType, UDAs ...)() { 889 string str; 890 // = "\ninfof(\"" ~ symbol.stringof ~ "\");"; 891 892 version(HUNT_FM_DEBUG) { 893 str ~= ` 894 tracef("varName=`~ varName ~`, paraName=` ~ paraName ~ `"); 895 `; 896 } 897 898 static foreach(uda; UDAs) { 899 static if(is(typeof(uda) == Max)) { 900 str ~= `{ 901 MaxValidator validator = new MaxValidator(); 902 validator.initialize(` ~ uda.stringof ~ `); 903 validator.setPropertyName("` ~ paraName ~ `"); 904 validator.isValid(`~ varName ~`, context); 905 }`; 906 } 907 908 static if(is(typeof(uda) == Min)) { 909 str ~= `{ 910 MinValidator validator = new MinValidator(); 911 validator.initialize(` ~ uda.stringof ~ `); 912 validator.setPropertyName("` ~ paraName ~ `"); 913 validator.isValid(`~ varName ~`, context); 914 }`; 915 } 916 917 static if(is(typeof(uda) == AssertFalse)) { 918 str ~= `{ 919 AssertFalseValidator validator = new AssertFalseValidator(); 920 validator.initialize(` ~ uda.stringof ~ `); 921 validator.setPropertyName("` ~ paraName ~ `"); 922 validator.isValid(`~ varName ~`, context); 923 }`; 924 } 925 926 static if(is(typeof(uda) == AssertTrue)) { 927 str ~= `{ 928 AssertTrueValidator validator = new AssertTrueValidator(); 929 validator.initialize(` ~ uda.stringof ~ `); 930 validator.setPropertyName("` ~ paraName ~ `"); 931 validator.isValid(`~ varName ~`, context); 932 }`; 933 } 934 935 static if(is(typeof(uda) == Email)) { 936 str ~= `{ 937 EmailValidator validator = new EmailValidator(); 938 validator.initialize(` ~ uda.stringof ~ `); 939 validator.setPropertyName("` ~ paraName ~ `"); 940 validator.isValid(`~ varName ~`, context); 941 }`; 942 } 943 944 static if(is(typeof(uda) == Length)) { 945 str ~= `{ 946 LengthValidator validator = new LengthValidator(); 947 validator.initialize(` ~ uda.stringof ~ `); 948 validator.setPropertyName("` ~ paraName ~ `"); 949 validator.isValid(`~ varName ~`, context); 950 }`; 951 } 952 953 static if(is(typeof(uda) == NotBlank)) { 954 str ~= `{ 955 NotBlankValidator validator = new NotBlankValidator(); 956 validator.initialize(` ~ uda.stringof ~ `); 957 validator.setPropertyName("` ~ paraName ~ `"); 958 validator.isValid(`~ varName ~`, context); 959 }`; 960 } 961 962 static if(is(typeof(uda) == NotEmpty)) { 963 str ~= `{ 964 auto validator = new NotEmptyValidator!` ~ paraType.stringof ~`(); 965 validator.initialize(` ~ uda.stringof ~ `); 966 validator.setPropertyName("` ~ paraName ~ `"); 967 validator.isValid(`~ varName ~`, context); 968 }`; 969 } 970 971 static if(is(typeof(uda) == Pattern)) { 972 str ~= `{ 973 PatternValidator validator = new PatternValidator(); 974 validator.initialize(` ~ uda.stringof ~ `); 975 validator.setPropertyName("` ~ paraName ~ `"); 976 validator.isValid(`~ varName ~`, context); 977 }`; 978 } 979 980 static if(is(typeof(uda) == Size)) { 981 str ~= `{ 982 SizeValidator validator = new SizeValidator(); 983 validator.initialize(` ~ uda.stringof ~ `); 984 validator.setPropertyName("` ~ paraName ~ `"); 985 validator.isValid(`~ varName ~`, context); 986 }`; 987 } 988 989 static if(is(typeof(uda) == Range)) { 990 str ~= `{ 991 RangeValidator validator = new RangeValidator(); 992 validator.initialize(` ~ uda.stringof ~ `); 993 validator.setPropertyName("` ~ paraName ~ `"); 994 validator.isValid(`~ varName ~`, context); 995 }`; 996 } 997 } 998 999 return str; 1000 } 1001 1002 alias QueryParameterValidator = void delegate(ConstraintValidatorContext); 1003 1004 string __createRouteMap(T, string moduleName)() 1005 { 1006 1007 enum len = "Controller".length; 1008 enum controllerName = moduleName[0..$-len]; 1009 1010 // The format: 1011 // 1) app.controller.[{group}.]{name}controller 1012 // app.controller.admin.IndexController 1013 // app.controller.IndexController 1014 // 1015 // 2) app.component.{component-name}.controller.{group}.{name}controller 1016 // app.component.system.controller.admin.DashboardController 1017 enum string[] parts = moduleName.split("."); 1018 // string groupName = "default"; 1019 1020 static if(parts.length == 4) { 1021 // app.controller.admin.DashboardController 1022 enum GroupName = parts[2]; 1023 } else static if(parts.length == 6) { 1024 // app.component.system.controller.admin.DashboardController 1025 enum GroupName = parts[4]; 1026 } else { 1027 enum GroupName = "default"; 1028 } 1029 1030 string str = ""; 1031 foreach (memberName; __traits(allMembers, T)) 1032 { 1033 // pragma(msg, "memberName: ", memberName); 1034 1035 static if (is(typeof(__traits(getMember, T, memberName)) == function)) { 1036 foreach (t; __traits(getOverloads, T, memberName)) { 1037 static if (hasUDA!(t, Action)) { 1038 enum string MemberName = memberName; 1039 } else static if (isActionMember(memberName)) { 1040 enum string MemberName = memberName[0 .. $ - actionNameLength]; 1041 } else { 1042 enum string MemberName = ""; 1043 } 1044 1045 static if(MemberName.length > 0) { 1046 str ~= "\n\tregisterRouteHandler(\"" ~ controllerName ~ "." ~ T.stringof ~ "." ~ MemberName 1047 ~ "\", (context) { 1048 context.groupName = \"" ~ GroupName ~ "\"; 1049 callHandler!(" ~ T.stringof ~ ",\"" ~ memberName ~ "\")(context); 1050 });\n"; 1051 } 1052 } 1053 } 1054 } 1055 1056 return str; 1057 } 1058 1059 void callHandler(T, string method)(RoutingContext context) 1060 if (is(T == class) || (is(T == struct) && hasMember!(T, "__CALLACTION__"))) 1061 { 1062 // req.action = method; 1063 // auto req = context.getRequest(); 1064 // warningf("group name: %s, Threads: %d", context.groupName(), Thread.getAll().length); 1065 1066 T controller = new T(); 1067 1068 scope(exit) { 1069 import hunt.util.ResoureManager; 1070 ApplicationConfig appConfig = app().config(); 1071 if(appConfig.http.workerThreads == 0) { 1072 collectResoure(); 1073 } 1074 controller.dispose(); 1075 // HUNT_THREAD_DEBUG 1076 version(HUNT_DEBUG) { 1077 warningf("Threads: %d, allocatedInCurrentThread: %d bytes", 1078 Thread.getAll().length, GC.stats().allocatedInCurrentThread); 1079 } 1080 // GC.collect(); 1081 } 1082 1083 try { 1084 controller.initializeMiddlewares(); 1085 controller.callActionMethod(method, context); 1086 controller.done(); 1087 } catch (Throwable t) { 1088 error(t); 1089 app().logger().error(t); 1090 Response errorRes = new Response(); 1091 errorRes.doError(HttpStatus.INTERNAL_SERVER_ERROR_500, t); 1092 controller.raiseError(errorRes); 1093 } 1094 1095 context.end(); 1096 } 1097 1098 RoutingHandler getRouteHandler(string str) 1099 { 1100 return _actions.get(str, null); 1101 } 1102 1103 void registerRouteHandler(string str, RoutingHandler method) 1104 { 1105 // key: app.controller.Index.IndexController.showString 1106 version (HUNT_FM_DEBUG) trace("Add route handler: ", str); 1107 _actions[str.toLower] = method; 1108 } 1109 1110 __gshared RoutingHandler[string] _actions;