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.application.Application; 13 14 import hunt.framework.auth.AuthService; 15 import hunt.framework.application.HostEnvironment; 16 import hunt.framework.application.closer; 17 import hunt.framework.command.ServeCommand; 18 import hunt.framework.Init; 19 import hunt.framework.http; 20 import hunt.framework.i18n.I18n; 21 import hunt.framework.config.ApplicationConfig; 22 import hunt.framework.config.ConfigManager; 23 import hunt.framework.middleware.MiddlewareInterface; 24 import hunt.framework.provider; 25 import hunt.framework.provider.listener; 26 import hunt.framework.routing; 27 28 import hunt.http.server.HttpServer; 29 import hunt.http.server.HttpServerOptions; 30 import hunt.http.WebSocketPolicy; 31 import hunt.http.WebSocketCommon; 32 33 import hunt.console; 34 import hunt.Functions; 35 import hunt.logging.ConsoleLogger; 36 import hunt.logging.Logger; 37 import hunt.redis; 38 import hunt.util.ResoureManager; 39 import hunt.util.worker; 40 41 import grpc.GrpcServer; 42 import grpc.GrpcClient; 43 44 version (WITH_HUNT_TRACE) { 45 import hunt.http.HttpConnection; 46 import hunt.net.util.HttpURI; 47 import hunt.trace.Constrants; 48 import hunt.trace.Endpoint; 49 import hunt.trace.Span; 50 import hunt.trace.Tracer; 51 import hunt.trace.HttpSender; 52 53 import std.format; 54 } 55 56 import poodinis; 57 58 import std.array; 59 import std.conv; 60 import std.meta; 61 import std.parallelism : totalCPUs; 62 import std.path; 63 import std.socket : Address, parseAddress; 64 import std.stdio; 65 import std.string; 66 67 alias DefaultServiceProviders = AliasSeq!(UserServiceProvider, AuthServiceProvider, 68 ConfigServiceProvider, RedisServiceProvider, 69 TranslationServiceProvider, CacheServiceProvider, SessionServiceProvider, 70 DatabaseServiceProvider, QueueServiceProvider, 71 TaskServiceProvider, HttpServiceProvider, GrpcServiceProvider, 72 BreadcrumbServiceProvider, ViewServiceProvider); 73 74 /** 75 * 76 */ 77 final class Application { 78 79 private string _name = DEFAULT_APP_NAME; 80 private string _description = DEFAULT_APP_DESCRIPTION; 81 private string _ver = DEFAULT_APP_VERSION; 82 83 private HttpServer _server; 84 private HttpServerOptions _serverOptions; 85 private ApplicationConfig _appConfig; 86 87 private bool _isBooted = false; 88 private bool[TypeInfo] _customizedServiceProviders; 89 private SimpleEventHandler _launchedHandler; 90 private SimpleEventHandler _configuringHandler; 91 private HostEnvironment _environment; 92 private hunt.console.Command[] _commands; 93 94 // private WebSocketPolicy _webSocketPolicy; 95 // private WebSocketHandler[string] webSocketHandlerMap; 96 97 private __gshared Application _app; 98 99 static Application instance() { 100 if (_app is null) 101 _app = new Application(); 102 return _app; 103 } 104 105 this() { 106 _environment = new HostEnvironment(); 107 setDefaultLogging(); 108 initializeProviderListener(); 109 } 110 111 this(string name, string ver = DEFAULT_APP_VERSION, string description = DEFAULT_APP_DESCRIPTION) { 112 _name = name; 113 _ver = ver; 114 _description = description; 115 _environment = new HostEnvironment(); 116 117 setDefaultLogging(); 118 initializeProviderListener(); 119 } 120 121 void register(T)() if (is(T : ServiceProvider)) { 122 if (_isBooted) { 123 warning("A provider can't be registered: %s after the app has been booted.", typeid(T)); 124 return; 125 } 126 127 ServiceProvider provider = new T(); 128 provider._container = serviceContainer(); 129 provider.register(); 130 serviceContainer().register!(ServiceProvider, T)().existingInstance(provider); 131 serviceContainer().autowire(provider); 132 _providerListener.registered(typeid(T)); 133 134 static foreach (S; DefaultServiceProviders) { 135 checkCustomizedProvider!(T, S); 136 } 137 } 138 139 private void tryRegister(T)() if (is(T : ServiceProvider)) { 140 if (!isRegistered!(T)) { 141 register!T(); 142 } 143 } 144 145 private void checkCustomizedProvider(T, S)() { 146 static if (is(T : S)) { 147 _customizedServiceProviders[typeid(S)] = true; 148 } 149 } 150 151 private bool isRegistered(T)() if (is(T : ServiceProvider)) { 152 auto itemPtr = typeid(T) in _customizedServiceProviders; 153 154 return itemPtr !is null; 155 } 156 157 GrpcService grpc() { 158 return serviceContainer().resolve!GrpcService(); 159 } 160 161 HostEnvironment environment() { 162 return _environment; 163 } 164 165 alias configuring = booting; 166 Application booting(SimpleEventHandler handler) { 167 _configuringHandler = handler; 168 return this; 169 } 170 171 deprecated("Using booted instead.") 172 alias onBooted = booted; 173 174 Application booted(SimpleEventHandler handler) { 175 _launchedHandler = handler; 176 return this; 177 } 178 179 void register(hunt.console.Command cmd) { 180 _commands ~= cmd; 181 } 182 183 void run(string[] args, SimpleEventHandler handler) { 184 _launchedHandler = handler; 185 run(args); 186 } 187 188 /** 189 Start the HttpServer , and block current thread. 190 */ 191 void run(string[] args) { 192 tryRegister!ConfigServiceProvider(); 193 194 ConfigManager manager = serviceContainer().resolve!ConfigManager; 195 manager.hostEnvironment = _environment; 196 197 if(_commands.length > 0) { 198 _appConfig = serviceContainer().resolve!ApplicationConfig(); 199 bootstrap(); 200 201 Console console = new Console(_description, _ver); 202 console.setAutoExit(false); 203 204 foreach(hunt.console.Command cmd; _commands) { 205 console.add(cmd); 206 } 207 208 try { 209 console.run(args); 210 } catch(Exception ex) { 211 warning(ex); 212 } catch(Error er) { 213 error(er); 214 } 215 216 return; 217 } 218 219 220 if (args.length > 1) { 221 ServeCommand serveCommand = new ServeCommand(); 222 serveCommand.onInput((ServeSignature signature) { 223 version (HUNT_DEBUG) tracef(signature.to!string); 224 225 // 226 string configPath = signature.configPath; 227 if(!configPath.empty()) { 228 _environment.configPath = signature.configPath; 229 } 230 231 // 232 string envName = signature.environment; 233 234 if(envName.empty()) { 235 _environment.name = DEFAULT_RUNTIME_ENVIRONMENT; 236 } else { 237 _environment.name = envName; 238 } 239 240 // loading config 241 _appConfig = serviceContainer().resolve!ApplicationConfig(); 242 243 // 244 string host = signature.host; 245 if(!host.empty()) { 246 _appConfig.http.address = host; 247 } 248 249 // 250 ushort port = signature.port; 251 if(port > 0) { 252 _appConfig.http.port = signature.port; 253 } 254 255 bootstrap(); 256 }); 257 258 Console console = new Console(_description, _ver); 259 console.setAutoExit(false); 260 console.add(serveCommand); 261 262 try { 263 console.run(args); 264 } catch(Exception ex) { 265 warning(ex); 266 } catch(Error er) { 267 error(er); 268 } 269 270 } else { 271 _appConfig = serviceContainer().resolve!ApplicationConfig(); 272 bootstrap(); 273 } 274 } 275 276 /** 277 Stop the server. 278 */ 279 void stop() { 280 _server.stop(); 281 } 282 283 // void registGrpcSerive(T)(){ 284 285 // } 286 287 /** 288 * https://laravel.com/docs/6.x/lifecycle 289 */ 290 private void bootstrap() { 291 // _appConfig = serviceContainer().resolve!ApplicationConfig(); 292 293 // 294 registerProviders(); 295 296 // 297 initializeLogger(); 298 299 version (WITH_HUNT_TRACE) { 300 initializeTracer(); 301 } 302 303 // Resolve the HTTP server firstly 304 _server = serviceContainer.resolve!(HttpServer); 305 _serverOptions = _server.getHttpOptions(); 306 307 // 308 if(_configuringHandler !is null) { 309 _configuringHandler(); 310 } 311 312 // booting Providers 313 bootProviders(); 314 315 // 316 showLogo(); 317 318 // Launch the HTTP server. 319 _server.start(); 320 321 // Notify that the application is ready. 322 if(_launchedHandler !is null) { 323 _launchedHandler(); 324 } 325 } 326 327 private void showLogo() { 328 Address bindingAddress = parseAddress(_serverOptions.getHost(), 329 cast(ushort) _serverOptions.getPort()); 330 331 // dfmt off 332 string cliText = ` 333 334 ___ ___ ___ ___ ________ _________ 335 |\ \|\ \ |\ \|\ \ |\ ___ \ |\___ ___\ Hunt Framework ` ~ HUNT_VERSION ~ ` 336 \ \ \\\ \ \ \ \\\ \ \ \ \\ \ \ \|___ \ \_| 337 \ \ __ \ \ \ \\\ \ \ \ \\ \ \ \ \ \ Listening: ` ~ bindingAddress.toString() ~ ` 338 \ \ \ \ \ \ \ \\\ \ \ \ \\ \ \ \ \ \ TLS: ` ~ (_serverOptions.isSecureConnectionEnabled() ? "Enabled" : "Disabled") ~ ` 339 \ \__\ \__\ \ \_______\ \ \__\\ \__\ \ \__\ 340 \|__|\|__| \|_______| \|__| \|__| \|__| https://www.huntframework.com 341 342 `; 343 writeln(cliText); 344 // dfmt on 345 346 if (_serverOptions.isSecureConnectionEnabled()) 347 writeln("Try to browse https://", bindingAddress.toString()); 348 else 349 writeln("Try to browse http://", bindingAddress.toString()); 350 } 351 352 Application providerLisener(ServiceProviderListener listener) { 353 _providerListener = listener; 354 return this; 355 } 356 357 ServiceProviderListener providerLisener() { 358 return _providerListener; 359 } 360 361 private ServiceProviderListener _providerListener; 362 363 private void initializeProviderListener() { 364 _providerListener = new DefaultServiceProviderListener; 365 } 366 367 /** 368 * Register all the default service providers 369 */ 370 private void registerProviders() { 371 // Register all the default service providers 372 static foreach (T; DefaultServiceProviders) { 373 static if (!is(T == ConfigServiceProvider)) { 374 tryRegister!T(); 375 } 376 } 377 378 // Register all the service provided by the providers 379 ServiceProvider[] providers = serviceContainer().resolveAll!(ServiceProvider); 380 version(HUNT_DEBUG) infof("Registering all the service providers (%d)...", providers.length); 381 382 // foreach(ServiceProvider p; providers) { 383 // p.register(); 384 // _providerListener.registered(typeid(p)); 385 // serviceContainer().autowire(p); 386 // } 387 388 } 389 390 /** 391 * Booting all the providers 392 */ 393 private void bootProviders() { 394 ServiceProvider[] providers = serviceContainer().resolveAll!(ServiceProvider); 395 version(HUNT_DEBUG) infof("Booting all the service providers (%d)...", providers.length); 396 397 foreach (ServiceProvider p; providers) { 398 p.boot(); 399 _providerListener.booted(typeid(p)); 400 } 401 _isBooted = true; 402 } 403 404 private void initializeLogger() { 405 ApplicationConfig.LoggingConfig conf = _appConfig.logging; 406 407 hunt.logging.Logger.LogLevel loggerLevel = hunt.logging.Logger.LogLevel.LOG_DEBUG; 408 switch (toLower(conf.level)) { 409 case "critical": 410 case "error": 411 loggerLevel = hunt.logging.Logger.LogLevel.LOG_ERROR; 412 break; 413 case "fatal": 414 loggerLevel = hunt.logging.Logger.LogLevel.LOG_FATAL; 415 break; 416 case "warning": 417 loggerLevel = hunt.logging.Logger.LogLevel.LOG_WARNING; 418 break; 419 case "info": 420 loggerLevel = hunt.logging.Logger.LogLevel.LOG_INFO; 421 break; 422 case "off": 423 loggerLevel = hunt.logging.Logger.LogLevel.LOG_Off; 424 break; 425 default: 426 break; 427 } 428 429 version (HUNT_DEBUG) { 430 hunt.logging.ConsoleLogger.LogLevel level = hunt.logging.ConsoleLogger.LogLevel.Trace; 431 switch (toLower(conf.level)) { 432 case "critical": 433 case "error": 434 level = hunt.logging.ConsoleLogger.LogLevel.Error; 435 break; 436 case "fatal": 437 level = hunt.logging.ConsoleLogger.LogLevel.Fatal; 438 break; 439 case "warning": 440 level = hunt.logging.ConsoleLogger.LogLevel.Warning; 441 break; 442 case "info": 443 level = hunt.logging.ConsoleLogger.LogLevel.Info; 444 break; 445 case "off": 446 level = hunt.logging.ConsoleLogger.LogLevel.Off; 447 break; 448 default: 449 break; 450 } 451 452 ConsoleLogger.setLogLevel(level); 453 } else { 454 LogConf logconf; 455 logconf.level = loggerLevel; 456 logconf.disableConsole = conf.disableConsole; 457 458 if (!conf.file.empty) 459 logconf.fileName = buildPath(conf.path, conf.file); 460 461 logconf.maxSize = conf.maxSize; 462 logconf.maxNum = conf.maxNum; 463 464 logLoadConf(logconf); 465 } 466 467 initFilebeatLogger(loggerLevel, conf); 468 } 469 470 private void initFilebeatLogger(hunt.logging.Logger.LogLevel level, ApplicationConfig.LoggingConfig conf) { 471 LogConf logconf; 472 logconf.level = level; 473 logconf.disableConsole = conf.disableConsole; 474 475 if (!conf.file.empty) 476 logconf.fileName = buildPath(conf.path, "filebeat", conf.file); 477 478 logconf.maxSize = conf.maxSize; 479 logconf.maxNum = conf.maxNum; 480 481 import core.thread; 482 import std.conv; 483 import std.process; 484 import std.datetime; 485 import std.json; 486 487 _logger = new Logger(logconf, 488 (string time_prior, string tid, string level, string myFunc, 489 string msg, string file, size_t line) { 490 491 SysTime now = Clock.currTime; 492 std.datetime.DateTime dt ; 493 494 JSONValue jv; 495 jv["timestamp"] = (cast(std.datetime.DateTime)now).toISOExtString(); 496 jv["msecs"] = now.fracSecs.total!("msecs"); 497 jv["level"] = level; 498 jv["file"] = file; 499 jv["module"] = myFunc; 500 jv["funcName"] = myFunc; 501 jv["line"] = line; 502 jv["thread"] = tid.to!long; 503 jv["threadName"] = Thread.getThis().name(); 504 jv["process"] = thisProcessID(); 505 jv["message"] = msg; 506 return jv.toString(); 507 }); 508 } 509 510 version (WITH_HUNT_TRACE) { 511 private void initializeTracer() { 512 513 isTraceEnabled = _appConfig.trace.enable; 514 515 // initialize HttpSender 516 httpSender().endpoint(_appConfig.trace.zipkin); 517 } 518 } 519 520 private void setDefaultLogging() { 521 version (HUNT_DEBUG) { 522 } else { 523 LogConf logconf; 524 logconf.level = hunt.logging.LogLevel.LOG_Off; 525 logconf.disableConsole = true; 526 logLoadConf(logconf); 527 } 528 } 529 530 // dfmtoff Some helpers 531 import hunt.cache.Cache; 532 import hunt.entity.EntityManager; 533 import hunt.entity.EntityManagerFactory; 534 import hunt.framework.breadcrumb.BreadcrumbsManager; 535 import hunt.framework.queue; 536 import hunt.framework.Simplify; 537 import hunt.framework.task; 538 import hunt.logging.Logger; 539 540 Logger logger() { 541 return _logger; 542 } 543 private Logger _logger; 544 545 ApplicationConfig config() { 546 return _appConfig; 547 } 548 549 RouteConfigManager route() { 550 return serviceContainer.resolve!(RouteConfigManager); 551 } 552 553 AuthService auth() { 554 return serviceContainer.resolve!(AuthService); 555 } 556 557 Redis redis() { 558 RedisPool pool = serviceContainer.resolve!RedisPool(); 559 Redis r = pool.getResource(); 560 registerResoure(new RedisCloser(r)); 561 return r; 562 } 563 564 RedisCluster redisCluster() { 565 RedisCluster cluster = serviceContainer.resolve!RedisCluster(); 566 return cluster; 567 } 568 569 Cache cache() { 570 return serviceContainer.resolve!(Cache); 571 } 572 573 TaskQueue queue() { 574 return serviceContainer.resolve!(TaskQueue); 575 } 576 577 Worker task() { 578 return serviceContainer.resolve!(Worker); 579 } 580 581 deprecated("Using defaultEntityManager instead.") 582 EntityManager entityManager() { 583 EntityManager _entityManager = serviceContainer.resolve!(EntityManagerFactory).currentEntityManager(); 584 registerResoure(new EntityCloser(_entityManager)); 585 return _entityManager; 586 } 587 588 BreadcrumbsManager breadcrumbs() { 589 if(_breadcrumbs is null) { 590 _breadcrumbs = serviceContainer.resolve!BreadcrumbsManager(); 591 } 592 return _breadcrumbs; 593 } 594 private BreadcrumbsManager _breadcrumbs; 595 596 I18n translation() { 597 return serviceContainer.resolve!(I18n); 598 } 599 // dfmton 600 } 601 602 /** 603 * 604 */ 605 Application app() { 606 return Application.instance(); 607 } 608 609 610 Logger filebeatLogger() { 611 return app().logger(); 612 }