#include <boost/utility.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
-#include <boost/iostreams/device/file_descriptor.hpp>
-#include <boost/iostreams/stream.hpp>
#include "../Utils/intrusive_refcount.hh"
#include "../Socket/Protocols/INet/TCPSocketHandle.hh"
#include "../Socket/ServerSocketHandle.hh"
#include "../Scheduler/Scheduler.hh"
+#include "../Scheduler/Binding.hh"
+#include "../Scheduler/Timer.hh"
#include "../Scheduler/ReadHelper.hh"
#include "Parse.hh"
#include "Executor.hh"
#include "../Socket/Protocols/INet/INetAddressing.hh"
+#include "../Utils/Logger.hh"
//#include "Server.mpp"
+#include "Server.ih"
///////////////////////////////hh.p////////////////////////////////////////
namespace senf {
This class provides an interactive console TCP server.
- \todo Add readline support
- \todo Add interactivity detection using timeout
+ \idea To support blocking commands, we could give the Client 'suspend()' and 'resume()'
+ members. suspend() would probably throw some kind of exception to transfer control back
+ to the Client instance. on resume(), the command would be called again, maybe setting
+ some flag or something. Example for use: Host name resolution: Here we can just built
+ our own little host-name cache. When the name is not found, we ask the resolver to
+ resolve it and call 'resume' when the name is found. Since it is in the cache now, the
+ command will now complete.
+
+ \ingroup console_access
*/
class Server
- : boost::noncopyable
+ : public senf::intrusive_refcount
{
SENF_LOG_CLASS_AREA();
SENF_LOG_DEFAULT_LEVEL( senf::log::NOTICE );
public:
///////////////////////////////////////////////////////////////////////////
// Types
+
+ typedef detail::ServerHandle ServerHandle;
- typedef senf::ServerSocketHandle<
- senf::MakeSocketPolicy< senf::TCPv4SocketProtocol::Policy,
- senf::UnspecifiedAddressingPolicy>::policy > ServerHandle;
+ enum Mode { Automatic, Interactive, Noninteractive };
+
+ ///////////////////////////////////////////////////////////////////////////
~Server();
static Server & start(senf::INet6SocketAddress const & address);
///< Start server on given IPv6 address/port
- void name(std::string const & name); ///< Set server name
+ std::string const & name() const; ///< Get server name
+ /**< This information is used in the prompt string. */
+
+ Server & name(std::string const & name); ///< Set server name
/**< This information is used in the prompt string. */
+ DirectoryNode & root() const; ///< Get root node
+
+ Server & root(DirectoryNode & root); ///< Set root node
+ /**< \a node will be the root node for all clients launched
+ from this server. */
+
+ Mode mode() const; ///< Get mode
+ /**< \see \ref mode(Mode) */
+
+ Server & mode(Mode mode); ///< Set mode
+ /**< There are two Server types:
+ \li An interactive server displays a command prompt and
+ optionally supports command-line editing.
+ \li A non-interactive server does not display any
+ prompt and does not allow any interactive
+ editing. This type of server is used for (remote)
+ scripting.
+
+ The \a mode parameter selects between these modes. In
+ \c Automatic (the default), a client connection is
+ considered to be interactive if there is no data
+ traffic in the first 500ms after the connection is
+ opened. */
+
+ void stop(); ///< Stop the server
+ /**< All clients will be closed
+ \warning The Server instance itself will be deleted */
+
protected:
private:
Server(ServerHandle handle);
- static void start(ServerHandle handle);
+ static Server & start(ServerHandle handle);
void newClient(Scheduler::EventId event);
void removeClient(Client & client);
ServerHandle handle_;
+ DirectoryNode::ptr root_;
+ Mode mode_;
typedef std::set< boost::intrusive_ptr<Client> > Clients;
Clients clients_;
std::string name_;
- static boost::scoped_ptr<Server> instance_;
-
friend class Client;
};
command prompt, receives the commands, parses them and then passes (using a CommandParser)
and passes the commands to an Executor instance.
- \fixme Fix Client::clientData implementation
- \fixme Remove the 'dup' needed here so we don't close the same fd twice (see Client
- constructor)
- \fixme Make output non-blocking (use a non-blocking/discarding streambuf) and possibly set
- socket send buffer size
- \fixme Don't register a new ReadHelper every round
+ \ingroup console_access
*/
class Client
- : public senf::intrusive_refcount
+ : public senf::intrusive_refcount,
+ private boost::base_from_member< detail::NonblockingSocketOStream >,
+ public senf::log::IOStreamTarget
{
+ typedef boost::base_from_member< detail::NonblockingSocketOStream > out_t;
+
SENF_LOG_CLASS_AREA();
SENF_LOG_DEFAULT_LEVEL( senf::log::NOTICE );
+
+ static const unsigned INTERACTIVE_TIMEOUT = 500; // milliseconds;
+
public:
typedef Server::ServerHandle::ClientSocketHandle ClientHandle;
~Client();
- void stopClient(); ///< Stop the client
+ void stop(); ///< Stop the client
/**< This will close the client socket. */
+ std::string const & name() const;
+ ClientHandle handle() const;
+ std::ostream & stream();
+ std::string promptString() const;
+ DirectoryNode & root() const;
+ Server::Mode mode() const;
+
+ static Client & get(std::ostream & os);
+
protected:
private:
- Client(ClientHandle handle, std::string const & name);
+ Client(Server & server, ClientHandle handle);
- void clientData(ReadHelper<ClientHandle>::ptr helper);
- void showPrompt();
+ void setInteractive();
+ void setNoninteractive();
+ void translate(std::string & data);
+ unsigned handleInput(std::string input, bool incremental = false);
+ virtual void v_write(senf::log::time_type timestamp, std::string const & stream,
+ std::string const & area, unsigned level,
+ std::string const & message);
+
+ Server & server_;
ClientHandle handle_;
- std::string tail_;
+ SchedulerBinding binding_;
+ SchedulerTimer timer_;
CommandParser parser_;
Executor executor_;
std::string name_;
-
- typedef boost::iostreams::stream<boost::iostreams::file_descriptor_sink> fdostream;
- fdostream out_;
+ std::string lastCommand_;
+ boost::scoped_ptr<detail::ClientReader> reader_;
+ Server::Mode mode_;
friend class Server;
+ friend class detail::ClientReader;
+ friend class detail::NonblockingSocketSink;
};
+
+ /** \brief Output Console Client instance as it's string representation
+ \related Client
+ */
+ std::ostream & operator<<(std::ostream & os, Client const & client);
+
+ /** \brief Output Console Client instance as it's string representation
+ \related Client
+ */
+ std::ostream & operator<<(std::ostream & os, Client * client);
}}