Line data Source code
1 : // Snap Communicator -- classes to ease handling communication between processes
2 : // Copyright (c) 2012-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // This program is free software; you can redistribute it and/or modify
5 : // it under the terms of the GNU General Public License as published by
6 : // the Free Software Foundation; either version 2 of the License, or
7 : // (at your option) any later version.
8 : //
9 : // This program is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : // GNU General Public License for more details.
13 : //
14 : // You should have received a copy of the GNU General Public License
15 : // along with this program; if not, write to the Free Software
16 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 : #pragma once
18 :
19 :
20 : // snapwebsites lib
21 : //
22 : #include "snapwebsites/snap_communicator.h"
23 : #include "snapwebsites/log.h" // should be in the snap_communicator.h but can't at the moment
24 :
25 :
26 :
27 : namespace snap
28 : {
29 :
30 :
31 : /** \brief A template to create a list of messages to dispatch on receival.
32 : *
33 : * Whenever you receive messages, they can automatically get dispatched to
34 : * various functions using the dispatcher.
35 : *
36 : * You define a dispatcher_match array and then add a dispatcher to
37 : * your connection object.
38 : *
39 : * \code
40 : * snap::dispatcher<my_connection>::dispatcher_match const my_messages[] =
41 : * {
42 : * {
43 : * "HELP"
44 : * , &my_connection::msg_help
45 : * //, &dispatcher<my_connection>::dispatcher_match::one_to_one_match -- use default
46 : * },
47 : * {
48 : * "STATUS"
49 : * , &my_connection::msg_status
50 : * //, &dispatcher<my_connection>::dispatcher_match::one_to_one_match -- use default
51 : * },
52 : * ... // other messages
53 : *
54 : * // if you'd like you can end your list with a catch all which
55 : * // generate the UNKNOWN message with the following (not required)
56 : * // if you have that entry, your own process_message() function
57 : * // will not get called
58 : * {
59 : * nullptr
60 : * , &dispatcher<my_connection>::dispatcher_match::msg_reply_with_unknown
61 : * , &dispatcher<my_connection>::dispatcher_match::always_match
62 : * },
63 : * };
64 : * \endcode
65 : *
66 : * In most cases you do not need to specify the matching function. It will
67 : * use the default which is a one to one match. So in the example above,
68 : * for "HELP", only a message with the command set to "HELP" will match.
69 : * When a match is found, the correcsponding function (msg_help() here)
70 : * gets called.
71 : *
72 : * Note that "functions" are actually offsets. You will get `this` defined
73 : * as expected when your function gets called. The one drawback is that
74 : * only a function of the connection you attach the dispatcher to can
75 : * be called from the dispatcher. This is because we want to have a
76 : * static table instead of a dynamic one created each time we start
77 : * a process (in a website server, many processes get started again and
78 : * again.)
79 : *
80 : * \note
81 : * The T parameter of this template is often referenced as a "connection"
82 : * because it is expected to be a connection. There is actually no such
83 : * constraint on that object. It just needs to understand the dispatcher
84 : * usage which is to call the dispatch() function whenever a message is
85 : * received. Also it needs to implement any f_execute() function as
86 : * defined in its dispatch_match vector.
87 : *
88 : * \note
89 : * This is documented here because it is a template and we cannot do
90 : * that in the .cpp (at least older versions of doxygen could not.)
91 : */
92 : template<typename T>
93 0 : class dispatcher
94 : : public dispatcher_base
95 : {
96 : public:
97 : /** \brief A smart pointer of the dispatcher.
98 : *
99 : * Although we expect the array of `dispatcher_match` to be
100 : * statically defined, the `dispatcher`, on the other hand,
101 : * is quite dynamic and needs to be allocated in a smart
102 : * pointer then added to your connection.
103 : */
104 : typedef std::shared_ptr<dispatcher> pointer_t;
105 :
106 : /** \brief This structure is used to define the list of supported messages.
107 : *
108 : * Whenever you create an array of messages, you use this structure.
109 : *
110 : * The structure takes a few parameters as follow:
111 : *
112 : * \li f_expr -- the "expression" to be matched to the command name
113 : * for example "HELP".
114 : * \li f_execute -- the function offset to execute on a match.
115 : * \li f_match -- the function to check whether the expression is a match;
116 : * it has a default of one_to_one_match() which means the
117 : * f_expr string is viewed as a plain string defining the
118 : * message name as is.
119 : *
120 : * The command name is called "f_expr" but some matching functions may
121 : * make use of the "f_expr" parameter as an expression such as a
122 : * regular expression. Such functions will be added here with time, you
123 : * may also have your own, of course. The match function is expected to
124 : * be a static or standalone function.
125 : */
126 : struct dispatcher_match
127 : {
128 : /** \brief Define a vector of dispatcher_match objects.
129 : *
130 : * This function includes an array of dispatcher_match objects.
131 : * Whenever you define dispatcher_match objects, you want to
132 : * use the C++11 syntax to create a vector.
133 : *
134 : * \attention
135 : * We are NOT using a match because the matching may make use
136 : * of complex functions that support things such as complex as
137 : * regular expressions. In other words, the name of the message
138 : * may not just be a simple string.
139 : */
140 : typedef std::vector<dispatcher_match> vector_t;
141 :
142 : /** \brief The execution function.
143 : *
144 : * This type defines the execution function. We give it the message
145 : * on a match. If the command name is not a match, it is ignored.
146 : */
147 : typedef void (T::*execute_func_t)(snap_communicator_message & msg);
148 :
149 : /** \brief The match function return types.
150 : *
151 : * Whenever a match function is called, it may return one of:
152 : *
153 : * \li MATCH_FALSE
154 : *
155 : * The function did not match anything. Ignore the corresponding
156 : * function.
157 : *
158 : * \li MATCH_TRUE
159 : *
160 : * This is a match, execute the function. We are done with this list
161 : * of matches.
162 : *
163 : * \li MATCH_CALLBACK
164 : *
165 : * The function is a callback, it gets called and the process
166 : * continues. Since the message parameter is read/write, it is
167 : * a way to tweak the message before other functions get it.
168 : */
169 : enum class match_t
170 : {
171 : MATCH_FALSE,
172 : MATCH_TRUE,
173 : MATCH_CALLBACK
174 : };
175 :
176 : /** \brief The match function.
177 : *
178 : * This type defines the match function. We give it the message
179 : * which has the command name, although specialized matching
180 : * function could test other parameters from the message such
181 : * as the origination of the message.
182 : */
183 : typedef match_t (*match_func_t)(QString const & expr, snap_communicator_message & msg);
184 :
185 : /** \brief The default matching function.
186 : *
187 : * This function checks the command one to one to the expression.
188 : * The word in the expression is compared as is to the command
189 : * name:
190 : *
191 : * \code
192 : * return expr == msg.get_command();
193 : * \endcode
194 : *
195 : * We will add other matching functions with time
196 : * (start_with_match(), regex_match(), etc.)
197 : *
198 : * \note
199 : * It is permissible to use a match function to modify the
200 : * message in some way, however, it is not recommended.
201 : *
202 : * \param[in] expr The expression to compare the command against.
203 : * \param[in] msg The message to match against this expression.
204 : *
205 : * \return MATCH_TRUE if it is a match, MATCH_FALSE otherwise.
206 : */
207 0 : static match_t one_to_one_match(QString const & expr, snap_communicator_message & msg)
208 : {
209 0 : return expr == msg.get_command()
210 0 : ? match_t::MATCH_TRUE
211 0 : : match_t::MATCH_FALSE;
212 : }
213 :
214 : /** \brief Always returns true.
215 : *
216 : * This function always returns true. This is practical to close
217 : * your list of messages and return a specific message. In most
218 : * cases this is used to reply with the UNKNOWN message.
219 : *
220 : * \param[in] expr The expression to compare the command against.
221 : * \param[in] msg The message to match against this expression.
222 : *
223 : * \return Always returns MATCH_TRUE.
224 : */
225 0 : static match_t always_match(QString const & expr, snap_communicator_message & msg)
226 : {
227 0 : NOTUSED(expr);
228 0 : NOTUSED(msg);
229 0 : return match_t::MATCH_TRUE;
230 : }
231 :
232 : /** \brief Always returns true.
233 : *
234 : * This function always returns true. This is practical to close
235 : * your list of messages and return a specific message. In most
236 : * cases this is used to reply with the UNKNOWN message.
237 : *
238 : * \param[in] expr The expression to compare the command against.
239 : * \param[in] msg The message to match against this expression.
240 : *
241 : * \return Always returns MATCH_CALLBACK.
242 : */
243 0 : static match_t callback_match(QString const & expr, snap_communicator_message & msg)
244 : {
245 0 : NOTUSED(expr);
246 0 : NOTUSED(msg);
247 0 : return match_t::MATCH_CALLBACK;
248 : }
249 :
250 : /** \brief The expression to compare against.
251 : *
252 : * The expression is most often going to be the exact command name
253 : * which will be matched with the one_to_one_match() function.
254 : *
255 : * For other match functions, this would be whatever type of
256 : * expression supported by those other functions.
257 : *
258 : * \note
259 : * Effective C++ doesn't like bare pointers, but there is no real
260 : * reason for us to waste time and memory by having an std::string
261 : * here. It's going to always be a constant pointer anyway.
262 : */
263 : char const * f_expr = nullptr;
264 :
265 : /** \brief The execute function.
266 : *
267 : * This is an offset in your connection class. We do not allow
268 : * std::bind() because we do not want the array of messages to be
269 : * dynamic (that way it is created at compile time and loaded as
270 : * ready/prepared data on load.)
271 : *
272 : * The functions called have `this` defined so you can access
273 : * your connection data and other functions. It requires the
274 : * `&` and the class name to define the pointer, like this:
275 : *
276 : * \code
277 : * &MyClass::my_message_function
278 : * \endcode
279 : *
280 : * The execution is started by calling the run() function.
281 : */
282 : execute_func_t f_execute = nullptr;
283 :
284 : /** \brief The match function.
285 : *
286 : * The match function is used to know whether that command
287 : * dispatch function was found.
288 : *
289 : * By default this parameter is set to one_to_one_match().
290 : * This means the command has to be one to one equal to
291 : * the f_expr string.
292 : *
293 : * The matching is done in the match() function.
294 : */
295 : match_func_t f_match = &snap::dispatcher<T>::dispatcher_match::one_to_one_match;
296 :
297 : /** \brief Run the execution function if this is a match.
298 : *
299 : * First this function checks whether the command of the message
300 : * in \p msg matches this `dispatcher_match` expression. In
301 : * most cases the match function is going to be
302 : * one_on_one_match() which means it has to be exactly equal.
303 : *
304 : * If it is a match, this function runs your \p connection execution
305 : * function (i.e. the message gets dispatched) and then it returns
306 : * true.
307 : *
308 : * If the message is not a match, then the function returns false
309 : * and only the matching function was called. In this case the
310 : * \p connection does not get used.
311 : *
312 : * When this function returns true, you should not call the
313 : * process_message() function since that was already taken care
314 : * of. The process_message() function should only be called
315 : * if the message was not yet dispatched. When the list of
316 : * matches includes a catch all at the end, the process_message()
317 : * will never be called.
318 : *
319 : * \note
320 : * Note that we do not offer two functions, one to run the match
321 : * function and one to execute the match because you could otherwise
322 : * end up calling the execute function (dispatch) on any
323 : * `dispatcher_match` entry and we did not want that to happen.
324 : *
325 : * \param[in] connection The connection attached to that
326 : * `dispatcher_match`.
327 : * \param[in] msg The message that matched.
328 : *
329 : * \return true if the connection execute function was called.
330 : */
331 0 : bool execute(T * connection, snap::snap_communicator_message & msg) const
332 : {
333 0 : match_t m(f_match(f_expr, msg));
334 0 : if(m == match_t::MATCH_TRUE
335 0 : || m == match_t::MATCH_CALLBACK)
336 : {
337 0 : (connection->*f_execute)(msg);
338 0 : if(m == match_t::MATCH_TRUE)
339 : {
340 0 : return true;
341 : }
342 : }
343 :
344 0 : return false;
345 : }
346 :
347 : /** \brief Check whether f_match is one_to_one_match().
348 : *
349 : * This function checks whether the f_match function was defined
350 : * to one_to_one_match()--the default--and if so returns true.
351 : *
352 : * \return true if f_match is the one_to_one_match() function.
353 : */
354 0 : bool match_is_one_to_one_match() const
355 : {
356 0 : return f_match == &snap::dispatcher<T>::dispatcher_match::one_to_one_match;
357 : }
358 :
359 : /** \brief Check whether f_match is always_match().
360 : *
361 : * This function checks whether the f_match function was defined
362 : * to always_match() and if so returns true.
363 : *
364 : * \return true if f_match is the always_match() function.
365 : */
366 0 : bool match_is_always_match() const
367 : {
368 0 : return f_match == &snap::dispatcher<T>::dispatcher_match::always_match;
369 : }
370 :
371 : /** \brief Check whether f_match is always_match().
372 : *
373 : * This function checks whether the f_match function was defined
374 : * to always_match() and if so returns true.
375 : *
376 : * \return true if f_match is the always_match() function.
377 : */
378 0 : bool match_is_callback_match() const
379 : {
380 0 : return f_match == &snap::dispatcher<T>::dispatcher_match::callback_match;
381 : }
382 : };
383 :
384 : private:
385 : /** \brief The connection pointer.
386 : *
387 : * This parameter is set by the constructor. It represents the
388 : * connection this dispatcher was added to (a form of parent of
389 : * this dispatcher object.)
390 : */
391 : T * f_connection = nullptr;
392 :
393 : /** \brief The array of possible matches.
394 : *
395 : * This is the vector of your messages with the corresponding
396 : * match and execute functions. This is used to go through
397 : * the matches and execute (dispatch) as required.
398 : */
399 : typename snap::dispatcher<T>::dispatcher_match::vector_t f_matches = {};
400 :
401 : /** \brief Tell whether messages should be traced or not.
402 : *
403 : * Because your service may accept and send many messages a full
404 : * trace on all of them can really be resource intensive. By default
405 : * the system will not trace anything. By setting this parameter to
406 : * true (call set_trace() for that) you request the SNAP_LOG_TRACE()
407 : * to run on each message received by this dispatcher. This is done
408 : * on entry so whether the message is processed by the dispatcher
409 : * or your own send_message() function, it will trace that message.
410 : */
411 : bool f_trace = false;
412 :
413 : public:
414 :
415 : /** \brief Initialize the dispatcher with your connection and messages.
416 : *
417 : * This function takes a pointer to your connection and an array
418 : * of matches.
419 : *
420 : * Whenever a message is received by one of your connections, the
421 : * dispatch() function gets called which checks the message against
422 : * each entry in this array of \p matches.
423 : *
424 : * \param[in] connection The connection for which this dispatcher is
425 : * created.
426 : * \param[in] matches The array of dispatch keywords and functions.
427 : * \param[in] matches_size The sizeof() of the matches array.
428 : */
429 0 : dispatcher<T>(T * connection, typename snap::dispatcher<T>::dispatcher_match::vector_t matches)
430 : : f_connection(connection)
431 0 : , f_matches(matches)
432 : {
433 0 : }
434 :
435 : // prevent copies
436 : dispatcher<T>(dispatcher<T> const & rhs) = delete;
437 : dispatcher<T> & operator = (dispatcher<T> const & rhs) = delete;
438 :
439 : /** \brief Add a default array of possible matches.
440 : *
441 : * In Snap! a certain number of messages are always exactly the same
442 : * and these can be implemented internally so each daemon doesn't have
443 : * to duplicate that work over and over again. These are there in part
444 : * because the snapcommunicator expects those messages there.
445 : *
446 : * IMPORTANT NOTE: If you add your own version in your dispatcher_match
447 : * vector, then these will be ignored since your version will match first
448 : * and the dispatcher uses the first function only.
449 : *
450 : * This array currently includes:
451 : *
452 : * \li HELP -- msg_help() -- returns the list of all the messages
453 : * \li LOG -- msg_log() -- reconfigure() the logger
454 : * \li QUITTING -- msg_quitting() -- calls stop(true);
455 : * \li READY -- msg_ready() -- calls ready() -- snapcommunicator always
456 : * sends that message so it has to be supported
457 : * \li STOP -- msg_stop() -- calls stop(false);
458 : * \li UNKNOWN -- msg_log_unknown() -- in case we receive a message we
459 : * don't understand
460 : * \li * -- msg_reply_with_unknown() -- the last entry will be a grab
461 : * all pattern which returns the UNKNOWN message automatically
462 : * for you
463 : *
464 : * The msg_...() functions must be declared in your class T. If you
465 : * use the system connection_with_send_message class then they're
466 : * already defined there.
467 : *
468 : * The HELP response is automatically built from the f_matches.f_expr
469 : * strings. However, if the function used to match the expression is
470 : * not one_to_one_match(), then that string doesn't get used.
471 : *
472 : * If any message can't be determine (i.e. the function is not the
473 : * one_to_one_match()) then the user help() function gets called and
474 : * we expect that function to add any dynamic message the daemon
475 : * understands.
476 : *
477 : * The LOG message reconfigures the logger if the is_configure() function
478 : * says it is configured. In any other circumstances, nothing happens.
479 : *
480 : * Note that the UNKNOWN message is understood and just logs the message
481 : * received. This allows us to see that WE sent a message that the receiver
482 : * (not us) does not understand and adjust our code accordingly (i.e. add
483 : * support for that message in that receiver or maybe fixed the spelling.)
484 : */
485 0 : void add_snap_communicator_commands()
486 : {
487 : // avoid more than one realloc()
488 : //
489 0 : f_matches.reserve(f_matches.size() + 7);
490 :
491 : {
492 0 : typename snap::dispatcher<T>::dispatcher_match m;
493 0 : m.f_expr = "HELP";
494 0 : m.f_execute = &T::msg_help;
495 : //m.f_match = <default>;
496 0 : f_matches.push_back(m);
497 : }
498 : {
499 0 : typename snap::dispatcher<T>::dispatcher_match m;
500 0 : m.f_expr = "ALIVE";
501 0 : m.f_execute = &T::msg_alive;
502 : //m.f_match = <default>;
503 0 : f_matches.push_back(m);
504 : }
505 : {
506 0 : typename snap::dispatcher<T>::dispatcher_match m;
507 0 : m.f_expr = "LOG";
508 0 : m.f_execute = &T::msg_log;
509 : //m.f_match = <default>;
510 0 : f_matches.push_back(m);
511 : }
512 : {
513 0 : typename snap::dispatcher<T>::dispatcher_match m;
514 0 : m.f_expr = "QUITTING";
515 0 : m.f_execute = &T::msg_quitting;
516 : //m.f_match = <default>;
517 0 : f_matches.push_back(m);
518 : }
519 : {
520 0 : typename snap::dispatcher<T>::dispatcher_match m;
521 0 : m.f_expr = "READY";
522 0 : m.f_execute = &T::msg_ready;
523 : //m.f_match = <default>;
524 0 : f_matches.push_back(m);
525 : }
526 : {
527 0 : typename snap::dispatcher<T>::dispatcher_match m;
528 0 : m.f_expr = "STOP";
529 0 : m.f_execute = &T::msg_stop;
530 : //m.f_match = <default>;
531 0 : f_matches.push_back(m);
532 : }
533 : {
534 0 : typename snap::dispatcher<T>::dispatcher_match m;
535 0 : m.f_expr = "UNKNOWN";
536 0 : m.f_execute = &T::msg_log_unknown;
537 : //m.f_match = <default>;
538 0 : f_matches.push_back(m);
539 : }
540 : {
541 0 : typename snap::dispatcher<T>::dispatcher_match m;
542 0 : m.f_expr = nullptr; // any other message
543 0 : m.f_execute = &T::msg_reply_with_unknown;
544 0 : m.f_match = &snap::dispatcher<T>::dispatcher_match::always_match;
545 0 : f_matches.push_back(m);
546 : }
547 0 : }
548 :
549 : typename snap::dispatcher<T>::dispatcher_match::vector_t const & get_matches() const
550 : {
551 : return f_matches;
552 : }
553 :
554 : /** \brief The dispatch function.
555 : *
556 : * This is the function your message system will call whenever
557 : * the system receives a message.
558 : *
559 : * The function returns true if the message was dispatched.
560 : * When that happen, the process_message() function of the
561 : * connection should not be called.
562 : *
563 : * You may not include a message in the array of `dispatch_match`
564 : * if it is too complicated to match or too many variables are
565 : * necessary then you will probably want to use your
566 : * process_message().
567 : *
568 : * By adding a catch-all at the end of your list of matches, you
569 : * can easily have one function called for any message. By default
570 : * the dispatcher environment offers such a match function and
571 : * it also includes a function that sends the UNKNOWN message as
572 : * an immediate reply to a received message.
573 : *
574 : * \param[in] msg The message to be dispatched.
575 : *
576 : * \return true if the message was dispatched, false otherwise.
577 : */
578 0 : virtual bool dispatch(snap::snap_communicator_message & msg) override
579 : {
580 0 : if(f_trace)
581 : {
582 0 : SNAP_LOG_TRACE("dispatch message \"")
583 0 : (msg.to_message())
584 : ("\".");
585 : }
586 :
587 : // go in order to execute matches
588 : //
589 : // remember that a dispatcher with just a set of well defined command
590 : // names is a special case (albeit frequent) and we can't process
591 : // using a map (a.k.a. fast binary search) as a consequence
592 : //
593 0 : for(auto const & m : f_matches)
594 : {
595 0 : if(m.execute(f_connection, msg))
596 : {
597 0 : return true;
598 : }
599 : }
600 :
601 0 : return false;
602 : }
603 :
604 : /** \brief Set whether the dispatcher should trace your messages or not.
605 : *
606 : * By default, the f_trace flag is set to false. You can change it to
607 : * true while debugging. You should remember to turn it back off once
608 : * you make an official version of your service to avoid the possibly
609 : * huge overhead of sending all those log messages. One way to do so
610 : * is to place the code within #ifdef/#endif as in:
611 : *
612 : * \code
613 : * #ifdef _DEBUG
614 : * my_dispatcher->set_trace();
615 : * #endif
616 : * \endcode
617 : *
618 : * \param[in] trace Set to true to get SNAP_LOG_TRACE() of each message.
619 : */
620 : void set_trace(bool trace = true)
621 : {
622 : f_trace = trace;
623 : }
624 :
625 : /** \brief Retrieve the list of commands.
626 : *
627 : * This function transforms the vector of f_matches in a list of
628 : * commands in a QStringList.
629 : *
630 : * \param[in,out] commands The place where the list of commands is saved.
631 : *
632 : * \return true if the commands were all determined, false if some need
633 : * help from the user of this dispatcher.
634 : */
635 0 : virtual bool get_commands(snap_string_list & commands) override
636 : {
637 0 : bool need_user_help(false);
638 0 : for(auto const & m : f_matches)
639 : {
640 0 : if(m.f_expr == nullptr)
641 : {
642 0 : if(!m.match_is_always_match()
643 0 : && !m.match_is_callback_match())
644 : {
645 : // this is a "special case" where the user has
646 : // a magical function which does not require an
647 : // expression at all (i.e. "hard coded" in a
648 : // function)
649 : //
650 0 : need_user_help = true;
651 : }
652 : //else -- always match is the last entry and that just
653 : // means we can return UNKNOWN on an unknown message
654 : }
655 0 : else if(m.match_is_one_to_one_match())
656 : {
657 : // add the f_expr as is since it represents a command
658 : // as is
659 : //
660 : // note: the fromUtf8() is not extremely important
661 : // since commands have to be uppercase letters
662 : // and digits and the underscore it would work
663 : // with fromLatin1() too
664 : //
665 0 : commands << QString::fromUtf8(m.f_expr);
666 : }
667 : else
668 : {
669 : // this is not a one to one match, so possibly a
670 : // full regex or similar
671 : //
672 0 : need_user_help = true;
673 : }
674 : }
675 0 : return need_user_help;
676 : }
677 : };
678 :
679 :
680 : } // namespace snap
681 : // vim: ts=4 sw=4 et
|