1 /* 2 Copyright (c) 2023-2024 Andrea Fontana 3 4 Permission is hereby granted, free of charge, to any person 5 obtaining a copy of this software and associated documentation 6 files (the "Software"), to deal in the Software without 7 restriction, including without limitation the rights to use, 8 copy, modify, merge, publish, distribute, sublicense, and/or sell 9 copies of the Software, and to permit persons to whom the 10 Software is furnished to do so, subject to the following 11 conditions: 12 13 The above copyright notice and this permission notice shall be 14 included in all copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 OTHER DEALINGS IN THE SOFTWARE. 24 */ 25 26 /// Configuration module for serverino. 27 module serverino.config; 28 29 import std.experimental.logger : LogLevel; 30 import std.socket : Socket, InternetAddress, Internet6Address, Address; 31 import std.stdio : File; 32 import std.datetime : Duration, dur; 33 import std.traits : ReturnType; 34 35 public struct priority { long priority; } /// UDA. Set @endpoint priority 36 37 public enum endpoint; /// UDA. Functions with @endpoint attached are called when a request is received 38 public enum onDaemonStart; /// UDA. Called when daemon start. Running in main thread, not in worker. 39 public enum onDaemonStop; /// UDA. Called when daemon exit. Running in main thread, not in worker. 40 public enum onWorkerStart; /// UDA. Functions with @onWorkerStart attached are called when worker is started 41 public enum onWorkerStop; /// UDA. Functions with @onWorkerStop attached are called when worker is stopped 42 public enum onServerInit; /// UDA. Used to setup serverino. Must return a ServerinoConfig struct. See `ServerinoConfig` struct. 43 44 import serverino.interfaces : Request; 45 46 /++ UDA. You can use to filter requests using a function `bool(Request request) { }` 47 + Example: 48 + --- 49 + @endpoint 50 + @route!(x => x.uri.startsWith("/api")) 51 + void api(Request r, Output o) { ... } 52 + --- 53 +/ 54 public struct route(alias T) 55 { 56 static bool apply(const Request r) { return T(r); } 57 } 58 59 private template compareUri(string _uri) 60 { 61 import std.uri : encode; 62 enum compareUri = (const Request r){ 63 static assert(_uri[0] == '/', "Every route must begin with a '/'"); 64 return r.uri == _uri.encode(); 65 }; 66 } 67 68 /++ UDA. You can use to filter requests using a uri. 69 + Example: 70 + --- 71 + @endpoint 72 + @route!"/hello.html" 73 + void api(Request r, Output o) { ... } 74 + --- 75 +/ 76 public alias route(string uri) = route!(r => compareUri!uri(r)); 77 78 /++ 79 Struct used to setup serverino. 80 You must return this struct from a function with @onServerInit UDA attached. 81 --- 82 @onServerInit 83 auto configure() 84 { 85 // You can chain methods 86 ServerinoConfig config = 87 ServerinoConfig.create() 88 .setWorkers(5) 89 .enableKeepAlive(); 90 91 return config; 92 } 93 --- 94 ++/ 95 struct ServerinoConfig 96 { 97 98 public: 99 100 @disable this(); 101 102 /// Create a new instance of ServerinoConfig 103 static ServerinoConfig create() 104 { 105 ServerinoConfig sc = ServerinoConfig.init; 106 107 sc.setLogLevel(); 108 sc.setReturnCode(); 109 sc.setMaxWorkers(); 110 sc.setMinWorkers(); 111 sc.setMaxWorkerLifetime(); 112 sc.setMaxWorkerIdling(); 113 sc.setListenerBacklog(); 114 115 sc.setMaxRequestTime(); 116 sc.setMaxRequestSize(); 117 118 version(Windows) { } 119 else 120 { 121 sc.setWorkerUser(); 122 sc.setWorkerGroup(); 123 } 124 125 sc.setHttpTimeout(); 126 127 sc.enableKeepAlive(); 128 129 sc.disableRemoteIp(); 130 131 return sc; 132 } 133 134 /// Min log level to display. Default == LogLevel.all 135 @safe ref ServerinoConfig setLogLevel(LogLevel level = LogLevel.all) return { daemonConfig.logLevel = level; return this; } 136 137 /// Every value != 0 is used to terminate server immediatly. 138 @safe ref ServerinoConfig setReturnCode(int retCode = 0) return { returnCode = retCode; return this; } 139 /// Max number of workers 140 @safe ref ServerinoConfig setMaxWorkers(size_t val = 5) return { daemonConfig.maxWorkers = val; return this; } 141 /// Min number of workers 142 @safe ref ServerinoConfig setMinWorkers(size_t val = 5) return { daemonConfig.minWorkers = val; return this; } 143 144 /// Same as setMaxWorkers(v); setMinWorkers(v); 145 @safe ref ServerinoConfig setWorkers(size_t val) return { setMinWorkers(val); setMaxWorkers(val); return this; } 146 147 /// Max time a worker can live. After this time, worker is terminated. 148 @safe ref ServerinoConfig setMaxWorkerLifetime(Duration dur = 6.dur!"hours") return { workerConfig.maxWorkerLifetime = dur; return this; } 149 150 /// Max time a worker can be idle. After this time, worker is terminated. 151 @safe ref ServerinoConfig setMaxWorkerIdling(Duration dur = 1.dur!"hours") return { workerConfig.maxWorkerIdling = dur; return this; } 152 153 /// Max number of pending connections 154 @safe ref ServerinoConfig setListenerBacklog(int val = 2048) return { daemonConfig.listenerBacklog = val; return this; } 155 156 /// Max time a request can take. After this time, worker is terminated. 157 @safe ref ServerinoConfig setMaxRequestTime(Duration dur = 5.dur!"seconds") return { workerConfig.maxRequestTime = dur; return this; } 158 159 /// Max size of a request. If a request is bigger than this value, error 413 is returned. 160 @safe ref ServerinoConfig setMaxRequestSize(size_t bytes = 1024*1024*10) return { daemonConfig.maxRequestSize = bytes; return this;} 161 162 version(Windows) { } 163 else 164 { 165 /// For example: "www-data" 166 @safe ref ServerinoConfig setWorkerUser(string s = string.init) return { workerConfig.user = s; return this; } 167 /// For example: "www-data" 168 @safe ref ServerinoConfig setWorkerGroup(string s = string.init) return { workerConfig.group = s; return this;} 169 } 170 171 /// How long the socket will wait for a request after the connection? 172 @safe ref ServerinoConfig setHttpTimeout(Duration dur = 10.dur!"seconds") return { daemonConfig.maxHttpWaiting = dur; workerConfig.maxHttpWaiting = dur; return this;} 173 174 /// Enable/Disable keep-alive for http/1.1 175 @safe ref ServerinoConfig enableKeepAlive(bool enable = true, Duration timeout = 3.dur!"seconds") return { workerConfig.keepAlive = enable; daemonConfig.keepAliveTimeout = timeout; return this; } 176 177 /// Ditto 178 @safe ref ServerinoConfig enableKeepAlive(Duration timeout) return { enableKeepAlive(true, timeout); return this; } 179 180 /// Ditto 181 @safe ref ServerinoConfig disableKeepAlive() return { enableKeepAlive(false); return this; } 182 183 /// Add a x-remote-ip header 184 @safe ref ServerinoConfig enableRemoteIp(bool enable = true) return { daemonConfig.withRemoteIp = enable; return this; } 185 186 /// 187 @safe ref ServerinoConfig disableRemoteIp() return { return enableRemoteIp(false); } 188 189 /// Add a new listener. 190 @safe ref ServerinoConfig addListener(ListenerProtocol p = ListenerProtocol.IPV4)(string address, ushort port) return 191 { 192 enum LISTEN_IPV4 = (p == ListenerProtocol.IPV4 || p == ListenerProtocol.BOTH); 193 enum LISTEN_IPV6 = (p == ListenerProtocol.IPV6 || p == ListenerProtocol.BOTH); 194 195 static if(LISTEN_IPV4) daemonConfig.listeners ~= Listener(daemonConfig.listeners.length, new InternetAddress(address, port)); 196 static if(LISTEN_IPV6) daemonConfig.listeners ~= Listener(daemonConfig.listeners.length, new Internet6Address(address, port)); 197 198 return this; 199 } 200 201 /// Protocol used by listener 202 enum ListenerProtocol 203 { 204 IPV4, /// Listen on IPV4 205 IPV6, /// Listen on IPV6 206 BOTH /// Listen on both IPV4 and IPV6 207 } 208 209 package: 210 211 void validate() 212 { 213 if (daemonConfig.minWorkers == 0 || daemonConfig.minWorkers > 1024) 214 throw new Exception("Configuration error. Must be 1 <= minWorkers <= 1024"); 215 216 if (daemonConfig.minWorkers > daemonConfig.maxWorkers) 217 throw new Exception("Configuration error. Must be minWorkers <= maxWorkers"); 218 219 if (daemonConfig.maxWorkers == 0 || daemonConfig.maxWorkers > 1024) 220 throw new Exception("Configuration error. Must be 1 <= maxWorkers <= 1024"); 221 222 if (daemonConfig.listeners.length == 0) 223 addListener("0.0.0.0", 8080); 224 } 225 226 DaemonConfig daemonConfig; 227 WorkerConfig workerConfig; 228 229 int returnCode; 230 } 231 232 // To avoid errors/mistakes copying data around 233 import std.typecons : Typedef; 234 package alias DaemonConfigPtr = Typedef!(DaemonConfig*); 235 package alias WorkerConfigPtr = Typedef!(WorkerConfig*); 236 237 package struct Listener 238 { 239 @safe: 240 241 @disable this(); 242 243 this(size_t index, Address address) 244 { 245 this.address = address; 246 this.index = index; 247 } 248 249 Address address; 250 size_t index; 251 252 Socket socket; 253 } 254 255 package struct WorkerConfig 256 { 257 258 Duration maxRequestTime; 259 Duration maxHttpWaiting; 260 Duration maxWorkerLifetime; 261 Duration maxWorkerIdling; 262 263 bool keepAlive; 264 string user; 265 string group; 266 267 } 268 269 package struct DaemonConfig 270 { 271 LogLevel logLevel; 272 size_t maxRequestSize; 273 Duration maxHttpWaiting; 274 Duration keepAliveTimeout; 275 size_t minWorkers; 276 size_t maxWorkers; 277 int listenerBacklog; 278 bool withRemoteIp; 279 280 Listener[] listeners; 281 }