common/Util.hpp | 6 + test/Makefile.am | 4 test/UnitPHPProxy.cpp | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/helpers.hpp | 54 +++++++++ 4 files changed, 335 insertions(+)
New commits: commit 1951fdbd426781fd652af2e517e2e61b22b2831d Author: gokaysatir <gokaysa...@collabora.com> AuthorDate: Thu Jul 2 19:54:56 2020 +0300 Commit: Tamás Zolnai <tamas.zol...@collabora.com> CommitDate: Wed Sep 9 15:50:10 2020 +0200 lool: php proxy simulation. Change-Id: I5ea5515e317242f2ad2abd3209ce0241d64b631b Reviewed-on: https://gerrit.libreoffice.org/c/online/+/97820 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Tamás Zolnai <tamas.zol...@collabora.com> diff --git a/common/Util.hpp b/common/Util.hpp index 9dbfebe8b..03719e97f 100644 --- a/common/Util.hpp +++ b/common/Util.hpp @@ -1217,6 +1217,12 @@ int main(int argc, char**argv) return pair.second ? pair : std::make_pair(def, false); } + /// Get system_clock now in miliseconds. + inline int64_t getNowInMS() + { + return std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count(); + } + } // end namespace Util /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/test/Makefile.am b/test/Makefile.am index 9fa6506b1..899dd0764 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -37,6 +37,7 @@ noinst_LTLIBRARIES = \ unit-load.la \ unit-cursor.la \ unit-calc.la \ + unit-php-proxy.la \ unit-insert-delete.la \ unit-close.la \ unit-bad-doc-load.la \ @@ -178,6 +179,8 @@ unit_cursor_la_SOURCES = UnitCursor.cpp unit_cursor_la_LIBADD = $(CPPUNIT_LIBS) unit_calc_la_SOURCES = UnitCalc.cpp unit_calc_la_LIBADD = $(CPPUNIT_LIBS) +unit_php_proxy_la_SOURCES = UnitPHPProxy.cpp +unit_php_proxy_la_LIBADD = $(CPPUNIT_LIBS) unit_insert_delete_la_SOURCES = UnitInsertDelete.cpp unit_insert_delete_la_LIBADD = $(CPPUNIT_LIBS) unit_close_la_SOURCES = UnitClose.cpp @@ -225,6 +228,7 @@ TESTS = \ unit-load.la \ unit-cursor.la \ unit-calc.la \ + unit-php-proxy.la \ unit-insert-delete.la \ unit-close.la \ unit-bad-doc-load.la \ diff --git a/test/UnitPHPProxy.cpp b/test/UnitPHPProxy.cpp new file mode 100644 index 000000000..57c44ac30 --- /dev/null +++ b/test/UnitPHPProxy.cpp @@ -0,0 +1,271 @@ +/* -*- 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/. + */ + +/* + road path: + * cypress test => php server => loolwsd + * loolwsd => php server => cypress test +*/ + +#include <memory> +#include <ostream> +#include <set> +#include <string> + +#include <Poco/Exception.h> +#include <Poco/RegularExpression.h> +#include <Poco/URI.h> +#include <test/lokassert.hpp> + +#include <Unit.hpp> +#include <helpers.hpp> +#include "net/ServerSocket.hpp" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <poll.h> +#include <LOOLWSD.hpp> + +#define _PORT_ 9979 + +const int bufferSize = 16 * 1024; +std::atomic<int64_t> lastRequestMS; + +class SocketThread +{ +public: + const std::string _proxyPrefix = "ProxyPrefix: http://localhost:" + std::to_string(_PORT_) + "/proxytest.php?req=\n"; + + void replaceRequest(std::vector<char>& message) + { + // First line includes the request. We will remove proxy prefix && get real request. + std::vector<char>::iterator firstLineEnd = std::find(message.begin(), message.end(), '\n'); + std::string firstLine(message.begin(), firstLineEnd); + std::vector<char>::iterator firstSpace = std::find(message.begin(), firstLineEnd, ' '); // Position of first space char. + std::string request = Util::splitStringToVector(firstLine, ' ')[1]; // First line's format is: METHOD (space) REQUEST (space) HTPP_VERSION + + if (request.find("proxytest.php?req=") != std::string::npos) + { + // We have a proper proxy request. + std::vector<char>::iterator equalSign = std::find(firstSpace + 1, firstLineEnd, '='); // Position of first '=' sign. + if (equalSign != firstLineEnd) + { + // Remove chars from first space until '=' sign (including '=' sign). + // So we remove "http://localhost:_PORT_/proxytest.php?req=" and get the real request. + for (std::vector<char>::iterator it = equalSign; it > firstSpace; it--) + { + message.erase(it); + } + } + } + else + { + // We don't have a proper request. Since we are testing, we will accept this one. + // We will remove only "http://localhost:_PORT_" + std::vector<char>::iterator portNumberLastChar = std::find(firstSpace + 1, firstLineEnd, '9'); // Position of first char of the port number. + if (portNumberLastChar != firstLineEnd) + { + portNumberLastChar = std::next(portNumberLastChar, 3); // We move it position to the last char of the port number. + + for (std::vector<char>::iterator it = portNumberLastChar; it > firstSpace; it--) // Erase including the last char. + { + message.erase(it); + } + } + else + { + LOG_ERR("We could not find the port number's char."); + } + } + } + void addProxyHeader(std::vector<char>& message) + { + std::vector<char>::iterator it = std::find(message.begin(), message.end(), '\n'); + + // Found the first line break. We will paste the prefix on the second line. + if (it == message.end()) + { + message.insert(it, _proxyPrefix.data(), &_proxyPrefix.data()[_proxyPrefix.size()]); + } + else + { + message.insert(it + 1, _proxyPrefix.data(), &_proxyPrefix.data()[_proxyPrefix.size()]); + } + } + bool sendMessage(int socketFD, std::vector<char>& message) + { + int res; + std::size_t wroteLen = 0; + do + { + res = send(socketFD, &message[wroteLen], (wroteLen + bufferSize < message.size() ? bufferSize: message.size() - wroteLen), MSG_NOSIGNAL); + wroteLen += bufferSize; + } + while (wroteLen < message.size() && res > 0); + return res > 0; + } + bool readMessage(int socketFD, std::vector<char>& inBuffer) + { + char buf[16 * 1024]; + ssize_t len; + do + { + do + { + len = recv(socketFD, buf, sizeof(buf), 0); + } + while (len < 0 && errno == EINTR); + + if (len > 0) + { + inBuffer.insert(inBuffer.end(), &buf[0], &buf[len]); + } + } + while (len == (sizeof(buf))); + return len > 0; + } + void handleRegularSocket(std::shared_ptr<StreamSocket> socket) + { + socket->setThreadOwner(std::this_thread::get_id()); + + replaceRequest(socket->getInBuffer()); + addProxyHeader(socket->getInBuffer()); + + int loolSocket = helpers::connectToLocalServer(LOOLWSD::getClientPortNumber(), 1000, true); // Create a socket for loolwsd. + if (loolSocket > 0) + { + sendMessage(loolSocket, socket->getInBuffer()); + std::vector<char> buffer; + while(readMessage(loolSocket, buffer)){}; + socket->send(buffer.data(), buffer.size()); // Send the response to client. + close(loolSocket); + } + socket->closeConnection(); // Close client socket. + } + static void startThread(std::shared_ptr<StreamSocket> socket) + { + SocketThread worker; + // Set socket's option to blocking mode. + helpers::setSocketBlockingMode(socket->getFD(), true); + + std::thread regularSocketThread(&SocketThread::handleRegularSocket, worker, socket); + regularSocketThread.detach(); + } +}; + +class PHPClientRequestHandler: public SimpleSocketHandler +{ +private: + std::weak_ptr<StreamSocket> _socket; + +public: + PHPClientRequestHandler() + { + } + +private: + void onConnect(const std::shared_ptr<StreamSocket>& socket) override + { + _socket = socket; + } + int getPollEvents(std::chrono::steady_clock::time_point /* now */, int64_t & /* timeoutMaxMs */) override + { + return POLLIN; + } + void performWrites() override + { + } + + void handleIncomingMessage(SocketDisposition& disposition) override + { + std::shared_ptr<StreamSocket> socket = _socket.lock(); + disposition.setMove([=] (const std::shared_ptr<Socket> &moveSocket) + { + moveSocket->setThreadOwner(std::thread::id(0)); + SocketThread::startThread(socket); + }); + } +}; + +class PHPServerSocketFactory final : public SocketFactory +{ +public: + PHPServerSocketFactory() + { + } + + std::shared_ptr<Socket> create(const int physicalFd) override + { + // This socket is test's client. + std::shared_ptr<Socket> socket = StreamSocket::create<StreamSocket>(physicalFd, false, std::make_shared<PHPClientRequestHandler>()); + lastRequestMS = Util::getNowInMS(); + return socket; + } +}; + +class UnitPHPProxy : public UnitWSD +{ +private: + std::shared_ptr<SocketPoll> _poll; + +public: + UnitPHPProxy() + { + } + + void configure(Poco::Util::LayeredConfiguration& config) override + { + UnitWSD::configure(config); + config.setBool("ssl.enable", false); + config.setBool("net.proxy_prefix", true); + } + + void invokeTest() + { + try + { + _poll = std::make_shared<SocketPoll>("php client poll"); + _poll->startThread(); + ServerSocket::Type clientPortProto = ServerSocket::Type::Public; + Socket::Type sType = Socket::Type::IPv4; + std::shared_ptr<SocketFactory> factory = std::make_shared<PHPServerSocketFactory>(); + std::shared_ptr<ServerSocket> _serverSocket = std::make_shared<ServerSocket>(sType, *_poll, factory); + _serverSocket->bind(clientPortProto, _PORT_); + _serverSocket->listen(10); + _poll->insertNewSocket(_serverSocket); + + lastRequestMS = Util::getNowInMS(); + int64_t diff = 0; + while (diff < 15000) + { + auto nowMS = Util::getNowInMS(); + diff = nowMS - lastRequestMS; + } + + _poll->joinThread(); + + exitTest(UnitBase::TestResult::Ok); + } + catch(const std::exception& e) + { + std::cerr << e.what() << '\n'; + exitTest(UnitBase::TestResult::Failed); + } + } +}; + +UnitBase* unit_create_wsd(void) { return new UnitPHPProxy(); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ \ No newline at end of file diff --git a/test/helpers.hpp b/test/helpers.hpp index 79948f836..67aac63ed 100644 --- a/test/helpers.hpp +++ b/test/helpers.hpp @@ -246,6 +246,60 @@ inline std::shared_ptr<Poco::Net::StreamSocket> createRawSocket() (Poco::Net::SocketAddress("127.0.0.1", ClientPortNumber)); } +// Sets read / write timeout for the given file descriptor. +inline void setSocketTimeOut(int socketFD, int timeMS) +{ + struct timeval tv; + tv.tv_sec = (float)timeMS / (float)1000; + tv.tv_usec = timeMS; + setsockopt(socketFD, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); +} + +// Sets socket's blocking mode. true for blocking, false for non blocking. +inline void setSocketBlockingMode(int socketFD, bool blocking) +{ + ioctl(socketFD, FIONBIO, blocking == true ? 0: 1); +} + +// Creates a socket and connects it to a local server. Returns the file descriptor. +inline int connectToLocalServer(int portNumber, int socketTimeOutMS, bool blocking) +{ + int socketFD = 0; + struct sockaddr_in serv_addr; + + if ((socketFD = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + LOG_ERR("helpers::connectToLocalServer: Server client could not be created."); + return -1; + } + else + { + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(portNumber); + if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) + { + LOG_ERR("helpers::connectToLocalServer: Invalid address."); + close(socketFD); + return -1; + } + else + { + if (connect(socketFD, (sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) + { + LOG_ERR("helpers::connectToLocalServer: Connection failed."); + close(socketFD); + return -1; + } + else + { + setSocketTimeOut(socketFD, socketTimeOutMS); + setSocketBlockingMode(socketFD, blocking); + return socketFD; + } + } + } +} + inline std::string const & getTestServerURI() { _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits