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