Rewrite middleware to support epoll
This commit is contained in:
@@ -1,44 +1,47 @@
|
||||
#include "configuration.hpp"
|
||||
|
||||
int ServerConfiguration::GetMajorVersion() const
|
||||
namespace Server
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int Configuration::GetMajorVersion() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ServerConfiguration::GetMinorVersion() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
int Configuration::GetMinorVersion() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string const & ServerConfiguration::GetWwwRoot() const
|
||||
{
|
||||
return wwwRoot;
|
||||
}
|
||||
std::string const & Configuration::GetWwwRoot() const
|
||||
{
|
||||
return wwwRoot;
|
||||
}
|
||||
|
||||
std::string const & ServerConfiguration::GetServerName() const
|
||||
{
|
||||
return serverName;
|
||||
}
|
||||
std::string const & Configuration::GetServerName() const
|
||||
{
|
||||
return serverName;
|
||||
}
|
||||
|
||||
int ServerConfiguration::GetPort() const
|
||||
{
|
||||
return port;
|
||||
}
|
||||
int Configuration::GetPort() const
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
bool ServerConfiguration::IsValid() const
|
||||
{
|
||||
return isValid;
|
||||
}
|
||||
bool Configuration::IsValid() const
|
||||
{
|
||||
return isValid;
|
||||
}
|
||||
|
||||
bool ServerConfiguration::LoadFromFile(std::string const & filePath)
|
||||
{
|
||||
// TODO implement
|
||||
return false;
|
||||
}
|
||||
bool Configuration::LoadFromFile(std::string const & filePath)
|
||||
{
|
||||
// TODO implement
|
||||
return false;
|
||||
}
|
||||
|
||||
ServerConfiguration::ServerConfiguration()
|
||||
: wwwRoot("./www"),
|
||||
serverName("http-server"),
|
||||
port(8080)
|
||||
{
|
||||
Configuration::Configuration()
|
||||
: wwwRoot("./www"),
|
||||
serverName("http-server"),
|
||||
port(8080)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class ServerConfiguration
|
||||
namespace Server
|
||||
{
|
||||
private:
|
||||
std::string wwwRoot;
|
||||
std::string serverName;
|
||||
int port;
|
||||
bool isValid;
|
||||
class Configuration
|
||||
{
|
||||
private:
|
||||
std::string wwwRoot;
|
||||
std::string serverName;
|
||||
int port;
|
||||
bool isValid;
|
||||
|
||||
public:
|
||||
int GetMajorVersion() const;
|
||||
int GetMinorVersion() const;
|
||||
std::string const & GetWwwRoot() const;
|
||||
std::string const & GetServerName() const;
|
||||
int GetPort() const;
|
||||
bool IsValid() const;
|
||||
public:
|
||||
int GetMajorVersion() const;
|
||||
int GetMinorVersion() const;
|
||||
std::string const & GetWwwRoot() const;
|
||||
std::string const & GetServerName() const;
|
||||
int GetPort() const;
|
||||
bool IsValid() const;
|
||||
|
||||
bool LoadFromFile(std::string const & filePath);
|
||||
bool LoadFromFile(std::string const & filePath);
|
||||
|
||||
ServerConfiguration();
|
||||
~ServerConfiguration() = default;
|
||||
Configuration();
|
||||
~Configuration() = default;
|
||||
|
||||
ServerConfiguration(ServerConfiguration & other) = delete;
|
||||
ServerConfiguration(ServerConfiguration && other) = delete;
|
||||
};
|
||||
Configuration(Configuration & other) = delete;
|
||||
Configuration(Configuration && other) = delete;
|
||||
};
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
#include "../middleware/notfound.hpp"
|
||||
#include "../middleware/staticcontent.hpp"
|
||||
#include "../logger.hpp"
|
||||
#include "configuration.hpp"
|
||||
#include "connectionoperator.hpp"
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
|
||||
std::vector<char> ConnectionOperator::HandleNewConnection(int fd)
|
||||
{
|
||||
auto requestBytes = Socket::ReadBytes(fd, 512);
|
||||
Http::Request request;
|
||||
Http::Response response;
|
||||
try
|
||||
{
|
||||
request = Http::Request::Deserialize(requestBytes);
|
||||
}
|
||||
catch (std::runtime_error & e)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Error during parsing of request <";
|
||||
ss << e.what();
|
||||
ss << '>';
|
||||
logger.Error(ss.str());
|
||||
|
||||
response.code = HttpResponse::Code::BAD_REQUEST;
|
||||
|
||||
return response.Serialize();
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < middlewares.size(); ++i)
|
||||
{
|
||||
middlewares[i]->HandleRequest(request, response);
|
||||
}
|
||||
|
||||
if (response.code == HttpResponse::Code::UNKNOWN)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Unhandled ";
|
||||
ss << HttpRequest::typeStrings[static_cast<int>(request.type)];
|
||||
ss << " request for file <";
|
||||
ss << request.url.GetPath();
|
||||
ss << '>';
|
||||
logger.Error(ss.str());
|
||||
|
||||
response.code = HttpResponse::Code::NOT_IMPLEMENTED;
|
||||
|
||||
return response.Serialize();
|
||||
}
|
||||
|
||||
return response.Serialize();
|
||||
}
|
||||
|
||||
ConnectionOperator::ConnectionOperator(Logger & _logger, ServerConfiguration const & serverConfiguration)
|
||||
: logger(_logger)
|
||||
{
|
||||
// Base static file server
|
||||
auto const & staticFileRoot = serverConfiguration.GetWwwRoot();
|
||||
if (staticFileRoot.size() > 0)
|
||||
{
|
||||
middlewares.emplace_back(std::make_unique<Middleware::StaticContent>(_logger, staticFileRoot));
|
||||
}
|
||||
|
||||
// ALWAYS LAST!
|
||||
middlewares.emplace_back(std::make_unique<Middleware::NotFound>(_logger));
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
#include "../logger.hpp"
|
||||
#include "../middleware/base.hpp"
|
||||
#include <memory>
|
||||
#include "socket.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ConnectionOperator
|
||||
{
|
||||
private:
|
||||
Logger & logger;
|
||||
std::vector<std::unique_ptr<Middleware::BaseMiddleware>> middlewares;
|
||||
|
||||
public:
|
||||
std::vector<char> HandleNewConnection(int fd);
|
||||
|
||||
ConnectionOperator(Logger & logger, ServerConfiguration const & serverConfiguration);
|
||||
};
|
||||
12
src/server/eventtype.hpp
Normal file
12
src/server/eventtype.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace Server
|
||||
{
|
||||
enum class EventType
|
||||
{
|
||||
Unknown = -1,
|
||||
NewConnection = 0,
|
||||
ReadRequest = 1,
|
||||
WriteResponse
|
||||
};
|
||||
}
|
||||
@@ -2,119 +2,163 @@
|
||||
#include "configuration.hpp"
|
||||
#include "server.hpp"
|
||||
#include <set>
|
||||
#include "socket.hpp"
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <sys/epoll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void HttpServer::HandleEpollInEvent(int fd)
|
||||
namespace Server
|
||||
{
|
||||
if (fd != listeningSocketFileDescriptor)
|
||||
void HttpServer::RegisterNewConnection(int const listeningSocketFd)
|
||||
{
|
||||
logger.Info("EPOLLIN Attempted to handle a non registered file descriptor");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned sockaddrSize = sizeof(sockaddr_in);
|
||||
int const connectionFileDescriptor = accept(
|
||||
listeningSocketFileDescriptor,
|
||||
reinterpret_cast<sockaddr *>(&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);
|
||||
socketWriteMap.erase(result);
|
||||
}
|
||||
|
||||
void HttpServer::Execute()
|
||||
{
|
||||
std::vector<epoll_event> epollEvents;
|
||||
epollEvents.resize(1000);
|
||||
|
||||
while(isOpen)
|
||||
{
|
||||
int eventsHappened = epoll_wait(pollFileDescriptor, epollEvents.data(), static_cast<int>(epollEvents.size()), -1);
|
||||
for (int i = 0; i < eventsHappened; ++i)
|
||||
unsigned sockaddrSize = sizeof(sockaddr_in);
|
||||
int const clientFd = accept(
|
||||
listeningSocketFd,
|
||||
reinterpret_cast<sockaddr *>(&socketAddress),
|
||||
&sockaddrSize);
|
||||
|
||||
if (clientFd < 0)
|
||||
{
|
||||
int const fd = epollEvents[i].data.fd;
|
||||
if (epollEvents[i].events & EPOLLIN)
|
||||
logger.Info("Connection dropped on accepting it");
|
||||
return;
|
||||
}
|
||||
|
||||
scheduleHelper.ScheduleReadFileDescriptor(clientFd);
|
||||
fileDescriptorEventTypeMap[clientFd] = EventType::ReadRequest;
|
||||
}
|
||||
|
||||
void HttpServer::ReadRequest(int const socketFd)
|
||||
{
|
||||
auto bytes = Socket::ReadBytes(socketFd, 512);
|
||||
if (bytes.size() < 1)
|
||||
{
|
||||
logger.Info("Could not read anything from socket");
|
||||
return;
|
||||
}
|
||||
|
||||
auto & request = socketRequestMap[socketFd];
|
||||
request.Deserialize(bytes, logger);
|
||||
|
||||
auto & response = socketResponseMap[socketFd];
|
||||
|
||||
auto middlewareResult = middleware.Start(socketFd, scheduleHelper, request, response);
|
||||
HandleMiddlewareResponse(middlewareResult);
|
||||
}
|
||||
|
||||
void HttpServer::HandleMiddlewareEvent(int const eventFd)
|
||||
{
|
||||
auto middlewareResult = middleware.Continue(eventFd, scheduleHelper);
|
||||
HandleMiddlewareResponse(middlewareResult);
|
||||
}
|
||||
|
||||
void HttpServer::HandleMiddlewareResponse(Middleware::MiddlewareResult const & middlewareResult)
|
||||
{
|
||||
if (middlewareResult.writeResult)
|
||||
{
|
||||
fileDescriptorEventTypeMap[middlewareResult.socketFd] = EventType::WriteResponse;
|
||||
scheduleHelper.ScheduleWriteFileDescriptor(middlewareResult.socketFd, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::WriteResponse(int const socketFd)
|
||||
{
|
||||
auto requestResult = socketRequestMap.find(socketFd);
|
||||
if (requestResult != socketRequestMap.end())
|
||||
{
|
||||
socketRequestMap.erase(requestResult);
|
||||
}
|
||||
|
||||
auto responseResult = socketResponseMap.find(socketFd);
|
||||
Socket::WriteBytes(socketFd, responseResult->second.Serialize());
|
||||
if (responseResult != socketResponseMap.end())
|
||||
{
|
||||
socketResponseMap.erase(responseResult);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::Execute()
|
||||
{
|
||||
while(isOpen)
|
||||
{
|
||||
auto events = scheduler.WaitForEvents();
|
||||
for (size_t i = 0; i < events.size(); ++i)
|
||||
{
|
||||
HandleEpollInEvent(fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleEpollOutEvent(fd);
|
||||
int const fd = events[i];
|
||||
|
||||
auto eventTypeMapIter = fileDescriptorEventTypeMap.find(fd);
|
||||
if (eventTypeMapIter == fileDescriptorEventTypeMap.end())
|
||||
{
|
||||
HandleMiddlewareEvent(fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (eventTypeMapIter->second != EventType::NewConnection)
|
||||
{
|
||||
fileDescriptorEventTypeMap.erase(eventTypeMapIter);
|
||||
}
|
||||
|
||||
switch(eventTypeMapIter->second)
|
||||
{
|
||||
case EventType::NewConnection:
|
||||
RegisterNewConnection(fd);
|
||||
break;
|
||||
|
||||
case EventType::ReadRequest:
|
||||
ReadRequest(fd);
|
||||
break;
|
||||
|
||||
case EventType::WriteResponse:
|
||||
WriteResponse(fd);
|
||||
close(fd);
|
||||
break;
|
||||
|
||||
case EventType::Unknown:
|
||||
default:
|
||||
close(fd);
|
||||
logger.Error("FD with unknown EventType fired and was closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpServer::HttpServer(Logger & _logger, Configuration const & serverConfiguration)
|
||||
: logger(_logger),
|
||||
scheduler(_logger),
|
||||
scheduleHelper(scheduler),
|
||||
listeningSocketFileDescriptor(-1),
|
||||
middleware(Middleware::Middleware::CreateWithStaticFile(_logger, serverConfiguration.GetWwwRoot())),
|
||||
isOpen(true)
|
||||
{
|
||||
socketAddress.sin_family = AF_INET;
|
||||
socketAddress.sin_addr.s_addr = INADDR_ANY;
|
||||
socketAddress.sin_port = htons(serverConfiguration.GetPort());
|
||||
|
||||
listeningSocketFileDescriptor = ListeningSocket::Create(socketAddress, 1000);
|
||||
if (listeningSocketFileDescriptor < 0)
|
||||
{
|
||||
throw std::runtime_error("Error creating listening socket");
|
||||
}
|
||||
|
||||
scheduleHelper.ScheduleReadFileDescriptor(listeningSocketFileDescriptor, false);
|
||||
fileDescriptorEventTypeMap[listeningSocketFileDescriptor] = EventType::NewConnection;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Listening on port " << serverConfiguration.GetPort();
|
||||
logger.Info(ss.str());
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer()
|
||||
{
|
||||
if (listeningSocketFileDescriptor >= 0)
|
||||
{
|
||||
close(listeningSocketFileDescriptor);
|
||||
}
|
||||
|
||||
for (auto & item : fileDescriptorEventTypeMap)
|
||||
{
|
||||
close(item.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HttpServer::HttpServer(Logger & _logger, ServerConfiguration const & serverConfiguration)
|
||||
: logger(_logger),
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,47 @@
|
||||
#pragma once
|
||||
#include "../http/request.hpp"
|
||||
#include "../http/response.hpp"
|
||||
#include "../logger.hpp"
|
||||
#include "connectionoperator.hpp"
|
||||
#include "../middleware/middleware.hpp"
|
||||
#include "../scheduler.hpp"
|
||||
#include "eventtype.hpp"
|
||||
#include <map>
|
||||
#include <netinet/in.h>
|
||||
|
||||
class HttpServer
|
||||
namespace Server
|
||||
{
|
||||
private:
|
||||
Logger & logger;
|
||||
int pollFileDescriptor;
|
||||
sockaddr_in socketAddress;
|
||||
int listeningSocketFileDescriptor;
|
||||
std::map<int, std::vector<char>> socketWriteMap;
|
||||
ConnectionOperator connectionOperator;
|
||||
bool isOpen;
|
||||
class HttpServer
|
||||
{
|
||||
private:
|
||||
Logger & logger;
|
||||
Scheduler scheduler;
|
||||
ScheduleHelper scheduleHelper;
|
||||
|
||||
void HandleEpollInEvent(int fd);
|
||||
sockaddr_in socketAddress;
|
||||
int listeningSocketFileDescriptor;
|
||||
|
||||
void HandleEpollOutEvent(int fd);
|
||||
std::map<int, EventType> fileDescriptorEventTypeMap;
|
||||
std::map<int, Http::Request> socketRequestMap;
|
||||
std::map<int, Http::Response> socketResponseMap;
|
||||
|
||||
public:
|
||||
void Execute();
|
||||
|
||||
HttpServer(Logger & logger, ServerConfiguration const & serverConfiguration);
|
||||
~HttpServer();
|
||||
HttpServer(HttpServer & other) = delete;
|
||||
HttpServer & operator=(HttpServer & other) = delete;
|
||||
};
|
||||
Middleware::Middleware middleware;
|
||||
|
||||
bool isOpen;
|
||||
|
||||
void RegisterNewConnection(int const listeningSocketFd);
|
||||
|
||||
void ReadRequest(int const socketFd);
|
||||
|
||||
void HandleMiddlewareEvent(int const eventFd);
|
||||
|
||||
void HandleMiddlewareResponse(Middleware::MiddlewareResult const & middlewareResult);
|
||||
|
||||
void WriteResponse(int const socketFd);
|
||||
|
||||
public:
|
||||
void Execute();
|
||||
|
||||
HttpServer(Logger & logger, Configuration const & serverConfiguration);
|
||||
~HttpServer();
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace ListeningSocket
|
||||
namespace Server::ListeningSocket
|
||||
{
|
||||
int Create(sockaddr_in & socketAddress, int const connectionLimit)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ namespace ListeningSocket
|
||||
}
|
||||
}
|
||||
|
||||
namespace Socket
|
||||
namespace Server::Socket
|
||||
{
|
||||
std::vector<char> ReadBytes(int fd, size_t limit)
|
||||
{
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include <sys/socket.h>
|
||||
#include <vector>
|
||||
|
||||
namespace ListeningSocket
|
||||
namespace Server::ListeningSocket
|
||||
{
|
||||
int Create(sockaddr_in & socketAddress, int const connectionLimit);
|
||||
}
|
||||
|
||||
namespace Socket
|
||||
namespace Server::Socket
|
||||
{
|
||||
std::vector<char> ReadBytes(int fd, size_t limit);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user