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 }