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