#ifndef __CommandDispatcher_h_ #define __CommandDispatcher_h_ #include #include "InputFramework.h" #include "DeadManTimer.h" #include "Messages.h" #include "ThreadClass.h" // This represents a command read from the client. It is, at it's heart, // a property list of names and values, all strings. class ExternalRequest : public Request { private: PropertyList _properties; static const std::string messageIdString; static const std::string commandString; static const std::string emptyString; public: // This is used for tracking requests. The client might send several // requests at once, and the server might answer them out of order. The // client is responsible for making up message ids. The server will preserve // that id, and return it to the client with each response. // // A message id used to be an int. Now it's an int64_t. Message ids are // used in so many places in the server, it was hard to find them all. I // made the MessageId class so the compiler would help me find all of the // places where I was using an int to store a message id. // // Note that C++ will automatically convert between int64_t and int without // a warning. It will not convert this class to or from an int. There is // no conversion or constructor to do that. // // In some places the code was implicitly converting a message id to a bool. // I had to make that illegal. It would be tempting to add a conversion from // MessageId to bool, but C++ would use that to create a conversion from // this class to an int. So we have to write a little more source code to // use this class vs an int or int64_t. // // This should make no appreciable difference once the code is compiled. // Under the hood we're just storing an int64_t. All the methods are inline // so they won't generate any more object code than directly using one of the // integer types directly. class MessageId { private: int64_t _value; static int64_t fromString(std::string const &value) { return strtolDefault(value, 0); } public: explicit MessageId(ExternalRequest const *message) : _value(fromString(message->getProperty(messageIdString))) { } explicit MessageId(int64_t value) : _value(value) { } explicit MessageId(std::string const &value) : _value(fromString(value)) { } MessageId() : _value(0) { } int64_t getValue() const { return _value; } bool isEmpty() const { return _value == 0; } bool present() const { return _value != 0; } void clear() { _value = 0; } static MessageId none() { return MessageId((int64_t)0); } }; ExternalRequest(SocketInfo *socketInfo) : Request(socketInfo) { } MessageId getResponseMessageId() const { return MessageId(this); } const PropertyList &getProperties() const { return _properties; } PropertyList &getProperties() { return _properties; } std::string const *getPropertyPtr(std::string propertyName) const { return ::getProperty(_properties, propertyName); } std::string getProperty(std::string propertyName, std::string defaultValue = emptyString) const { return ::getProperty(_properties, propertyName, defaultValue); } bool getProperty(std::string propertyName, bool defaultValue) const { const std::string asString = getProperty(propertyName); if (asString == "1") return true; if (asString == "0") return false; return defaultValue; } std::string getProperty(std::string propertyName, char const *defaultValue) const { // C++ can automatically convert a char * to a string. But given the // choice, C++ would rather convert a char * to a bool than to a sting. // Adding this overloaded function will explicitly tell the compiler to // convert a char * to a string. return getProperty(propertyName, (std::string)defaultValue); } std::string getCommand() const { return getProperty(commandString); } void setCommand(std::string const &name) { _properties[commandString]=name; } virtual TclList debugDump() const; }; // This class takes care of the work of dispatching specific messages to // specific handlers which have registered themselves. The command dispatcher // is the ideal example of this. // // At one time a thread receiving a message from here would redispatch the // message to additional listeners. That's why we've abstracted out this // class and made it public. // // That's no longer necessary. Instead we added the lock and unlock logic. // That made things much simpler. There is no reason for anyone else to // use the CallbackList right now. class CallbackList { private: struct CallbackInfo { RequestListener *requestListener; int callbackId; bool lock; bool immuneToLock; }; std::map< std::string, CallbackInfo > _commandList; std::map< SocketInfo *, std::queue< ExternalRequest * > > _locked; std::set< SocketInfo * > _initialized; volatile bool _requireLogin; bool _dumpAll; bool _dumpUnknownCommands; void dumpMessage(ExternalRequest *request, std::string const &reason); protected: // This is called for each command which has been successfully looked up, // immediately before it is dispatched. If there is no handler for the // message, this is not called. By default this does nothing. virtual void successfulDispatch(Request *); public: // When we have an external request, we give it to this function. It will // try to dispatch that message. If there is no listener, it will delete // the message. If the socket is locked, this will queue up the request // until it is unlocked. If the request type is flagged appropriately, // the socket will be locked after dispatching this message. void dispatchMessage(ExternalRequest *request); // These are used to listen to a new command. First call // createNewCommandRequest() with the details of the new mapping. Then send // that message to the dispatch thread. In the right thread, send that // message to addNewCommand(). static Request *createNewCommandRequest(std::string commandName, RequestListener *listener, int callbackId, bool lock = false, bool immuneToLock = false); void addNewCommand(Request *request); // Normally a command is dispatched to a thread as soon as it is received. // However, sometimes we need to keep things in order. For example, the // first thing the client does when it opens a connection is log in. When // we receive the login command we lock the socket, and we don't pass on any // more messages until we get unlocked. That way a user's other requests, // handled by other threads, are not visible until the login request was // published. This is not thread safe. Send yourself a request to do the // unlock. void unlock(SocketInfo *socket); // This object contains information about any locked socket. If a socket // closes, use this to remove the references. void remove(SocketInfo *socket); CallbackList(); // This is thread safe. Presumably you'd do this before the call to start // receiving new connections. void requireLogin() { _requireLogin = true; } // This is virtual to avoid a compiler warning. virtual ~CallbackList(); }; // This is the main command dispatcher. It listens for external messages, // converts them into the standard message object, and impliments a // dispatcher. class CommandDispatcher : private RequestListener, private ThreadClass { private: typedef std::map< class SocketInfo *, class Buffer * > AllBuffers; AllBuffers _allBuffers; enum { mtNewCommand, mtNewInput, mtUnlock, mtQuit }; // Process in our thread queue. enum { mtSetInput, mtZLibStats, mtProxyInfo }; // For processing immediately. RequestQueue _incomingRequests; static CommandDispatcher *_instance; void newRequest(Request *request); DeadManTimer _deadManTimer; class TimerCallbackList : public CallbackList { private: DeadManTimer * const _timer; protected: void successfulDispatch(Request *request); public: TimerCallbackList(DeadManTimer *timer); }; TimerCallbackList _callbackList; volatile bool _showEOF; protected: void threadFunction(); public: CommandDispatcher(std::string const &name = "CommandDispatcher"); ~CommandDispatcher(); // This is thread safe. However, you probably want to // do this in the main thread before you return from your // initialization code, to ensure that this is registered // before any user commands come in. void listenForCommand(std::string commandName, RequestListener *listener, int callbackId, bool lock = false, bool immuneToLock = false); // This is also thread safe. If you lock a socket, you should unlock it // or disconnect it. Otherwise messages will just keep building up. void unlock(SocketInfo *socket); // This is thread safe. Presumably you'd do this before the call to start // receiving new connections. // By default logging in is optional. That's how the AX server worked. Now // we have the option of a more traditional server. The first command has // to be a login or you will get disconnected. For simplicity we assume // that any command that locks the queue is a valid first command. void requireLogin() { _callbackList.requireLogin(); } // You can have more than one of these objects, but it is easier // for the command implementors if we only have one. This result // is fixed and therefore thread-safe. static CommandDispatcher *getInstance(); // These are for the thread which sends data to us. RequestListener *getInput() { return &_incomingRequests; } int getInputCallbackId() { return mtNewInput; } DeadManTimer &getDeadManTimer() { return _deadManTimer; } // This says how much we report to the log. Some errors are always reported. // EOF is nice to follow, and often nice to know, but in some applications // is it ridiculously common. void setShowEOF(bool showEOF) { _showEOF = showEOF; } }; #endif