1 /**
2  * Модуль для генерации http обработчиков
3  *
4  * Copyright: (c) 2015-2017, Milofon Project.
5  * License: Subject to the terms of the BSD license, as written in the included LICENSE.txt file.
6  * Authors: Maksim Galanin
7  */
8 module dango.controller.http;
9 
10 public
11 {
12     import vibe.http.router : URLRouter;
13     import vibe.http.server : HTTPServerRequest, HTTPServerResponse, HTTPServerRequestDelegate;
14 }
15 
16 private
17 {
18     import core.time : dur;
19 
20     import std.traits : getUDAs;
21     import std.meta : Alias;
22 
23     import vibe.core.path : InetPath;
24     import vibe.stream.tls : createTLSContext, TLSContextKind;
25     import vibe.http.server : HTTPServerSettings, TLSContext, HTTPServerOptionImpl, HTTPServerOption;
26 
27     import proped : PropertiesNotFoundException;
28 
29     import dango.controller.core;
30 }
31 
32 
33 /**
34  * Аннотация для обозначение объекта контроллера
35  * Params:
36  *
37  * prefix = Префикс для всех путей
38  */
39 struct HTTPController
40 {
41     string prefix;
42 }
43 
44 
45 /**
46  * Аннотация для обозначения метода для обработки входящих запросов
47  * Params:
48  *
49  * path   = Путь
50  * method = Метод
51  */
52 struct HTTPHandler
53 {
54     string path;
55     HTTPMethod method = HTTPMethod.GET;
56 }
57 
58 
59 /**
60  * Аннотация для составления документации к методу
61  * Params:
62  *
63  * helpText = Справочная информация о методе
64  * params   = Информация о принимаемых параметрах в URL
65  * query    = Информация о передаваемых параметрах запроса GET
66  */
67 struct HTTPHandlerInfo
68 {
69     string helpText;
70     string[string] params;
71     string[string] query;
72 }
73 
74 
75 /**
76  * Аннотация для обозначения метода или контроллера
77  * доступ к которым осуществляется только авторизованными пользователями
78  */
79 enum Auth;
80 
81 
82 template isHTTPController(C)
83 {
84     enum isHTTPController = is(C == class) && is(C : Controller);
85 }
86 
87 
88 alias RegisterHandler(T) = void delegate(HTTPMethod, string, T);
89 
90 
91 string getHandlerPath(C)(string path)
92 {
93     auto udas = getUDAs!(C, HTTPController);
94     static if (udas.length > 0)
95     {
96         string prefix = udas[0].prefix;
97         if (prefix.length == 0)
98             prefix = "/";
99 
100         InetPath parent = InetPath(prefix);
101         InetPath child = InetPath(path);
102 
103         if (child.absolute && parent.absolute)
104         {
105             auto childSegments = child.bySegment();
106             childSegments.popFront();
107             child = InetPath(childSegments);
108         }
109 
110         if (!child.empty)
111             parent ~= child;
112 
113         return parent.toString();
114     }
115     else
116         return path;
117 }
118 
119 
120 void registerControllerHandlers(C, Handler)(URLRouter router, C controller, RegisterHandler!Handler handler)
121     if (isHTTPController!C)
122 {
123     foreach (string fName; __traits(allMembers, C))
124     {
125         enum access = __traits(getProtection, __traits(getMember, C, fName));
126         static if (access == "public")
127         {
128             alias member = Alias!(__traits(getMember, C, fName));
129             foreach (attr; __traits(getAttributes, member))
130             {
131                 static if (is(typeof(attr) == HTTPHandler))
132                 {
133                     alias Type = typeof(&__traits(getMember, controller, fName));
134                     static assert(is(Type == Handler), "Handler '" ~ fName ~ "' does not match the type");
135                     handler(attr.method, getHandlerPath!C(attr.path),
136                             &__traits(getMember, controller, fName));
137                 }
138             }
139         }
140     }
141 }
142 
143 
144 /**
145  * Функция стоит объект настроект http сервера по параметрам конфигурации
146  * Params:
147  *
148  * config = Конфигурация
149  */
150 HTTPServerSettings loadServiceSettings(Properties config)
151 {
152     HTTPServerSettings settings = new HTTPServerSettings();
153 
154     string host = config.getOrElse("host", "0.0.0.0");
155     settings.bindAddresses = [host];
156 
157     auto port = config.get!long("port");
158     if (port.isNull)
159         throw new PropertiesNotFoundException(config, "port");
160     settings.port = cast(ushort)port.get;
161 
162     HTTPServerOption options = HTTPServerOption.defaults;
163     if ("options" in config)
164         options.setOptionsByName!(HTTPServerOptionImpl,
165                 "distribute",
166                 "errorStackTraces"
167                 )(config.sub("options"));
168 
169     settings.options = options;
170 
171     if ("hostName" in config)
172         settings.hostName = config.get!string("hostName");
173 
174     if ("maxRequestTime" in config)
175         settings.maxRequestTime = dur!"seconds"(config.get!long("maxRequestTime"));
176 
177     if ("keepAliveTimeout" in config)
178         settings.keepAliveTimeout = dur!"seconds"(config.get!long("keepAliveTimeout"));
179 
180     if ("maxRequestSize" in config)
181         settings.maxRequestSize = config.get!long("maxRequestSize");
182 
183     if ("maxRequestHeaderSize" in config)
184         settings.maxRequestHeaderSize = config.get!long("maxRequestHeaderSize");
185 
186     if ("accessLogFormat" in config)
187         settings.accessLogFormat = config.get!string("accessLogFormat");
188 
189     if ("accessLogFile" in config)
190         settings.accessLogFile = config.get!string("accessLogFile");
191 
192     settings.accessLogToConsole = config.getOrElse("accessLogToConsole", false);
193 
194     if ("ssl" in config)
195     {
196         Properties sslConfig = config.sub("ssl");
197         settings.tlsContext = createTLSContextFrom(sslConfig);
198     }
199 
200     return settings;
201 }
202 
203 
204 /**
205  * Создание TLS контекста из конфигурации сервиса
206  */
207 TLSContext createTLSContextFrom(Properties sslConfig)
208 {
209     TLSContext tlsCtx = createTLSContext(TLSContextKind.server);
210 
211     auto certChainFile = sslConfig.get!string("certificateChainFile");
212     auto privateKeyFile = sslConfig.get!string("privateKeyFile");
213 
214     if (certChainFile.isNull)
215         throw new PropertiesNotFoundException(sslConfig, "certificateChainFile");
216 
217     if (privateKeyFile.isNull)
218         throw new PropertiesNotFoundException(sslConfig, "privateKeyFile");
219 
220     tlsCtx.useCertificateChainFile(certChainFile.get);
221     tlsCtx.usePrivateKeyFile(privateKeyFile.get);
222 
223     return tlsCtx;
224 }
225 
226 
227 /**
228  * Установка свойства в битовую маску
229  */
230 void setOption(T)(ref T options, bool flag, T value)
231 {
232     if (flag)
233         options |= value;
234     else
235         options &= ~value;
236 }
237 
238 
239 /**
240  * Установка свойства в битовую маску по имени свойства
241  */
242 void setOptionByName(T, string name)(ref T options, Properties config)
243 {
244     if (name in config)
245         options.setOption(config.get!bool(name), __traits(getMember, T, name));
246 }
247 
248 
249 /**
250  * Установка массива свойств в битовую маску
251  */
252 void setOptionsByName(T, NAMES...)(ref T options, Properties config)
253 {
254     foreach(string name; NAMES)
255         options.setOptionByName!(T, name)(config);
256 }
257