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; 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 void run(string[] args) { 189 try { 190 doRun(args); 191 import core.thread; 192 thread_joinAll(); 193 } catch(Exception ex) { 194 warning(ex); 195 } catch(Error er) { 196 error(er); 197 } 198 } 199 200 private void doRun(string[] args) { 201 202 203 tryRegister!ConfigServiceProvider(); 204 205 ConfigManager manager = serviceContainer().resolve!ConfigManager; 206 manager.hostEnvironment = _environment; 207 208 if(_commands.length > 0) { 209 _appConfig = serviceContainer().resolve!ApplicationConfig(); 210 bootstrap(); 211 212 Console console = new Console(_description, _ver); 213 console.setAutoExit(false); 214 215 foreach(hunt.console.Command cmd; _commands) { 216 console.add(cmd); 217 } 218 219 try { 220 console.run(args); 221 } catch(Exception ex) { 222 warning(ex); 223 } catch(Error er) { 224 error(er); 225 } 226 227 return; 228 } 229 230 231 if (args.length > 1) { 232 ServeCommand serveCommand = new ServeCommand(); 233 serveCommand.onInput((ServeSignature signature) { 234 version (HUNT_DEBUG) tracef(signature.to!string); 235 236 // 237 string configPath = signature.configPath; 238 if(!configPath.empty()) { 239 _environment.configPath = signature.configPath; 240 } 241 242 // 243 string envName = signature.environment; 244 245 if(envName.empty()) { 246 _environment.name = DEFAULT_RUNTIME_ENVIRONMENT; 247 } else { 248 _environment.name = envName; 249 } 250 251 // loading config 252 _appConfig = serviceContainer().resolve!ApplicationConfig(); 253 254 // 255 string host = signature.host; 256 if(!host.empty()) { 257 _appConfig.http.address = host; 258 } 259 260 // 261 ushort port = signature.port; 262 if(port > 0) { 263 _appConfig.http.port = signature.port; 264 } 265 266 bootstrap(); 267 }); 268 269 Console console = new Console(_description, _ver); 270 console.setAutoExit(false); 271 console.add(serveCommand); 272 273 try { 274 console.run(args); 275 } catch(Exception ex) { 276 warning(ex); 277 } catch(Error er) { 278 error(er); 279 } 280 281 } else { 282 _appConfig = serviceContainer().resolve!ApplicationConfig(); 283 bootstrap(); 284 } 285 } 286 287 /** 288 Stop the server. 289 */ 290 void stop() { 291 _server.stop(); 292 } 293 294 // void registGrpcSerive(T)(){ 295 296 // } 297 298 /** 299 * https://laravel.com/docs/6.x/lifecycle 300 */ 301 private void bootstrap() { 302 // _appConfig = serviceContainer().resolve!ApplicationConfig(); 303 304 version(HUNT_DEBUG) { 305 infof("Application environment: %s", _environment.name); 306 } 307 308 // 309 registerProviders(); 310 311 // 312 initializeLogger(); 313 314 version (WITH_HUNT_TRACE) { 315 initializeTracer(); 316 } 317 318 // Resolve the HTTP server firstly 319 _server = serviceContainer.resolve!(HttpServer); 320 _serverOptions = _server.getHttpOptions(); 321 322 // 323 if(_configuringHandler !is null) { 324 _configuringHandler(); 325 } 326 327 // booting Providers 328 bootProviders(); 329 330 // 331 showLogo(); 332 333 // Launch the HTTP server. 334 _server.start(); 335 336 // Notify that the application is ready. 337 if(_launchedHandler !is null) { 338 _launchedHandler(); 339 } 340 } 341 342 private void showLogo() { 343 Address bindingAddress = parseAddress(_serverOptions.getHost(), 344 cast(ushort) _serverOptions.getPort()); 345 346 // dfmt off 347 string cliText = ` 348 349 ___ ___ ___ ___ ________ _________ 350 |\ \|\ \ |\ \|\ \ |\ ___ \ |\___ ___\ Hunt Framework ` ~ HUNT_VERSION ~ ` 351 \ \ \\\ \ \ \ \\\ \ \ \ \\ \ \ \|___ \ \_| 352 \ \ __ \ \ \ \\\ \ \ \ \\ \ \ \ \ \ Listening: ` ~ bindingAddress.toString() ~ ` 353 \ \ \ \ \ \ \ \\\ \ \ \ \\ \ \ \ \ \ TLS: ` ~ (_serverOptions.isSecureConnectionEnabled() ? "Enabled" : "Disabled") ~ ` 354 \ \__\ \__\ \ \_______\ \ \__\\ \__\ \ \__\ 355 \|__|\|__| \|_______| \|__| \|__| \|__| https://www.huntframework.com 356 357 `; 358 writeln(cliText); 359 // dfmt on 360 361 if (_serverOptions.isSecureConnectionEnabled()) 362 writeln("Try to browse https://", bindingAddress.toString()); 363 else 364 writeln("Try to browse http://", bindingAddress.toString()); 365 } 366 367 Application providerLisener(ServiceProviderListener listener) { 368 _providerListener = listener; 369 return this; 370 } 371 372 ServiceProviderListener providerLisener() { 373 return _providerListener; 374 } 375 376 private ServiceProviderListener _providerListener; 377 378 private void initializeProviderListener() { 379 _providerListener = new DefaultServiceProviderListener; 380 } 381 382 /** 383 * Register all the default service providers 384 */ 385 private void registerProviders() { 386 // Register all the default service providers 387 static foreach (T; DefaultServiceProviders) { 388 static if (!is(T == ConfigServiceProvider)) { 389 tryRegister!T(); 390 } 391 } 392 393 // Register all the service provided by the providers 394 ServiceProvider[] providers = serviceContainer().resolveAll!(ServiceProvider); 395 version(HUNT_DEBUG) infof("Registering all the service providers (%d)...", providers.length); 396 397 } 398 399 /** 400 * Booting all the providers 401 */ 402 private void bootProviders() { 403 ServiceProvider[] providers = serviceContainer().resolveAll!(ServiceProvider); 404 version(HUNT_DEBUG) infof("Booting all the service providers (%d)...", providers.length); 405 406 foreach (ServiceProvider p; providers) { 407 p.boot(); 408 _providerListener.booted(typeid(p)); 409 } 410 _isBooted = true; 411 } 412 413 private void initializeLogger() { 414 ApplicationConfig.LoggingConfig conf = _appConfig.logging; 415 416 hunt.logging.Logger.LogLevel loggerLevel = hunt.logging.Logger.LogLevel.LOG_DEBUG; 417 switch (toLower(conf.level)) { 418 case "critical": 419 case "error": 420 loggerLevel = hunt.logging.Logger.LogLevel.LOG_ERROR; 421 break; 422 case "fatal": 423 loggerLevel = hunt.logging.Logger.LogLevel.LOG_FATAL; 424 break; 425 case "warning": 426 loggerLevel = hunt.logging.Logger.LogLevel.LOG_WARNING; 427 break; 428 case "info": 429 loggerLevel = hunt.logging.Logger.LogLevel.LOG_INFO; 430 break; 431 case "off": 432 loggerLevel = hunt.logging.Logger.LogLevel.LOG_Off; 433 break; 434 default: 435 break; 436 } 437 438 LogConf logconf; 439 logconf.level = loggerLevel; 440 logconf.disableConsole = conf.disableConsole; 441 442 if (!conf.file.empty) 443 logconf.fileName = buildPath(conf.path, conf.file); 444 445 logconf.maxSize = conf.maxSize; 446 logconf.maxNum = conf.maxNum; 447 448 logLoadConf(logconf); 449 450 451 initFilebeatLogger(loggerLevel, conf); 452 } 453 454 private void initFilebeatLogger(hunt.logging.Logger.LogLevel level, ApplicationConfig.LoggingConfig conf) { 455 LogConf logconf; 456 logconf.level = level; 457 logconf.disableConsole = conf.disableConsole; 458 459 if (!conf.file.empty) 460 logconf.fileName = buildPath(conf.path, "filebeat", conf.file); 461 462 logconf.maxSize = conf.maxSize; 463 logconf.maxNum = conf.maxNum; 464 465 import core.thread; 466 import std.conv; 467 import std.process; 468 import std.datetime; 469 import std.json; 470 471 _logger = new Logger(logconf, 472 (string time_prior, string tid, string level, string myFunc, 473 string msg, string file, size_t line) { 474 475 SysTime now = Clock.currTime; 476 std.datetime.DateTime dt ; 477 478 JSONValue jv; 479 jv["timestamp"] = (cast(std.datetime.DateTime)now).toISOExtString(); 480 jv["msecs"] = now.fracSecs.total!("msecs"); 481 jv["level"] = level; 482 jv["file"] = file; 483 jv["module"] = myFunc; 484 jv["funcName"] = myFunc; 485 jv["line"] = line; 486 jv["thread"] = tid.to!long; 487 jv["threadName"] = Thread.getThis().name(); 488 jv["process"] = thisProcessID(); 489 jv["message"] = msg; 490 return jv.toString(); 491 }); 492 } 493 494 version (WITH_HUNT_TRACE) { 495 private void initializeTracer() { 496 497 isTraceEnabled = _appConfig.trace.enable; 498 499 // initialize HttpSender 500 httpSender().endpoint(_appConfig.trace.zipkin); 501 } 502 } 503 504 private void setDefaultLogging() { 505 version (HUNT_DEBUG) { 506 } else { 507 LogConf logconf; 508 logconf.level = hunt.logging.LogLevel.LOG_Off; 509 logconf.disableConsole = true; 510 logLoadConf(logconf); 511 } 512 } 513 514 // dfmtoff Some helpers 515 import hunt.cache.Cache; 516 import hunt.entity.EntityManager; 517 import hunt.entity.EntityManagerFactory; 518 import hunt.framework.breadcrumb.BreadcrumbsManager; 519 import hunt.framework.queue; 520 import hunt.framework.task; 521 import hunt.logging.Logger; 522 523 Logger logger() { 524 return _logger; 525 } 526 private Logger _logger; 527 528 ApplicationConfig config() { 529 return _appConfig; 530 } 531 532 RouteConfigManager route() { 533 return serviceContainer.resolve!(RouteConfigManager); 534 } 535 536 AuthService auth() { 537 return serviceContainer.resolve!(AuthService); 538 } 539 540 Redis redis() { 541 RedisPool pool = serviceContainer.resolve!RedisPool(); 542 Redis r = pool.borrow(); 543 registerResoure(new RedisCloser(r)); 544 return r; 545 } 546 547 RedisCluster redisCluster() { 548 RedisCluster cluster = serviceContainer.resolve!RedisCluster(); 549 return cluster; 550 } 551 552 Cache cache() { 553 return serviceContainer.resolve!(Cache); 554 } 555 556 TaskQueue queue() { 557 return serviceContainer.resolve!(TaskQueue); 558 } 559 560 Worker task() { 561 return serviceContainer.resolve!(Worker); 562 } 563 564 deprecated("Using defaultEntityManager instead.") 565 EntityManager entityManager() { 566 EntityManager _entityManager = serviceContainer.resolve!(EntityManagerFactory).currentEntityManager(); 567 registerResoure(new EntityCloser(_entityManager)); 568 return _entityManager; 569 } 570 571 BreadcrumbsManager breadcrumbs() { 572 if(_breadcrumbs is null) { 573 _breadcrumbs = serviceContainer.resolve!BreadcrumbsManager(); 574 } 575 return _breadcrumbs; 576 } 577 private BreadcrumbsManager _breadcrumbs; 578 579 I18n translation() { 580 return serviceContainer.resolve!(I18n); 581 } 582 // dfmton 583 } 584 585 /** 586 * 587 */ 588 Application app() { 589 return Application.instance(); 590 } 591 592 593 Logger filebeatLogger() { 594 return app().logger(); 595 }