1 /**
2  * Модуль реализации сервер web application
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.server;
11 
12 private
13 {
14     import vibe.core.core;
15     import vibe.http.server;
16     import vibe.stream.tls : createTLSContext, TLSContext, TLSContextKind;
17 
18     import uniconf.core : UniConf;
19 
20     import dango.system.logging;
21     import dango.system.properties;
22 }
23 
24 
25 /**
26  * Интерфейс Web сервера
27  */
28 interface WebApplicationServer
29 {
30     /**
31      * Запуск сервера
32      */
33     void listen();
34 
35 
36     /**
37      * Остановка сервера
38      */
39     void shutdown();
40 }
41 
42 
43 /**
44  * Класс веб сервера
45  */
46 class HTTPApplicationServer : WebApplicationServer
47 {
48     private
49     {
50         HTTPListener[] _listeners;
51         HTTPServerSettings _httpSettings;
52         HTTPServerRequestHandler _handler;
53     }
54 
55 
56     this(HTTPServerSettings settings, HTTPServerRequestHandler handler) @safe
57     {
58         this._httpSettings = settings;
59         this._handler = handler;
60     }
61 
62     /**
63      * Запуск сервера
64      */
65     void listen()
66     {
67         runWorkerTaskDist!(runWorker)(cast(shared)this);
68         logInfo("HTTP Application Server start");
69     }
70 
71     /**
72      * Остановка сервера
73      */
74     void shutdown()
75     {
76         foreach (HTTPListener listener; _listeners)
77             listener.stopListening();
78         logInfo("HTTP Application Server stop");
79     }
80 
81 
82     private void runWorker() shared
83     {
84         HTTPServerSettings settings = cast(HTTPServerSettings)_httpSettings;
85         settings.options = HTTPServerOption.reusePort;
86         HTTPServerRequestHandler handler = cast(HTTPServerRequestHandler)_handler;
87         _listeners ~= cast(shared)listenHTTP(settings, handler);
88     }
89 }
90 
91 
92 /**
93  * Функция стоит объект настроект http сервера по параметрам конфигурации
94  * Params:
95  *
96  * config = Конфигурация
97  */
98 HTTPServerSettings loadHTTPServerSettings(UniConf config) @safe
99 {
100     import core.time : dur;
101     import std.algorithm.iteration : map;
102     import std.array : array;
103 
104     HTTPServerSettings settings = new HTTPServerSettings();
105 
106     auto host = config.getOrEnforce!UniConf("host", "Not defined host property");
107     settings.bindAddresses = host.toSequence().map!((h) {
108             return h.get!string;
109         }).array;
110 
111     settings.port = config.getOrEnforce!ushort("port", "Not defined port property");
112     settings.options = HTTPServerOption.defaults;
113 
114     if ("hostName" in config)
115         settings.hostName = config.get!string("hostName");
116 
117     if ("maxRequestTime" in config)
118         settings.maxRequestTime = dur!"seconds"(config.get!long("maxRequestTime"));
119 
120     if ("keepAliveTimeout" in config)
121         settings.keepAliveTimeout = dur!"seconds"(config.get!long("keepAliveTimeout"));
122 
123     if ("maxRequestSize" in config)
124         settings.maxRequestSize = config.get!long("maxRequestSize");
125 
126     if ("maxRequestHeaderSize" in config)
127         settings.maxRequestHeaderSize = config.get!long("maxRequestHeaderSize");
128 
129     if ("accessLogFormat" in config)
130         settings.accessLogFormat = config.get!string("accessLogFormat");
131 
132     if ("accessLogFile" in config)
133         settings.accessLogFile = config.get!string("accessLogFile");
134 
135     settings.accessLogToConsole = config.getOrElse("accessLogToConsole", false);
136 
137     if ("ssl" in config)
138     {
139         UniConf sslConfig = config.get!UniConf("ssl");
140         settings.tlsContext = createTLSContextFrom(sslConfig);
141     }
142 
143     return settings;
144 }
145 
146 
147 /**
148  * Создание TLS контекста из конфигурации сервиса
149  */
150 TLSContext createTLSContextFrom(UniConf sslConfig) @safe
151 {
152     TLSContext tlsCtx = createTLSContext(TLSContextKind.server);
153 
154     auto certChainFile = sslConfig.getOrEnforce!string("certificateChainFile",
155             "Not defined certificateChainFile property");
156     auto privateKeyFile = sslConfig.getOrEnforce!string("privateKeyFile",
157             "Not defined privateKeyFile property");
158 
159     tlsCtx.useCertificateChainFile(certChainFile);
160     tlsCtx.usePrivateKeyFile(privateKeyFile);
161 
162     return tlsCtx;
163 }
164 
165 
166 /**
167  * Добаляет перфикс к url пути
168  */
169 string joinInetPath(string prefix, string path) @safe
170 {
171     import vibe.core.path : InetPath;
172 
173     auto parent = InetPath(prefix);
174     auto child = InetPath(path);
175 
176     if (!parent.absolute)
177         parent = InetPath("/") ~ parent;
178 
179     if (child.absolute)
180     {
181         auto childSegments = child.bySegment();
182         childSegments.popFront();
183         child = InetPath(childSegments);
184     }
185 
186     if (!child.empty)
187         parent ~= child;
188 
189     return parent.toString;
190 }
191 
192 @("Should work joinInetPath method")
193 @safe unittest
194 {
195     assert (joinInetPath("/", "api") == "/api");
196     assert (joinInetPath("", "api") == "/api");
197     assert (joinInetPath("/", "/api") == "/api");
198     assert (joinInetPath("", "/api") == "/api");
199 }
200