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 }