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 }