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 }