diff --git a/http/CMakeLists.txt b/http/CMakeLists.txt index 6b4aa30..03b07d4 100644 --- a/http/CMakeLists.txt +++ b/http/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(fwoophttp STATIC fwoop_httpclient.cpp + fwoop_httpconnhandler.cpp fwoop_httpdataframe.cpp fwoop_httpframe.cpp fwoop_httpgoawayframe.cpp @@ -21,8 +22,10 @@ target_link_libraries(fwoophttp fwoopbasis) install(TARGETS fwoophttp DESTINATION lib) install(FILES fwoop_httpclient.h + fwoop_httpconnhandler.h fwoop_httpdataframe.h fwoop_httpframe.h + fwoop_httpfunc.h fwoop_httpgoawayframe.h fwoop_httpheader.h fwoop_httpheadersframe.h @@ -39,6 +42,7 @@ install(FILES include(GoogleTest) add_executable(fwoophttp_test + fwoop_httpconnhandler.g.cpp fwoop_httpheadersframe.g.cpp fwoop_httphpacker.g.cpp fwoop_httprequest.g.cpp diff --git a/http/fwoop_httpconnhandler.cpp b/http/fwoop_httpconnhandler.cpp new file mode 100644 index 0000000..daa602c --- /dev/null +++ b/http/fwoop_httpconnhandler.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include + +namespace fwoop { + +HttpConnHandler::HttpConnHandler(int fd, const std::unordered_map &routeMap, + const std::unordered_map &serverEventMap) + : d_fd(fd), d_routeMap(routeMap), d_serverEventMap(serverEventMap) +{ +} + +HttpConnHandler::~HttpConnHandler() +{ + if (d_fd > 0) { + close(d_fd); + d_fd = -1; + } +} + +HttpConnHandler::HttpConnHandler(HttpConnHandler &&rhs) + : d_fd(rhs.d_fd), d_routeMap(rhs.d_routeMap), d_serverEventMap(rhs.d_serverEventMap) +{ + rhs.d_fd = -1; +} + +void HttpConnHandler::operator()() +{ + Log::Debug("received http/1.1 connection"); + constexpr unsigned int bufferSize = 2048; + uint8_t buffer[bufferSize]; + unsigned int bytesRead; + std::error_code ec = SocketIO::read(d_fd, buffer, bufferSize, bytesRead); + if (ec) { + Log::Error("socket read failed", ec); + } + + unsigned int bytesParsed = 0; + int rc; + std::shared_ptr request = HttpRequest::parse(buffer, bytesRead, bytesParsed); + if (!request) { + Log::Error("did not receive full http request"); + } + + Log::Debug("Recieved request: ", *request); + + auto routeFunc = d_routeMap.find(request->getPath()); + auto serverEventFunc = d_serverEventMap.find(request->getPath()); + HttpResponse response; + if (routeFunc != d_routeMap.end()) { + routeFunc->second(*request, response); + } else if (serverEventFunc != d_serverEventMap.end()) { + response.addHeader(HttpHeader::ContentType, "text/event-stream"); + response.addHeader(HttpHeader::CacheControl, "no-store"); + response.setStatus("200 OK"); + } else { + response.setStatus("404 Not Found"); + } + + Log::Debug("Sending response: ", response); + uint32_t length; + uint8_t *encResp = response.encode(length); + rc = SocketIO::write(d_fd, encResp, length); + delete[] encResp; + if (0 != rc) { + Log::Error("socket write failed, ec=", ec); + } + + if (serverEventFunc != d_serverEventMap.end()) { + HttpServerEvent serverEvent(d_fd); + serverEventFunc->second(*request, serverEvent); + } + + Log::Debug("done"); +} + +} // namespace fwoop diff --git a/http/fwoop_httpconnhandler.g.cpp b/http/fwoop_httpconnhandler.g.cpp new file mode 100644 index 0000000..02c547d --- /dev/null +++ b/http/fwoop_httpconnhandler.g.cpp @@ -0,0 +1,12 @@ +#include + +#include + +TEST(HttpConnHandler, Constructor) +{ + // GIVEN + + // WHEN + + // THEN +} diff --git a/http/fwoop_httpconnhandler.h b/http/fwoop_httpconnhandler.h new file mode 100644 index 0000000..005be65 --- /dev/null +++ b/http/fwoop_httpconnhandler.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace fwoop { + +class HttpConnHandler { + private: + int d_fd; + const std::unordered_map d_routeMap; + const std::unordered_map d_serverEventMap; + + public: + HttpConnHandler(int fd, const std::unordered_map &routeMap, + const std::unordered_map &serverEventMap); + ~HttpConnHandler(); + HttpConnHandler(const HttpConnHandler &rhs) = delete; + HttpConnHandler operator=(const HttpConnHandler &rhs) = delete; + HttpConnHandler(HttpConnHandler &&rhs); + + void operator()(); +}; + +} // namespace fwoop diff --git a/http/fwoop_httpfunc.h b/http/fwoop_httpfunc.h new file mode 100644 index 0000000..bcd1cc1 --- /dev/null +++ b/http/fwoop_httpfunc.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +#include + +namespace fwoop { + +typedef std::function HttpHandlerFunc_t; +typedef std::function HttpServerEventHandlerFunc_t; + +} // namespace fwoop diff --git a/http/fwoop_httpserver.cpp b/http/fwoop_httpserver.cpp index f145f6f..41c753f 100644 --- a/http/fwoop_httpserver.cpp +++ b/http/fwoop_httpserver.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include #include @@ -24,7 +26,7 @@ namespace fwoop { HttpServer::HttpServer(int port, HttpVersion::Value version) - : d_port(port), d_serverFd(-1), d_version(version), d_isActive(false) + : d_port(port), d_serverFd(-1), d_version(version), d_isActive(false), d_handlerPool(5) { } @@ -61,7 +63,7 @@ int HttpServer::serve() const int maxQueued = 1; if (0 != listen(d_serverFd, maxQueued)) { - std::cerr << "failed to listen, errno" << errno << '\n'; + std::cerr << "failed to listen, errno=" << std::strerror(errno) << '\n'; return -1; } @@ -71,17 +73,23 @@ int HttpServer::serve() while (d_isActive) { int clientFd = accept4(d_serverFd, (struct sockaddr *)&clientAddr, (socklen_t *)&addrLen, SOCK_NONBLOCK); if (clientFd < 0) { - std::cerr << "failed to accept, errno" << errno << '\n'; + std::cerr << "failed to accept, errno" << std::strerror(errno) << '\n'; return -1; } switch (d_version) { case HttpVersion::Value::Http1_1: - handleHttp1Connection(clientFd); + { + HttpConnHandler handler(clientFd, d_routeMap, d_serverEventMap); + d_handlerPool.enqueue(std::move(handler)); break; + } case HttpVersion::Value::Http2: + { + // TODO use the thread pool handleHttp2Connection(clientFd); break; + } default: std::cerr << "unsupported HTTP version: " << d_version; return -1; diff --git a/http/fwoop_httpserver.h b/http/fwoop_httpserver.h index 9c5113e..ff79614 100644 --- a/http/fwoop_httpserver.h +++ b/http/fwoop_httpserver.h @@ -1,9 +1,12 @@ #pragma once +#include +#include #include #include #include #include +#include #include #include @@ -11,9 +14,6 @@ namespace fwoop { -typedef std::function HttpHandlerFunc_t; -typedef std::function HttpServerEventHandlerFunc_t; - class HttpServer { private: int d_port; @@ -22,6 +22,7 @@ class HttpServer { std::unordered_map d_routeMap; std::unordered_map d_serverEventMap; bool d_isActive; + ThreadPool d_handlerPool; int parsePayloadBody(uint8_t *buffer, unsigned bufferSize, unsigned int &bytesParsed) const; int handleHttp1Connection(int clientFd) const; @@ -39,6 +40,11 @@ class HttpServer { void addServerEventRoute(const std::string &route, HttpServerEventHandlerFunc_t func); }; -inline void HttpServer::stop() { d_isActive = false; } +inline void HttpServer::stop() +{ + d_isActive = false; + d_handlerPool.close(); + d_handlerPool.wait(); +} } // namespace fwoop