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 }