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