1 // Written in the D programming language.
2 
3 /**
4 This module contains the base class of the server that can be used to create various kinds of servers.
5 
6 Copyright: LightHouse Software, 2023
7 License:   $(HTTP https://github.com/aquaratixc/ESL-License, Experimental Software License 1.0).
8 Authors:   Oleg Bakharev,
9 		   Ilya Pertsev   
10 */
11 module styx2000.protosrv.baseserver;
12 
13 private {
14     import std.algorithm : remove;
15     import std.socket;
16 }
17 
18 class BaseServer(uint BUFFER_SIZE, uint MAXIMAL_NUMBER_OF_CONNECTIONS)
19 {
20     protected
21     {
22         ubyte[BUFFER_SIZE] _buffer;
23 
24         string _address;
25         ushort _port;
26         bool _immediate;
27         Socket _listener;
28         Socket[] _readable;
29         SocketSet _sockets;
30     }
31 
32 	
33 	
34 	/**
35 	An abstract method that must be implemented in a derived class and that is necessary for the server to process incoming data.
36 	Params:
37 	request = Array of unsigned bytes that represents message.
38 	*/
39     abstract ubyte[] handle(ubyte[] request);
40 
41 	/**
42 	Runs a configured server.
43 	*/
44     final void run()
45     {
46         while (true)
47         {
48             serve;
49 
50             scope (failure)
51             {
52                 _sockets = null;
53                 _listener.close;
54             }
55         }
56     }
57 
58 
59 	/**
60 	An abstract method that must be implemented in a derived class and that is necessary for the server to process incoming data.
61 	Params:
62 	address = IPv4 address as string.
63 	port = Unsigned port number.
64 	backlog = An integer that means the number of established connections that can be processed at any time.
65 	immediate = The logical value showing whether the server will close the connection after processing the request from the client.
66 	*/
67     final void setup4(string address, ushort port, int backlog = 10, bool immediate = false)
68     {
69         _address = address;
70         _port = port;
71         _listener = new Socket(AddressFamily.INET, SocketType.STREAM);
72         _immediate = immediate;
73 
74         with (_listener)
75         {
76             bind(new InternetAddress(_address, _port));
77             listen(backlog);
78         }
79 
80         _sockets = new SocketSet(MAXIMAL_NUMBER_OF_CONNECTIONS + 1);
81     }
82 
83 
84 	/**
85 	An abstract method that must be implemented in a derived class and that is necessary for the server to process incoming data.
86 	Params:
87 	address = IPv4 address as string.
88 	port = Unsigned port number.
89 	backlog = An integer that means the number of established connections that can be processed at any time.
90 	immediate = The logical value showing whether the server will close the connection after processing the request from the client.
91 	*/
92     final void setup6(string address, ushort port, int backlog = 10, bool immediate = false)
93     {
94         _address = address;
95         _port = port;
96         _listener = new Socket(AddressFamily.INET6, SocketType.STREAM);
97         _immediate = immediate;
98 
99         with (_listener)
100         {
101             bind(new Internet6Address(_address, _port));
102             listen(backlog);
103         }
104 
105         _sockets = new SocketSet(MAXIMAL_NUMBER_OF_CONNECTIONS + 1);
106     }
107 
108     private
109     {
110 		/// Serve a connection. For internal use.
111         final void serve()
112         {
113             _sockets.add(_listener);
114 
115             foreach (socket; _readable)
116             {
117                 _sockets.add(socket);
118             }
119 
120             Socket.select(_sockets, null, null);
121 
122             for (uint i = 0; i < _readable.length; i++)
123             {
124                 if (_sockets.isSet(_readable[i]))
125                 {
126                     auto realBufferSize = _readable[i].receive(_buffer);
127 
128                     if (realBufferSize != 0)
129                     {
130                         auto data = _buffer[0 .. realBufferSize];
131 
132                         _readable[i].send(handle(data));
133                     }
134 
135                     if (_immediate)
136                     {
137                         _readable[i].close;
138                         _readable = _readable.remove(i);
139                         i--;
140                     }
141                 }
142             }
143 
144             if (_sockets.isSet(_listener))
145             {
146                 Socket currentSocket = null;
147 
148                 scope (failure)
149                 {
150                     if (currentSocket)
151                     {
152                         currentSocket.close;
153                     }
154                 }
155 
156                 currentSocket = _listener.accept;
157 
158                 if (_readable.length < MAXIMAL_NUMBER_OF_CONNECTIONS)
159                 {
160                     _readable ~= currentSocket;
161                 }
162                 else
163                 {
164                     currentSocket.close;
165                 }
166             }
167             _sockets.reset;
168         }
169     }
170 }