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