1 /**
2  * Реализация плагина для содания веб приложения
3  *
4  * Copyright: (c) 2015-2020, Milofon Project.
5  * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file.
6  * Author: <m.galanin@milofon.pro> Maksim Galanin
7  * Date: 2020-04-29
8  */
9 
10 module dango.web.plugin;
11 
12 public
13 {
14     import dango.web.server : WebApplicationServer;
15 }
16 
17 private
18 {
19     import std.algorithm.searching : canFind;
20     import std.algorithm.iteration : filter, map;
21     import std.algorithm.sorting : sort;
22     import std.format : fmt = format;
23     import std.array : array;
24     import std.uni : toUpper;
25 
26     import vibe.http.router : URLRouter;
27 
28     import dango.inject;
29     import dango.system.application : Application, UniConf;
30     import dango.system.properties;
31     import dango.system.exception;
32     import dango.system.logging;
33     import dango.system.plugin;
34 
35     import dango.web.middleware;
36     import dango.web.controller;
37     import dango.web.server;
38 }
39 
40 
41 /**
42  * Плагин поддерживающий обработку запросов
43  */
44 interface WebPlugin : Plugin
45 {
46     /**
47      * Регистрация сервера
48      */
49     void registerServer(void delegate(WebApplicationServer) dg) @safe;
50 }
51 
52 
53 /**
54  * Реализация демона для запуска web приложения
55  */
56 class WebApplicationPlugin : DaemonPlugin, PluginContainer!WebPlugin
57 {
58     private
59     {
60         WebApplicationServer[] _servers;
61         WebPlugin[] _plugins;
62     }
63 
64 
65     /**
66      * Свойство возвращает наименование плагина
67      */
68     string name() pure @safe nothrow
69     {
70         return "WEB";
71     }
72 
73     /**
74      * Свойство возвращает версию приложения
75      */
76     SemVer release() pure nothrow @safe
77     {
78         return SemVer(0, 0, 1);
79     }
80 
81     /**
82      * Запуск процесса
83      */
84     int startDaemon()
85     {
86         foreach (plugin; _plugins)
87             plugin.registerServer((server) {
88                     _servers ~= server;
89                 });
90 
91         foreach (WebApplicationServer server; _servers)
92             server.listen();
93 
94         return 0;
95     }
96 
97     /**
98      * Остановка процесса
99      *
100      * Params:
101      * exitStatus = Код завершения приложения
102      */
103     int stopDaemon(int exitStatus)
104     {
105         foreach (WebApplicationServer server; _servers)
106             server.shutdown();
107         return exitStatus;
108     }
109 
110     /**
111      * Регистрация плагина
112      * Params:
113      * plugin = Плагин для регистрации
114      */
115     void collectPlugin(WebPlugin plugin) @safe nothrow
116     {
117         _plugins ~= plugin;
118     }
119 }
120 
121 
122 /**
123  * Контекст регистрации компонентов контроллера
124  */
125 alias WebServerContext = PluginContext!(WebServerPlugin);
126 
127 
128 /**
129  * Плагин сервера с роутингом
130  */
131 class WebServerPlugin : WebPlugin
132 {
133     private 
134     {
135         WebControllerFactory[string] _controllers;
136         WebMiddlewareFactory[string] _middlewares;
137         DependencyContainer _container;
138         UniConf _config;
139     }
140 
141 
142     /**
143      * Main constructor
144      */
145     @Inject
146     this(Application application)
147     {
148         this._container = application.getContainer();
149         this._config = application.getConfig();
150     }
151 
152     /**
153      * Свойство возвращает наименование плагина
154      */
155     string name() pure @safe nothrow
156     {
157         return "Web Server";
158     }
159 
160     /**
161      * Свойство возвращает версию приложения
162      */
163     SemVer release() pure nothrow @safe
164     {
165         return SemVer(0, 0, 1);
166     }
167 
168     /**
169      * Регистрация сервера
170      */
171     void registerServer(void delegate(WebApplicationServer) @safe dg) @safe
172     {
173         auto webConfigs = _config.getOrEnforce!UniConf("web",
174                 "Not found web application configurations");
175 
176         foreach (UniConf webConf; webConfigs.toSequence())
177         {
178             if (webConf.getOrElse("enabled", false))
179             {
180                 if (auto server = createServer(webConf))
181                     dg(server);
182             }
183         }
184     }
185 
186     /**
187      * Регистрация middleware
188      */
189     void registerMiddleware(M : WebMiddleware)(string name) @safe
190     {
191         alias MF = ComponentFactoryCtor!(WebMiddleware, M, UniConf);
192         registerMiddleware!(MF)(name);
193     }
194 
195     /**
196      * Регистрация middleware с использованием фабрики
197      */
198     void registerMiddleware(MF : WebMiddlewareFactory)(string name) @safe
199     {
200         auto factory = new WrapDependencyFactory!(MF)();
201         registerMiddleware!MF(name, factory);
202     }
203 
204     /**
205      * Регистрация middleware с использованием существующей фабрики
206      */
207     void registerMiddleware(MF : WebMiddlewareFactory)(string name, 
208             WebMiddlewareFactory factory) @safe
209     {
210         string uName = name.toUpper;
211         _middlewares[uName] = factory;
212     }
213 
214     /**
215      * Регистрация контроллера
216      */
217     void registerController(C : WebController)(string name) @safe
218     {
219         alias CF = ComponentFactoryCtor!(WebController, C, UniConf);
220         registerController!(CF)(name);
221     }
222 
223     /**
224      * Регистрация контроллера с использованием фабрики
225      */
226     void registerController(CF : WebControllerFactory)(string name) @safe
227     {
228         auto factory = new WrapDependencyFactory!(CF)();
229         registerController!CF(name, factory);
230     }
231 
232     /**
233      * Регистрация контроллера с использованием существующей фабрики
234      */
235     void registerController(CF : WebControllerFactory)(string name,
236             WebControllerFactory factory) @safe
237     {
238         string uName = name.toUpper;
239         _controllers[uName] = factory;
240     }
241 
242 
243 private:
244 
245 
246     WebApplicationServer createServer(UniConf config) @safe
247     {
248         string webName = config.getOrElse!string("__name", "Undefined");
249         logInfo("Configuring web server '%s'", webName);
250 
251         URLRouter routes = new URLRouter();
252         auto settings = loadHTTPServerSettings(config);
253 
254         MiddlewareInfo[] middlewares;
255 
256         foreach (UniConf mdwConf; config.toSequence("middleware"))
257         {
258             string mdwName = mdwConf.getNameOrEnforce(
259                         "Not defined middleware name");
260 
261             long ordering = mdwConf.getOrElse!long("order", 0);
262             string label = mdwConf.getOrElse!string("label", mdwName);
263             enforceConfig(!middlewares.canFind!((m) {
264                         return m.label == label;
265                     }), fmt!"Middleware '%s' already defined"(label));
266 
267             auto mdwFactory = mdwName.toUpper in _middlewares;
268             enforceConfig(mdwFactory !is null,
269                     fmt!"Middleware '%s' not register"(mdwName));
270 
271             middlewares ~= MiddlewareInfo(label, ordering, mdwConf, *mdwFactory);
272         }
273 
274         foreach (UniConf ctrConf; config.toSequence("controller")
275                     .filter!((c) => c.getOrElse("enabled", false)))
276         {
277             string ctrName = getNameOrEnforce(ctrConf,
278                         "Not defined controller name");
279 
280             auto ctrlFactory = ctrName.toUpper in _controllers;
281             enforceConfig(ctrlFactory !is null,
282                     fmt!"Controller '%s' not register"(ctrName));
283 
284             auto ctrl = ctrlFactory.createComponent(_container, ctrConf);
285 
286             auto ctrlMiddlewares = ctrConf.toSequence("middlewares")
287                     .map!(pm => pm.get!string);
288 
289             // проверка на наличие конфигураций
290             foreach (string mdwLabel; ctrlMiddlewares)
291                 enforceConfig(middlewares.canFind!((m) => m.label == mdwLabel),
292                         fmt!"Middleware %s not found configuration"(mdwLabel));
293 
294             auto activeMiddlewares = middlewares.filter!((mdwConf) {
295                         bool def = mdwConf.config.getOrElse!bool("default", false);
296                         return def || ctrlMiddlewares.canFind(mdwConf.label);
297                     }).array;
298 
299             activeMiddlewares.sort!((a, b) => a.ordering > b.ordering);
300 
301             logInfo("Register controller: '%s'", ctrName);
302             logInfo("  Activated middlewares: %s", activeMiddlewares
303                     .map!(m => m.label));
304 
305             string prefix = ctrConf.getOrElse("prefix", "");
306 
307             ctrl.registerChains((HTTPMethod method, string path, Chain ch) @safe {
308                     string absPath = joinInetPath(prefix, path);
309                     foreach (mdwInfo; activeMiddlewares)
310                     {
311                         WebMiddleware mdw = mdwInfo.factory.createComponent(
312                                 _container, mdwInfo.config);
313                         ch.attachMiddleware(mdw);
314                         mdw.registerHandlers(method, absPath, (mm, mp, mh) @safe {
315                                 routes.match(mm, mp, mh);
316                             });
317                     }
318                     logInfo("  %s: %s", method, absPath);
319                     routes.match(method, absPath, ch);
320                 });
321         }
322 
323         routes.rebuild();
324 
325         return new HTTPApplicationServer(settings, routes);
326     }
327 
328 
329     struct MiddlewareInfo
330     {
331         string label;
332         long ordering;
333         UniConf config;
334         WebMiddlewareFactory factory;
335     }
336 }
337