diff --git a/readme.md b/readme.md index 67899d7..0804039 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,9 @@ http-server is a simple http server that does not conform to any standards. It's main purpose is for experimentation: both playing around with HTTP quirks and simple socket programming under Linux. +## Versions +The current master branch version is epoll based, but a traditional iterative version was tagged as `traditional-iterative` (use `git show traditional-iterative` to view more information about it). + ## Requirements I have tested building the program with the following compilers: - gcc 8.3.0 diff --git a/src/server/connectionoperator.cpp b/src/server/connectionoperator.cpp index a3763c6..505e4f5 100644 --- a/src/server/connectionoperator.cpp +++ b/src/server/connectionoperator.cpp @@ -6,22 +6,9 @@ #include #include -void ConnectionOperator::SendResponse(ClientSocket const & clientSocket, Http::Response const & response) const +std::vector ConnectionOperator::HandleNewConnection(int fd) { - auto bytesToSend = response.Serialize(); - try - { - clientSocket.WriteBytes(bytesToSend); - } - catch(std::runtime_error & e) - { - logger.Error("Error writing data to clientSocket"); - } -} - -void ConnectionOperator::HandleNewConnection(ClientSocket const & newClient) -{ - auto requestBytes = newClient.ReadBytes(); + auto requestBytes = Socket::ReadBytes(fd, 512); Http::Request request; Http::Response response; try @@ -37,8 +24,8 @@ void ConnectionOperator::HandleNewConnection(ClientSocket const & newClient) logger.Error(ss.str()); response.code = HttpResponse::Code::BAD_REQUEST; - SendResponse(newClient, response); - return; + + return response.Serialize(); } for(size_t i = 0; i < middlewares.size(); ++i) @@ -57,12 +44,11 @@ void ConnectionOperator::HandleNewConnection(ClientSocket const & newClient) logger.Error(ss.str()); response.code = HttpResponse::Code::NOT_IMPLEMENTED; - SendResponse(newClient, response); - return; + + return response.Serialize(); } - auto bytesToSend = response.Serialize(); - newClient.WriteBytes(bytesToSend); + return response.Serialize(); } ConnectionOperator::ConnectionOperator(Logger & _logger, ServerConfiguration const & serverConfiguration) diff --git a/src/server/connectionoperator.hpp b/src/server/connectionoperator.hpp index 62709d0..7bcbb91 100644 --- a/src/server/connectionoperator.hpp +++ b/src/server/connectionoperator.hpp @@ -2,7 +2,7 @@ #include "../logger.hpp" #include "../middleware/base.hpp" #include -#include "socket/clientsocket.hpp" +#include "socket.hpp" #include #include @@ -12,10 +12,8 @@ private: Logger & logger; std::vector> middlewares; - void SendResponse(ClientSocket const & clientSocket, Http::Response const & response) const; - public: - void HandleNewConnection(ClientSocket const & newClient); + std::vector HandleNewConnection(int fd); ConnectionOperator(Logger & logger, ServerConfiguration const & serverConfiguration); }; \ No newline at end of file diff --git a/src/server/server.cpp b/src/server/server.cpp index 0390fbf..50846cd 100755 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -1,32 +1,119 @@ #include "../logger.hpp" #include "configuration.hpp" #include "server.hpp" +#include #include #include +#include +#include + +void HttpServer::HandleEpollInEvent(int fd) +{ + if (fd != listeningSocketFileDescriptor) + { + logger.Info("EPOLLIN Attempted to handle a non registered file descriptor"); + + return; + } + + unsigned sockaddrSize = sizeof(sockaddr_in); + int const connectionFileDescriptor = accept( + listeningSocketFileDescriptor, + reinterpret_cast(&socketAddress), + &sockaddrSize); + + socketWriteMap[connectionFileDescriptor] = connectionOperator.HandleNewConnection(connectionFileDescriptor); + + epoll_event event; + event.data.fd = connectionFileDescriptor; + event.events = EPOLLOUT | EPOLLONESHOT; + if (epoll_ctl(pollFileDescriptor, EPOLL_CTL_ADD, connectionFileDescriptor, &event) < 0) + { + logger.Error("Error registering file descriptor for EPOLLOUT"); + } +} + +void HttpServer::HandleEpollOutEvent(int fd) +{ + auto result = socketWriteMap.find(fd); + if (result == socketWriteMap.end()) + { + logger.Error("EPOLLOUT Event for unexpected fd"); + return; + } + + Socket::WriteBytes(fd, result->second); + close(fd); +} void HttpServer::Execute() { + std::vector epollEvents; + epollEvents.resize(1000); + while(isOpen) { - try + int eventsHappened = epoll_wait(pollFileDescriptor, epollEvents.data(), static_cast(epollEvents.size()), -1); + for (int i = 0; i < eventsHappened; ++i) { - ClientSocket newClient = listeningSocket.AcceptNextConnection(); - connectionOperator.HandleNewConnection(newClient); - } - catch (std::runtime_error & e) - { - logger.Info("ClientSocket dropped on accept"); + int const fd = epollEvents[i].data.fd; + if (epollEvents[i].events & EPOLLIN) + { + HandleEpollInEvent(fd); + } + else + { + HandleEpollOutEvent(fd); + } } } } HttpServer::HttpServer(Logger & _logger, ServerConfiguration const & serverConfiguration) : logger(_logger), - listeningSocket(serverConfiguration.GetPort()), + pollFileDescriptor(-1), + listeningSocketFileDescriptor(-1), connectionOperator(_logger, serverConfiguration), isOpen(true) { + pollFileDescriptor = epoll_create1(0); + if (pollFileDescriptor < 0) + { + throw std::runtime_error("Error creating epoll file descriptor"); + } + + socketAddress.sin_family = AF_INET; + socketAddress.sin_addr.s_addr = INADDR_ANY; + socketAddress.sin_port = htons(serverConfiguration.GetPort()); + + listeningSocketFileDescriptor = ListeningSocket::Create(socketAddress, 10000); + if (listeningSocketFileDescriptor < 0) + { + throw std::runtime_error("Error creating listening socket"); + } + + epoll_event event; + event.data.fd = listeningSocketFileDescriptor; + event.events = EPOLLIN; + if (epoll_ctl(pollFileDescriptor, EPOLL_CTL_ADD, listeningSocketFileDescriptor, &event) < 0) + { + throw std::runtime_error("Error registering listening socket with epoll facilities"); + } + std::stringstream ss; ss << "Listening on port " << serverConfiguration.GetPort(); logger.Info(ss.str()); +} + +HttpServer::~HttpServer() +{ + if (listeningSocketFileDescriptor >= 0) + { + close(listeningSocketFileDescriptor); + } + + if (pollFileDescriptor >= 0) + { + close(pollFileDescriptor); + } } \ No newline at end of file diff --git a/src/server/server.hpp b/src/server/server.hpp index 3ef38da..171a1b6 100755 --- a/src/server/server.hpp +++ b/src/server/server.hpp @@ -1,20 +1,28 @@ #pragma once #include "../logger.hpp" #include "connectionoperator.hpp" -#include "socket/listeningsocket.hpp" +#include class HttpServer { private: Logger & logger; - ListeningSocket listeningSocket; + int pollFileDescriptor; + sockaddr_in socketAddress; + int listeningSocketFileDescriptor; + std::map> socketWriteMap; ConnectionOperator connectionOperator; bool isOpen; + void HandleEpollInEvent(int fd); + + void HandleEpollOutEvent(int fd); + public: void Execute(); HttpServer(Logger & logger, ServerConfiguration const & serverConfiguration); + ~HttpServer(); HttpServer(HttpServer & other) = delete; HttpServer & operator=(HttpServer & other) = delete; }; \ No newline at end of file diff --git a/src/server/socket.cpp b/src/server/socket.cpp new file mode 100644 index 0000000..8ee3f24 --- /dev/null +++ b/src/server/socket.cpp @@ -0,0 +1,85 @@ +#include "socket.hpp" +#include +#include + +namespace ListeningSocket +{ + int Create(sockaddr_in & socketAddress, int const connectionLimit) + { + int socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0); + if (socketFileDescriptor < 0) + { + return -1; + } + + int enable = 1; + if (setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) + { + close(socketFileDescriptor); + return -1; + } + + int const bindResult = bind( + socketFileDescriptor, + reinterpret_cast(&socketAddress), + sizeof(sockaddr_in)); + if (bindResult < 0) + { + close(socketFileDescriptor); + return -1; + } + + int const listenResult = listen(socketFileDescriptor, connectionLimit); + if (listenResult < 0) + { + close(socketFileDescriptor); + return -1; + } + + return socketFileDescriptor; + } +} + +namespace Socket +{ + std::vector ReadBytes(int fd, size_t limit) + { + size_t const readChunkSize = 128; + + std::vector buffer; + ssize_t totalBytesRead = 0; + ssize_t bytesRead = 0; + do + { + buffer.resize(buffer.size() + readChunkSize); + bytesRead = read(fd, &buffer[totalBytesRead], readChunkSize); + if (bytesRead < 0) + { + throw std::runtime_error("Error reading from filedescriptor"); + } + + totalBytesRead += bytesRead; + } while (bytesRead == readChunkSize && bytesRead < limit); + + buffer.resize(totalBytesRead); + return buffer; + } + + size_t WriteBytes(int fd, std::vector const & bytes) + { + ssize_t totalBytesWritten = 0; + size_t const sizeToWrite = bytes.size(); + while (totalBytesWritten < sizeToWrite) + { + ssize_t bytesWritten = write(fd, &bytes[totalBytesWritten], sizeToWrite - totalBytesWritten); + if (bytesWritten <= 0) + { + throw std::runtime_error("Error writing to filedescriptor"); + } + + totalBytesWritten += bytesWritten; + } + + return totalBytesWritten; + } +} \ No newline at end of file diff --git a/src/server/socket.hpp b/src/server/socket.hpp new file mode 100644 index 0000000..4f62227 --- /dev/null +++ b/src/server/socket.hpp @@ -0,0 +1,16 @@ +#pragma once +#include +#include +#include + +namespace ListeningSocket +{ + int Create(sockaddr_in & socketAddress, int const connectionLimit); +} + +namespace Socket +{ + std::vector ReadBytes(int fd, size_t limit); + + size_t WriteBytes(int fd, std::vector const & bytes); +} \ No newline at end of file diff --git a/src/server/socket/clientsocket.cpp b/src/server/socket/clientsocket.cpp deleted file mode 100644 index cdf6f43..0000000 --- a/src/server/socket/clientsocket.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "clientsocket.hpp" -#include -#include - -std::vector ClientSocket::ReadBytes(size_t limit) const -{ - size_t const readChunkSize = 128; - - std::vector buffer; - ssize_t totalBytesRead = 0; - ssize_t bytesRead = 0; - do - { - buffer.resize(buffer.size() + readChunkSize); - bytesRead = read(fileDescriptor, &buffer[totalBytesRead], readChunkSize); - if (bytesRead < 0) - { - throw std::runtime_error("Error reading from filedescriptor"); - } - - totalBytesRead += bytesRead; - } while (bytesRead == readChunkSize && bytesRead < limit); - - buffer.resize(totalBytesRead); - return buffer; -} - -size_t ClientSocket::WriteBytes(std::vector const & bytes) const -{ - ssize_t totalBytesWritten = 0; - size_t const sizeToWrite = bytes.size(); - while (totalBytesWritten < sizeToWrite) - { - ssize_t bytesWritten = write(fileDescriptor, &bytes[totalBytesWritten], sizeToWrite - totalBytesWritten); - if (bytesWritten <= 0) - { - throw std::runtime_error("Error writing to filedescriptor"); - } - - totalBytesWritten += bytesWritten; - } - - return totalBytesWritten; -} - -ClientSocket::ClientSocket(int _fileDescriptor) - : fileDescriptor(_fileDescriptor) -{ - if (_fileDescriptor < 0) - { - throw std::runtime_error("clientSocket created with invalid file descriptor"); - } -} - -ClientSocket::~ClientSocket() -{ - if (fileDescriptor >= 0) - { - close(fileDescriptor); - } -} - -ClientSocket::ClientSocket(ClientSocket && other) -{ - fileDescriptor = other.fileDescriptor; - - other.fileDescriptor = -1; -} \ No newline at end of file diff --git a/src/server/socket/clientsocket.hpp b/src/server/socket/clientsocket.hpp deleted file mode 100644 index 83f638f..0000000 --- a/src/server/socket/clientsocket.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once -#include -#include - -class ClientSocket -{ -private: - int fileDescriptor; - -public: - // Parameter limit is a multiple of 128 - std::vector ReadBytes(size_t limit = 512) const; - - size_t WriteBytes(std::vector const & bytes) const; - - ClientSocket(int _fileDescriptor); - ~ClientSocket(); - - ClientSocket(ClientSocket && other); - - ClientSocket(ClientSocket & other) = delete; - ClientSocket & operator=(ClientSocket & other) = delete; -}; \ No newline at end of file diff --git a/src/server/socket/listeningsocket.cpp b/src/server/socket/listeningsocket.cpp deleted file mode 100644 index 3d68b6d..0000000 --- a/src/server/socket/listeningsocket.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "listeningsocket.hpp" -#include -#include - -int const connectionLimit = 10; - -ClientSocket ListeningSocket::AcceptNextConnection() -{ - unsigned sockaddrSize = sizeof(sockaddr_in); - int connectionFileDescriptor = accept( - socketFileDescriptor, - reinterpret_cast(&socketAddress), - &sockaddrSize); - - return ClientSocket(connectionFileDescriptor); -} - -ListeningSocket::ListeningSocket(int const port) - : socketFileDescriptor(-1), - socketAddress() -{ - socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0); - if (socketFileDescriptor < 0) - { - throw std::runtime_error("socket creation error"); - } - - int enable = 1; - if (setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) - { - throw std::runtime_error("setsockopt SO_REUSEADDR failed"); - } - - socketAddress.sin_family = AF_INET; - socketAddress.sin_addr.s_addr = INADDR_ANY; - socketAddress.sin_port = htons(port); - - int const bindResult = bind( - socketFileDescriptor, - reinterpret_cast(&socketAddress), - sizeof(sockaddr_in)); - if (bindResult < 0) - { - throw std::runtime_error("socket bind error code " + std::to_string(errno)); - } - - int const listenResult = listen(socketFileDescriptor, connectionLimit); - if (listenResult < 0) - { - throw std::runtime_error("socket listening error"); - } -} - -ListeningSocket::~ListeningSocket() -{ - if (socketFileDescriptor >= 0) - { - close(socketFileDescriptor); - } -} \ No newline at end of file diff --git a/src/server/socket/listeningsocket.hpp b/src/server/socket/listeningsocket.hpp deleted file mode 100644 index e187517..0000000 --- a/src/server/socket/listeningsocket.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include "clientsocket.hpp" -#include -#include - -class ListeningSocket -{ -private: - int socketFileDescriptor; - sockaddr_in socketAddress; - -public: - ClientSocket AcceptNextConnection(); - - ListeningSocket(int const port); - ~ListeningSocket(); - - ListeningSocket(ListeningSocket & other) = delete; - ListeningSocket & operator=(ListeningSocket & other) = delete; -}; \ No newline at end of file