1 /*******************************************************************************
2 
3     Unix domain socket connection handler
4 
5     The connection handler expects the client to send UTF-8 text data in the
6     form of '\n'-separated lines. Each line starts with a command. The command
7     is terminated with either a linebreak '\n' or a space ' '. If it is
8     terminated with a space then the rest of the line are the arguments for the
9     command. Whitespace at the beginning of the line, command and arguments is
10     trimmed. Examples:
11 
12         "my-cmd\n" -- command = "my-cmd", arguments = ""
13         " my-cmd \n" -- ditto
14         "my-cmd arg1 arg2\n" -- command = "my-cmd", arguments = "arg1 arg2"
15         " my-cmd   arg1 arg2 \n" -- ditto
16 
17     Empty lines or lines containing only white space are ignored.
18 
19     The constructor takes a command handler that must contain a method matching
20 
21     ```
22         void handle ( cstring command, cstring args,
23                       void delegate ( cstring response ) send_response )
24 
25     ```
26 
27     where `command` is the name of the command (as explained above), args is
28     everything after the command and `send_response` sends the `response` string
29     to the client. Note that the response should end in a '\n' newline
30     character, which is not automatically added.
31 
32     Copyright:
33         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
34         All rights reserved.
35 
36     License:
37         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
38         Alternatively, this file may be distributed under the terms of the Tango
39         3-Clause BSD License (see LICENSE_BSD.txt for details).
40 
41 *******************************************************************************/
42 
43 module ocean.net.server.unix.UnixConnectionHandler;
44 
45 
46 import ocean.net.server.connection.IFiberConnectionHandler;
47 
48 import ocean.meta.types.Qualifiers;
49 import ocean.core.array.Mutation;
50 
51 import ocean.io.select.EpollSelectDispatcher;
52 import ocean.io.select.protocol.fiber.FiberSelectReader;
53 import ocean.io.select.protocol.fiber.FiberSelectWriter;
54 
55 import ocean.sys.socket.UnixSocket;
56 import ocean.io.select.protocol.generic.ErrnoIOException : SocketError;
57 
58 import ocean.util.log.Logger;
59 import ocean.text.util.SplitIterator: ChrSplitIterator;
60 import ocean.core.array.Mutation : copy;
61 import ocean.meta.types.Function;
62 
63 /// Provides basic command handling functionality for unix socket commands.
64 public class BasicCommandHandler
65 {
66     /// Alias for an interactive command handler delegate.
67     public alias void delegate ( cstring,  void delegate (cstring),
68             void delegate (ref mstring)) InteractiveHandler;
69 
70     /// Alias for a non-interactive command handler delegate.
71     public alias void delegate ( cstring, void delegate (cstring) ) Handler;
72 
73     /// Map of command name to interactive handler response delegate.
74     public InteractiveHandler[istring] interactive_handlers;
75 
76     /// Map of a command name to non-interactive handlers delegate
77     public Handler[istring] handlers;
78 
79     /***************************************************************************
80 
81         Constructor
82 
83         Note that handlers and interactive handlers' command names may overlap.
84         In that case, the interactive handler is given the priority.
85 
86         Params:
87             handlers = Array of command string to handler delegate.
88             interactive_handlers = Array of command string to interactive handler
89                 delegate.
90 
91     ***************************************************************************/
92 
93     public this ( scope Handler[istring] handlers,
94             scope InteractiveHandler[istring] interactive_handlers )
95     {
96         this.handlers = handlers;
97         this.interactive_handlers = interactive_handlers;
98     }
99 
100     /***************************************************************************
101 
102         Constructor
103 
104         Params:
105             handlers = Array of command string to handler delegate.
106 
107     ***************************************************************************/
108 
109     public this ( scope Handler[istring] handlers )
110     {
111         this.handlers = handlers;
112     }
113 
114     /***************************************************************************
115 
116         Receive the command from the unix socket and call appropriate handler
117         delegate if registered.
118 
119         Params:
120             command = Command received from unix socket.
121             args = Arguments provided (if any).
122             send_response = Delegate to send a response to the unix socket.
123             wait_reply = delegate to get a reply from the unix socket
124 
125     ***************************************************************************/
126 
127     public void handle ( cstring command, cstring args,
128         scope void delegate ( cstring ) send_response,
129         scope void delegate (ref mstring) wait_reply)
130     {
131 
132         if (auto handler = command in this.interactive_handlers)
133         {
134             (*handler)(args, send_response, wait_reply);
135         }
136         else if (auto handler = command in this.handlers)
137         {
138             (*handler)(args, send_response);
139         }
140         else
141         {
142             send_response("Command not found\n");
143         }
144     }
145 }
146 
147 /// Provides default functionality for handling unix socket commands.
148 public class UnixConnectionHandler : UnixSocketConnectionHandler!(BasicCommandHandler)
149 {
150     /***************************************************************************
151 
152         Constructor.
153 
154         Params:
155             finalize_dg  = internal select listener parameter for super class
156             epoll        = epoll select dispatcher to use for I/O
157             handlers     = Array of command to handler delegate.
158             interactive_handlers = Array of command to interactive handler
159                                    delegate.
160             address_path = the path of the server socket address, for logging
161 
162     ***************************************************************************/
163 
164     public this ( scope FinalizeDg finalize_dg, EpollSelectDispatcher epoll,
165                   scope BasicCommandHandler.Handler[istring] handlers,
166                   scope BasicCommandHandler.InteractiveHandler[istring] interactive_handlers,
167                   istring address_path )
168     {
169         super(finalize_dg, epoll,
170             new BasicCommandHandler(handlers, interactive_handlers),
171             address_path);
172     }
173 
174     /***************************************************************************
175 
176         Constructor.
177 
178         Params:
179             finalize_dg  = internal select listener parameter for super class
180             epoll        = epoll select dispatcher to use for I/O
181             handlers     = Array of command to handler delegate.
182             address_path = the path of the server socket address, for logging
183 
184     ***************************************************************************/
185 
186     public this ( scope FinalizeDg finalize_dg, EpollSelectDispatcher epoll,
187                   scope BasicCommandHandler.Handler[istring] handlers,
188                   cstring address_path )
189     {
190         super(finalize_dg, epoll, new BasicCommandHandler(handlers),
191             address_path);
192     }
193 }
194 
195 /*******************************************************************************
196 
197     Params:
198         CommandHandlerType = The handler type that will process commands
199                              received by the socket. Must contain a
200                              `void handle ( cstring, cstring, void delegate
201                              ( cstring ) )` method.
202 
203 *******************************************************************************/
204 
205 public class UnixSocketConnectionHandler ( CommandHandlerType ) : IFiberConnectionHandler
206 {
207 
208     /***************************************************************************
209 
210         Responder to process the received commands.
211 
212     ***************************************************************************/
213 
214     private CommandHandlerType handler;
215 
216     /***************************************************************************
217 
218         Client connection reader
219 
220     ***************************************************************************/
221 
222     private FiberSelectReader reader;
223 
224     /***************************************************************************
225 
226         Client connection writer
227 
228     ***************************************************************************/
229 
230     private FiberSelectWriter writer;
231 
232     /***************************************************************************
233 
234         Buffer to store the partial handler that followed the last occurrence of
235         '\n' in the most recently read input data.
236 
237     ***************************************************************************/
238 
239     private char[] remaining_request_ln;
240 
241     /***************************************************************************
242 
243         Buffer to store the partial line of a in-command response.
244 
245     ***************************************************************************/
246 
247     private mstring last_read_line;
248 
249     /***************************************************************************
250 
251         The Unix domain server socket address for logging.
252 
253     ***************************************************************************/
254 
255     private cstring address_path;
256 
257     /***************************************************************************
258 
259         Logger.
260 
261     ***************************************************************************/
262 
263     private static Logger log;
264     static this ( )
265     {
266         log = Log.lookup("ocean.net.server.unixsocket");
267     }
268 
269     /***************************************************************************
270 
271         Constructor.
272 
273         Params:
274             finalize_dg  = internal select listener parameter for super class
275             epoll        = epoll select dispatcher to use for I/O
276             handler      = processes incoming commands.
277             address_path = the path of the server socket address, for logging
278 
279     ***************************************************************************/
280 
281     public this ( scope FinalizeDg finalize_dg, EpollSelectDispatcher epoll,
282                   CommandHandlerType handler, cstring address_path )
283     {
284         super(epoll, new UnixSocket, finalize_dg);
285         auto e = new SocketError(this.socket);
286         this.reader = new FiberSelectReader(this.socket, this.fiber, e, e);
287         this.writer = new FiberSelectWriter(this.socket, this.fiber, e, e);
288         this.address_path = address_path;
289         this.handler = handler;
290     }
291 
292     /***************************************************************************
293 
294         Request handler. Reads socket input data in an endless loop. Each chunk
295         of input data is processed by `parseLinesAndHandle()`.
296 
297     ***************************************************************************/
298 
299     protected override void handle ( )
300     {
301         log.info("{} - client connected", this.address_path);
302         scope (exit)
303             log.info("{} - client disconnected", this.address_path);
304 
305         this.remaining_request_ln.length = 0;
306         assumeSafeAppend(this.remaining_request_ln);
307 
308         while (true)
309         {
310             getNextLine();
311             handleCmd(this.last_read_line);
312         }
313     }
314 
315     /***************************************************************************
316 
317         `FiberSelectReader.readConsume()` callback. Splits `data` into lines by
318         '\n' newline characters and calls `handleCmd()` for each line. Prepends
319         `this.remaining_request_ln` to `data` initially, and finally copies the
320         end of `data` that follows the last newline character to
321         `this.remaining_request_ln`.
322 
323         Params:
324             data = socket input data
325 
326         Returns:
327             If no newline is found, A value greater than `data.length` so
328             that `this.reader.readConsume` continues reading from the socket.
329             Otherwise, `data.lenght`, so the reading continues when needed.
330 
331     ***************************************************************************/
332 
333     private size_t parseLine ( void[] data )
334     {
335         scope split = new ChrSplitIterator('\n');
336         split.include_remaining = false;
337         split.reset(cast(char[])data);
338 
339         auto before_newline = split.next();
340 
341         // no newline found, read more
342         if (before_newline.length == data.length)
343         {
344             this.remaining_request_ln ~= before_newline;
345             return data.length + 1;
346         }
347 
348         // We have read up to newline, leave the line for the user
349         // to process, or for this facility to call the next command, and
350         // save the optional rest
351         this.last_read_line.length = 0;
352         assumeSafeAppend(this.last_read_line);
353 
354         this.last_read_line ~= this.remaining_request_ln;
355         this.last_read_line ~= before_newline;
356 
357         this.remaining_request_ln.copy(split.remaining());
358 
359         return data.length;
360     }
361 
362     private void getNextLine()
363     {
364         // do we have more lines in the remaining data?
365         scope split = new ChrSplitIterator('\n');
366 
367         split.include_remaining = false;
368         split.reset(this.remaining_request_ln);
369 
370         auto before_newline = split.next();
371 
372         if (before_newline.length)
373         {
374             this.last_read_line.copy(before_newline);
375             auto remaining = split.remaining();
376             removeShift(this.remaining_request_ln,
377                 0, remaining_request_ln.length - remaining.length);
378             return;
379         }
380 
381 
382         // else fetch a new one
383         this.reader.readConsume(&this.parseLine);
384     }
385 
386     /***************************************************************************
387 
388         Splits `request_ln` into command and arguments. The handler's `handle`
389         method is called with the command, arguments and the send response
390         delegate. If `request_ln` is empty or contains only white space,
391         nothing is done.
392 
393         Params:
394             request_ln = one line of text data read from the socket
395 
396     ***************************************************************************/
397 
398     private void handleCmd ( cstring request_ln )
399     {
400         auto trimmed_request_ln = ChrSplitIterator.trim(request_ln);
401 
402         if (!trimmed_request_ln.length)
403             return;
404 
405         scope split_cmd = new ChrSplitIterator(' ');
406         split_cmd.reset(trimmed_request_ln);
407 
408         cstring cmd = split_cmd.trim(split_cmd.next());
409 
410         static if (ParametersOf!(typeof(this.handler.handle)).length == 4)
411             this.handler.handle(cmd, split_cmd.remaining, &this.sendResponse,
412                     &this.waitReply);
413         else
414             this.handler.handle(cmd, split_cmd.remaining, &this.sendResponse,
415                     &this.waitReply, this.socket);
416     }
417 
418     /***************************************************************************
419 
420         Writes `response` to the client socket. This method is passed to the
421         user's command handler as a delegate.
422 
423         Params:
424             response = a string to write to the client socket.
425 
426     ***************************************************************************/
427 
428     private void sendResponse ( cstring response )
429     {
430         this.writer.send(response);
431     }
432 
433     /***************************************************************************
434 
435         Reads the input from the client socket. This method is passed to the
436         user's command handler as a delegate.
437 
438         Params:
439             prompt = prompt to send to the user
440             buf = buffer to read the response in.
441 
442     ***************************************************************************/
443 
444     private void waitReply ( ref mstring response )
445     {
446         this.getNextLine();
447         response.copy(this.last_read_line);
448     }
449 }