[Libreoffice-commits] online.git: loolwsd/Admin.cpp loolwsd/Admin.hpp loolwsd/AdminModel.hpp loolwsd/ChildProcessSession.hpp loolwsd/LOOLBroker.cpp loolwsd/Makefile.am
Pranav Kant
pranavk at collabora.com
Thu Mar 3 18:27:13 UTC 2016
loolwsd/Admin.cpp | 272 ++++++++++++++++++++++++++++++++++++++++
loolwsd/Admin.hpp | 243 +----------------------------------
loolwsd/AdminModel.hpp | 19 ++
loolwsd/ChildProcessSession.hpp | 2
loolwsd/LOOLBroker.cpp | 4
loolwsd/Makefile.am | 2
6 files changed, 308 insertions(+), 234 deletions(-)
New commits:
commit 85c6467344710ef1717140c9d17fe6383b420746
Author: Pranav Kant <pranavk at collabora.com>
Date: Fri Feb 26 11:55:13 2016 +0530
loolwsd: Move Admin class to separate header
As a test, add command to fetch documents from AdminModel.
Change-Id: I3cb7097ba7dde049f3b2478fe7b6b6c309da1d92
Reviewed-on: https://gerrit.libreoffice.org/22781
Reviewed-by: Tor Lillqvist <tml at collabora.com>
Tested-by: Tor Lillqvist <tml at collabora.com>
diff --git a/loolwsd/Admin.cpp b/loolwsd/Admin.cpp
new file mode 100644
index 0000000..e30d55e
--- /dev/null
+++ b/loolwsd/Admin.cpp
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <cassert>
+#include <sys/poll.h>
+#include <sys/prctl.h>
+
+#include <Poco/Net/WebSocket.h>
+#include <Poco/Net/HTTPRequestHandler.h>
+#include <Poco/Net/HTTPServerParams.h>
+#include <Poco/Net/HTTPServerRequest.h>
+#include <Poco/Net/HTTPServerResponse.h>
+#include <Poco/Net/NetException.h>
+#include <Poco/StringTokenizer.h>
+
+#include "Admin.hpp"
+#include "AdminModel.hpp"
+#include "Common.hpp"
+#include "LOOLProtocol.hpp"
+#include "Util.hpp"
+
+using namespace LOOLProtocol;
+
+using Poco::Net::HTTPRequestHandler;
+using Poco::Net::HTTPRequestHandlerFactory;
+using Poco::Net::HTTPResponse;
+using Poco::Net::HTTPServerParams;
+using Poco::Net::HTTPServerRequest;
+using Poco::Net::HTTPServerResponse;
+using Poco::Net::ServerSocket;
+using Poco::Net::WebSocket;
+using Poco::Net::WebSocketException;
+using Poco::StringTokenizer;
+using Poco::Net::Socket;
+
+/// Handle admin requests.
+class AdminRequestHandler: public HTTPRequestHandler
+{
+public:
+
+ AdminRequestHandler(Admin* adminManager)
+ : _admin(adminManager)
+ { }
+
+ void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+ {
+ assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
+
+ const std::string thread_name = "admin_ws";
+ try
+ {
+ if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
+ Log::error("Cannot set thread name to " + thread_name + ".");
+
+ Log::debug("Thread [" + thread_name + "] started.");
+
+ auto ws = std::make_shared<WebSocket>(request, response);
+ const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000);
+ int flags = 0;
+ int n = 0;
+ ws->setReceiveTimeout(0);
+ do
+ {
+ char buffer[200000]; //FIXME: Dynamic?
+
+ if (ws->poll(waitTime, Socket::SELECT_READ))
+ {
+ n = ws->receiveFrame(buffer, sizeof(buffer), flags);
+
+ if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
+ {
+ // Echo back the ping payload as pong.
+ // Technically, we should send back a PONG control frame.
+ // However Firefox (probably) or Node.js (possibly) doesn't
+ // like that and closes the socket when we do.
+ // Echoing the payload as a normal frame works with Firefox.
+ ws->sendFrame(buffer, n /*, WebSocket::FRAME_OP_PONG*/);
+ }
+ else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
+ {
+ // In case we do send pings in the future.
+ }
+ else if (n <= 0)
+ {
+ // Connection closed.
+ Log::warn() << "Received " << n
+ << " bytes. Connection closed. Flags: "
+ << std::hex << flags << Log::end;
+ break;
+ }
+ else
+ {
+ assert(n > 0);
+ const std::string firstLine = getFirstLine(buffer, n);
+ StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+ Log::trace() << "Recv: " << firstLine << Log::end;
+
+ if (firstLine == "eof")
+ {
+ Log::info("Received EOF. Finishing.");
+ break;
+ }
+
+ if (tokens.count() == 1 && tokens[0] == "stats")
+ {
+ //TODO: Collect stats and reply back to admin.
+ // We need to ask Broker to give us some numbers on docs/clients/etc.
+ // But we can also collect some memory info using system calls.
+
+ std::string statsResponse;
+
+ const auto cmd = "pstree -a -c -h -A -p " + std::to_string(getpid());
+ FILE* fp = popen(cmd.c_str(), "r");
+ if (fp == nullptr)
+ {
+ statsResponse = "error: failed to collect stats.";
+ ws->sendFrame(statsResponse.data(), statsResponse.size());
+ continue;
+ }
+
+ char treeBuffer[1024];
+ while (fgets(treeBuffer, sizeof(treeBuffer)-1, fp) != nullptr)
+ {
+ statsResponse += treeBuffer;
+ statsResponse += "</ BR>\n";
+ }
+
+ pclose(fp);
+
+ ws->sendFrame(statsResponse.data(), statsResponse.size());
+ }
+ else if (tokens.count() == 1 && tokens[0] == "documents")
+ {
+
+ std::string response = "documents " + _admin->getDocuments();
+ ws->sendFrame(response.data(), response.size());
+ }
+ }
+ }
+ }
+ while (!TerminationFlag &&
+ (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
+ Log::debug() << "Finishing AdminProcessor. TerminationFlag: " << TerminationFlag
+ << ", payload size: " << n
+ << ", flags: " << std::hex << flags << Log::end;
+ }
+ catch (const WebSocketException& exc)
+ {
+ Log::error("AdminRequestHandler::handleRequest(), WebSocketException: " + exc.message());
+ switch (exc.code())
+ {
+ case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
+ response.set("Sec-WebSocket-Version", WebSocket::WEBSOCKET_VERSION);
+ // fallthrough
+ case WebSocket::WS_ERR_NO_HANDSHAKE:
+ case WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
+ case WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
+ response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
+ response.setContentLength(0);
+ response.send();
+ break;
+ }
+ }
+ catch (const std::exception& exc)
+ {
+ Log::error(std::string("Exception: ") + exc.what());
+ }
+
+ Log::debug("Thread [" + thread_name + "] finished.");
+ }
+
+private:
+ Admin* _admin;
+};
+
+//TODO: Move to common header.
+class AdminRequestHandlerFactory: public HTTPRequestHandlerFactory
+{
+public:
+ AdminRequestHandlerFactory(Admin* adminManager)
+ : _admin(adminManager)
+ {}
+
+ HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
+ {
+ auto logger = Log::info();
+ logger << "Request from " << request.clientAddress().toString() << ": "
+ << request.getMethod() << " " << request.getURI() << " "
+ << request.getVersion();
+
+ for (HTTPServerRequest::ConstIterator it = request.begin(); it != request.end(); ++it)
+ {
+ logger << " / " << it->first << ": " << it->second;
+ }
+
+ logger << Log::end;
+ return new AdminRequestHandler(_admin);
+ }
+
+private:
+ Admin* _admin;
+};
+
+/// An admin command processor.
+Admin::Admin(const int brokerPipe, const int notifyPipe) :
+ _srv(new AdminRequestHandlerFactory(this), ServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams),
+ _model(AdminModel())
+{
+ Admin::BrokerPipe = brokerPipe;
+ Admin::NotifyPipe = notifyPipe;
+}
+
+Admin::~Admin()
+{
+ Log::info("~Admin dtor.");
+ _srv.stop();
+}
+
+std::string Admin::getDocuments()
+{
+ return _model.getDocuments();
+}
+
+void Admin::handleInput(std::string& message)
+{
+ StringTokenizer tokens(message, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+ if (tokens.count() > 2 && tokens[0] == "document")
+ {
+ std::string pid = tokens[1];
+ std::string url = tokens[2];
+
+ _model.addDocument(std::stoi(pid), url);
+ }
+}
+
+void Admin::run()
+{
+ Log::info("Listening on Admin port " + std::to_string(ADMIN_PORT_NUMBER));
+
+ // Start a server listening on the admin port.
+ _srv.start();
+
+ // Start listening for data changes
+ struct pollfd pollPipeNotify;
+ pollPipeNotify.fd = NotifyPipe;
+ pollPipeNotify.events = POLLIN;
+ pollPipeNotify.revents = 0;
+
+ static const std::string thread_name = "admin_thread";
+
+ if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
+ Log::error("Cannot set thread name to " + thread_name + ".");
+
+ Log::info("Thread [" + thread_name + "] started.");
+
+ Util::pollPipeForReading(pollPipeNotify, FIFO_NOTIFY, NotifyPipe,
+ [this](std::string& message) { return handleInput(message); } );
+
+ Log::debug("Thread [" + thread_name + "] finished.");
+}
+
+//TODO: Clean up with something more elegant.
+int Admin::BrokerPipe;
+int Admin::NotifyPipe;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/Admin.hpp b/loolwsd/Admin.hpp
index 1216505..0356213 100644
--- a/loolwsd/Admin.hpp
+++ b/loolwsd/Admin.hpp
@@ -10,262 +10,39 @@
#ifndef INCLUDED_ADMIN_HPP
#define INCLUDED_ADMIN_HPP
-#include <cassert>
-#include <condition_variable>
-#include <map>
-#include <memory>
-#include <mutex>
-#include <ostream>
-#include <set>
-#include <sys/poll.h>
-
-#include <Poco/Net/WebSocket.h>
-#include <Poco/Buffer.h>
-#include <Poco/Path.h>
-#include <Poco/StringTokenizer.h>
-#include <Poco/Types.h>
#include <Poco/Net/HTTPServer.h>
-#include <Poco/Net/HTTPServerParams.h>
-#include <Poco/Net/HTTPServerParams.h>
-#include <Poco/Net/HTTPServerRequest.h>
-#include <Poco/Net/HTTPServerResponse.h>
+#include <Poco/Runnable.h>
+#include <Poco/Types.h>
#include "AdminModel.hpp"
-#include "Common.hpp"
-#include "LOOLProtocol.hpp"
-#include "Util.hpp"
-
-using namespace LOOLProtocol;
-
-using Poco::Exception;
-using Poco::File;
-using Poco::Net::HTTPRequest;
-using Poco::Net::HTTPRequestHandler;
-using Poco::Net::HTTPRequestHandlerFactory;
-using Poco::Net::HTTPResponse;
-using Poco::Net::HTTPServer;
-using Poco::Net::HTTPServerParams;
-using Poco::Net::HTTPServerRequest;
-using Poco::Net::HTTPServerResponse;
-using Poco::Net::ServerSocket;
-using Poco::Net::WebSocket;
-using Poco::Net::WebSocketException;
-using Poco::Path;
-using Poco::Runnable;
-using Poco::StringTokenizer;
-using Poco::Net::Socket;
const std::string FIFO_NOTIFY = "loolnotify.fifo";
-/// Handle admin requests.
-class AdminRequestHandler: public HTTPRequestHandler
-{
-public:
-
- void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
- {
- assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
-
- const std::string thread_name = "admin_ws";
- try
- {
- if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
- Log::error("Cannot set thread name to " + thread_name + ".");
-
- Log::debug("Thread [" + thread_name + "] started.");
-
- auto ws = std::make_shared<WebSocket>(request, response);
- const Poco::Timespan waitTime(POLL_TIMEOUT_MS * 1000);
- int flags = 0;
- int n = 0;
- ws->setReceiveTimeout(0);
- do
- {
- char buffer[200000]; //FIXME: Dynamic?
-
- if (ws->poll(waitTime, Socket::SELECT_READ))
- {
- n = ws->receiveFrame(buffer, sizeof(buffer), flags);
-
- if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PING)
- {
- // Echo back the ping payload as pong.
- // Technically, we should send back a PONG control frame.
- // However Firefox (probably) or Node.js (possibly) doesn't
- // like that and closes the socket when we do.
- // Echoing the payload as a normal frame works with Firefox.
- ws->sendFrame(buffer, n /*, WebSocket::FRAME_OP_PONG*/);
- }
- else if ((flags & WebSocket::FRAME_OP_BITMASK) == WebSocket::FRAME_OP_PONG)
- {
- // In case we do send pings in the future.
- }
- else if (n <= 0)
- {
- // Connection closed.
- Log::warn() << "Received " << n
- << " bytes. Connection closed. Flags: "
- << std::hex << flags << Log::end;
- break;
- }
- else
- {
- assert(n > 0);
- const std::string firstLine = getFirstLine(buffer, n);
- StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
- if (firstLine == "eof")
- {
- Log::info("Received EOF. Finishing.");
- break;
- }
-
- if (tokens.count() == 1 && tokens[0] == "stats")
- {
- //TODO: Collect stats and reply back to admin.
- // We need to ask Broker to give us some numbers on docs/clients/etc.
- // But we can also collect some memory info using system calls.
-
- std::string statsResponse;
-
- const auto cmd = "pstree -a -c -h -A -p " + std::to_string(getpid());
- FILE* fp = popen(cmd.c_str(), "r");
- if (fp == nullptr)
- {
- statsResponse = "error: failed to collect stats.";
- ws->sendFrame(statsResponse.data(), statsResponse.size());
- continue;
- }
-
- char treeBuffer[1024];
- while (fgets(treeBuffer, sizeof(treeBuffer)-1, fp) != nullptr)
- {
- statsResponse += treeBuffer;
- statsResponse += "</ BR>\n";
- }
-
- pclose(fp);
-
- ws->sendFrame(statsResponse.data(), statsResponse.size());
- }
- }
- }
- }
- while (!TerminationFlag &&
- (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
- Log::debug() << "Finishing AdminProcessor. TerminationFlag: " << TerminationFlag
- << ", payload size: " << n
- << ", flags: " << std::hex << flags << Log::end;
- }
- catch (const WebSocketException& exc)
- {
- Log::error("AdminRequestHandler::handleRequest(), WebSocketException: " + exc.message());
- switch (exc.code())
- {
- case WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION:
- response.set("Sec-WebSocket-Version", WebSocket::WEBSOCKET_VERSION);
- // fallthrough
- case WebSocket::WS_ERR_NO_HANDSHAKE:
- case WebSocket::WS_ERR_HANDSHAKE_NO_VERSION:
- case WebSocket::WS_ERR_HANDSHAKE_NO_KEY:
- response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
- response.setContentLength(0);
- response.send();
- break;
- }
- }
- catch (const std::exception& exc)
- {
- Log::error(std::string("Exception: ") + exc.what());
- }
-
- Log::debug("Thread [" + thread_name + "] finished.");
- }
-};
-
-//TODO: Move to common header.
-class AdminRequestHandlerFactory: public HTTPRequestHandlerFactory
-{
-public:
- HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request) override
- {
- auto logger = Log::info();
- logger << "Request from " << request.clientAddress().toString() << ": "
- << request.getMethod() << " " << request.getURI() << " "
- << request.getVersion();
-
- for (HTTPServerRequest::ConstIterator it = request.begin(); it != request.end(); ++it)
- {
- logger << " / " << it->first << ": " << it->second;
- }
-
- logger << Log::end;
- return new AdminRequestHandler();
- }
-};
-
/// An admin command processor.
-class Admin : public Runnable
+class Admin : public Poco::Runnable
{
public:
- Admin(const int brokerPipe, const int notifyPipe) :
- _srv(new AdminRequestHandlerFactory(), ServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams),
- _model(AdminModel())
- {
- Admin::BrokerPipe = brokerPipe;
- Admin::NotifyPipe = notifyPipe;
- }
+ Admin(const int brokerPipe, const int notifyPipe);
- ~Admin()
- {
- Log::info("~Admin dtor.");
- _srv.stop();
- }
+ ~Admin();
static int getBrokerPid() { return Admin::BrokerPipe; }
- void handleInput(std::string& message)
- {
- std::cout << message << std::endl;
- }
-
- void run() override
- {
- Log::info("Listening on Admin port " + std::to_string(ADMIN_PORT_NUMBER));
-
- // Start a server listening on the admin port.
- _srv.start();
-
- struct pollfd pollPipeNotify;
-
- pollPipeNotify.fd = NotifyPipe;
- pollPipeNotify.events = POLLIN;
- pollPipeNotify.revents = 0;
+ std::string getDocuments();
- static const std::string thread_name = "admin_thread";
+ void run() override;
- if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
- Log::error("Cannot set thread name to " + thread_name + ".");
-
- Log::info("Thread [" + thread_name + "] started.");
-
- Util::pollPipeForReading(pollPipeNotify, FIFO_NOTIFY, NotifyPipe,
- [this](std::string& message) { return handleInput(message); } );
-
- Log::debug("Thread [" + thread_name + "] finished.");
- }
+private:
+ void handleInput(std::string& message);
private:
- HTTPServer _srv;
+ Poco::Net::HTTPServer _srv;
AdminModel _model;
static int BrokerPipe;
static int NotifyPipe;
};
-//TODO: Clean up with something more elegant.
-int Admin::BrokerPipe;
-int Admin::NotifyPipe;
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/AdminModel.hpp b/loolwsd/AdminModel.hpp
index 438666f..dd7cea3 100644
--- a/loolwsd/AdminModel.hpp
+++ b/loolwsd/AdminModel.hpp
@@ -26,6 +26,25 @@ public:
{
Log::info("AdminModel dtor.");
}
+
+ void addDocument(Poco::Process::PID pid, std::string url)
+ {
+ _documents[pid] = url;
+ }
+
+ std::string getDocuments()
+ {
+ std::string response;
+ for (const auto& it: _documents)
+ {
+ response += std::to_string(it.first) + " " + it.second + " <BR/>";
+ }
+
+ return response;
+ }
+
+private:
+ std::map<Poco::Process::PID, std::string> _documents;
};
#endif
diff --git a/loolwsd/ChildProcessSession.hpp b/loolwsd/ChildProcessSession.hpp
index b5e3511..0519fd8 100644
--- a/loolwsd/ChildProcessSession.hpp
+++ b/loolwsd/ChildProcessSession.hpp
@@ -18,6 +18,8 @@
#include <Poco/Thread.h>
#include <Poco/NotificationQueue.h>
+
+#include "Common.hpp"
#include "LOOLSession.hpp"
// The client port number, which is changed via loolwsd args.
diff --git a/loolwsd/LOOLBroker.cpp b/loolwsd/LOOLBroker.cpp
index 2e759cf..d1ab001 100644
--- a/loolwsd/LOOLBroker.cpp
+++ b/loolwsd/LOOLBroker.cpp
@@ -249,6 +249,10 @@ public:
}
StringTokenizer tokens(response, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+ if (tokens.count() > 1 && tokens[1] == "ok")
+ {
+ Util::writeFIFO(writerNotify, "document " + std::to_string(pid) + " " + url + " \r\n");
+ }
return (tokens.count() == 2 && tokens[0] == std::to_string(pid) && tokens[1] == "ok");
}
diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am
index cbd6f02..abf3b7b 100644
--- a/loolwsd/Makefile.am
+++ b/loolwsd/Makefile.am
@@ -9,7 +9,7 @@ AM_LDFLAGS = -pthread
shared_sources = LOOLProtocol.cpp LOOLSession.cpp MessageQueue.cpp Util.cpp
-loolwsd_SOURCES = LOOLWSD.cpp ChildProcessSession.cpp MasterProcessSession.cpp TileCache.cpp $(shared_sources)
+loolwsd_SOURCES = LOOLWSD.cpp ChildProcessSession.cpp MasterProcessSession.cpp TileCache.cpp Admin.cpp $(shared_sources)
noinst_PROGRAMS = loadtest connect lokitclient
More information about the Libreoffice-commits
mailing list