diff --git a/implementations/Crafter.Network-ClientHTTP.cpp b/implementations/Crafter.Network-ClientHTTP.cpp index b2bbe8c..b6218e2 100644 --- a/implementations/Crafter.Network-ClientHTTP.cpp +++ b/implementations/Crafter.Network-ClientHTTP.cpp @@ -37,7 +37,7 @@ import std; using namespace Crafter; -ClientHTTP::ClientHTTP(const char* host, std::uint16_t port): client(host, port) { +ClientHTTP::ClientHTTP(const char* host, std::uint16_t port): host(host), port(port), client(host, port) { } @@ -45,14 +45,21 @@ ClientHTTP::ClientHTTP(std::string host, std::uint16_t port): ClientHTTP(host.c_ } -HTTPResponse ClientHTTP::Send(const char* request, std::uint32_t length) const { +HTTPResponse ClientHTTP::Send(const char* request, std::uint32_t length) { client.Send(request, length); std::vector buffer; HTTPResponse response; std::uint32_t i = 0; std::uint32_t statusStart = 0; while(true) { - buffer = client.RecieveSync(); + try { + buffer = client.RecieveSync(); + } catch(const SocketClosedException& e) { + client = ClientTCP(host.c_str(), port); + client.Send(request, length); + buffer = client.RecieveSync(); + } + for(; i < buffer.size(); i++) { if(buffer[i] == ' ') { statusStart = i; @@ -185,6 +192,6 @@ HTTPResponse ClientHTTP::Send(const char* request, std::uint32_t length) const { } return response; } -HTTPResponse ClientHTTP::Send(std::string request) const { +HTTPResponse ClientHTTP::Send(std::string request) { return Send(request.c_str(), request.size()); } diff --git a/implementations/Crafter.Network-ClientTCP.cpp b/implementations/Crafter.Network-ClientTCP.cpp index 0ee81dd..3c1e813 100755 --- a/implementations/Crafter.Network-ClientTCP.cpp +++ b/implementations/Crafter.Network-ClientTCP.cpp @@ -32,6 +32,7 @@ module; #include #include #include +#include module Crafter.Network:ClientTCP_impl; import :ClientTCP; @@ -72,7 +73,16 @@ ClientTCP::ClientTCP(std::string hostName, std::uint16_t port): ClientTCP(hostNa ClientTCP::~ClientTCP() { + if(socketid != 1) { + shutdown(socketid, SHUT_RDWR); + close(socketid); + } +} + +void ClientTCP::Stop() { + shutdown(socketid, SHUT_RDWR); close(socketid); + socketid = -1; } void ClientTCP::Send(const void* buffer, std::uint32_t size) const { @@ -82,8 +92,10 @@ void ClientTCP::Send(const void* buffer, std::uint32_t size) const { std::vector ClientTCP::RecieveSync(std::uint32_t bufferSize) const { std::vector totalBuffer(bufferSize); int read = recv(socketid, totalBuffer.data(), bufferSize, 0); - if(read < bufferSize){ - totalBuffer.resize(read); + if(read < 0) { + throw std::runtime_error(std::strerror(errno)); + } else if(read == 0) { + throw SocketClosedException(); } return totalBuffer; } @@ -94,22 +106,49 @@ int ClientTCP::RecieveSync(std::uint32_t bufferSize, void* buffer) const { std::vector ClientTCP::RecieveSync() const { int count; ioctl(socketid, FIONREAD, &count); + if(count == 0){ + count = 1024; + } std::vector buffer(count); - recv(socketid, buffer.data(), count, 0); + int read = recv(socketid, buffer.data(), count, 0); + if(read < 0) { + throw std::runtime_error(std::strerror(errno)); + } else if(read == 0) { + throw SocketClosedException(); + } else if(count != read) { + buffer.resize(read); + } return buffer; } std::vector ClientTCP::RecieveUntilCloseSync() const { int count; ioctl(socketid, FIONREAD, &count); + if(count == 0){ + count = 1024; + } std::vector buffer(count); - recv(socketid, buffer.data(), count, 0); + int read = recv(socketid, buffer.data(), count, 0); + if(read < 0) { + throw std::runtime_error(std::strerror(errno)); + } else if(read == 0) { + throw SocketClosedException(); + } + while(true) { ioctl(socketid, FIONREAD, &count); + if(count == 0){ + count = 1024; + } unsigned int oldSize = buffer.size(); buffer.resize(buffer.size()+count); - if(recv(socketid, buffer.data()+oldSize, count, 0) == -1) { - break; + int read = recv(socketid, buffer.data()+oldSize, count, 0); + if(read < 0) { + throw std::runtime_error(std::strerror(errno)); + } else if(read == 0) { + return buffer; + } else if(count != read) { + buffer.resize(read); } } return buffer; @@ -120,11 +159,12 @@ std::vector ClientTCP::RecieveUntilFullSync(std::uint32_t bufferSize) cons int read = 0; while(read < bufferSize) { int newRead = recv(socketid, buffer.data()+read, bufferSize-read, 0); - if(newRead == -1) { - break; - } else{ - read+=newRead; + if(read < 0) { + throw std::runtime_error(std::strerror(errno)); + } else if(read == 0) { + throw SocketClosedException(); } + read+=newRead; } buffer.resize(read); return buffer; diff --git a/implementations/Crafter.Network-ListenerHTTP.cpp b/implementations/Crafter.Network-ListenerHTTP.cpp index 3ad163e..27fc294 100644 --- a/implementations/Crafter.Network-ListenerHTTP.cpp +++ b/implementations/Crafter.Network-ListenerHTTP.cpp @@ -68,150 +68,170 @@ ListenerHTTP::ListenerHTTP(std::uint16_t port, std::unordered_mapclient.Stop(); + client->thread.join(); + delete client; + } } -void ListenerHTTP::ListenSyncSync() const { +void ListenerHTTP::Listen() { while(running) { sockaddr_in newSockAddr; socklen_t newSockAddrSize = sizeof(newSockAddr); int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); + if(!running) { + return; + } if (client > 0) { - CallRoute(client); + clients.push_back(new ListenerHTTPClient(this, client)); } else { std::cerr << "Error accepting request from client!" << std::endl; } + std::erase_if(clients, [](ListenerHTTPClient* client) { + if (client->disconnected.load()) { + client->thread.join(); + delete client; + return true; + } + return false; + }); } } -void ListenerHTTP::ListenSyncAsync() const { - while(running) { - sockaddr_in newSockAddr; - socklen_t newSockAddrSize = sizeof(newSockAddr); - int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); - if (client > 0) { - ThreadPool::Enqueue([this, client]() {CallRoute(client); }); - } - else { - std::cerr << "Error accepting request from client!" << std::endl; - } - } +ListenerHTTPClient::ListenerHTTPClient(ListenerHTTP* server, int s) : server(server), client(s), thread(&ListenerHTTPClient::ListenRoutes, this), disconnected(false) { + } -void ListenerHTTP::ListenAsyncSync() const { - ThreadPool::Enqueue([this]() { - while(running) { - sockaddr_in newSockAddr; - socklen_t newSockAddrSize = sizeof(newSockAddr); - int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); - if (client > 0) { - CallRoute(client); - } - else if(running) { - std::cerr << "Error accepting request from client: "<< strerror(errno) << " (errno: " << errno << ")"<< std::endl; - } - } - }); -} - -void ListenerHTTP::ListenAsyncAsync() const { - ThreadPool::Enqueue([this]() { - while(running) { - sockaddr_in newSockAddr; - socklen_t newSockAddrSize = sizeof(newSockAddr); - int client = accept(s, (sockaddr*)&newSockAddr, &newSockAddrSize); - if (client > 0) { - ThreadPool::Enqueue([this, client]() {CallRoute(client); }); - } - else { - std::cerr << "Error accepting request from client!" << std::endl; - } - } - }); -} - -void ListenerHTTP::CallRoute(int clientid) const { - ClientTCP client(clientid); - std::vector buffer = client.RecieveSync(1024); - HTTPRequest request; - std::string route; - std::uint32_t i = 0; - std::uint32_t routeStart = 0; - for(; i < 1024; i++) { - if(buffer[i] == ' ') { - request.method.assign(buffer.data(), i); - break; - } - } - for(; i < 1024; i++) { - if(buffer[i] == '/') { - routeStart = i; - break; - } - } - for(; i < 1024; i++) { - if(buffer[i] == ' ') { - route.assign(buffer.data()+routeStart, i-routeStart); - break; - } - } - for(; i < 1024; i++) { - if(buffer[i] == '\r' && buffer[i+1] == '\n') { - break; - } - } - i+=2; - while(i < 1024) { - std::uint32_t headerStart = i; - std::string headerName; - for(; i < 1024-i; i++) { - if(buffer[i] == ':') { - headerName.assign(buffer.data()+headerStart, i-headerStart); - i++; - break; - } - } - headerStart = i; - std::string headerValue; - for(; i < 1024; i++) { - if(buffer[i] == '\r' && buffer[i+1] == '\n') { - headerValue.assign(buffer.data()+headerStart, i-headerStart); - request.headers.insert({headerName, headerValue}); - if(buffer[i+2] == '\r'){ - goto headersComplete; - } else{ +void ListenerHTTPClient::ListenRoutes() { + try { + while(true) { + std::vector buffer; + HTTPRequest request; + std::string route; + std::uint32_t i = 0; + std::uint32_t routeStart = 0; + while(true) { + while(true) { + buffer = client.RecieveSync(); + std::string str(buffer.begin(), buffer.end()); + for(; i < buffer.size(); i++) { + if(buffer[i] == ' ') { + request.method.assign(buffer.data(), i); + break; + } + } + for(; i < buffer.size(); i++) { + if(buffer[i] == '/') { + routeStart = i; + break; + } + } + for(; i < buffer.size(); i++) { + if(buffer[i] == ' ') { + route.assign(buffer.data()+routeStart, i-routeStart); + break; + } + } + for(; i < buffer.size(); i++) { + if(buffer[i] == '\r' && buffer[i+1] == '\n') { + break; + } + } i+=2; - break; + while(i < buffer.size()) { + std::uint32_t headerStart = i; + std::string headerName; + for(; i < buffer.size()-i; i++) { + if(buffer[i] == ':') { + headerName.assign(buffer.data()+headerStart, i-headerStart); + i++; + break; + } + } + headerStart = i; + std::string headerValue; + for(; i < buffer.size(); i++) { + if(buffer[i] == '\r' && buffer[i+1] == '\n') { + headerValue.assign(buffer.data()+headerStart, i-headerStart); + request.headers.insert({headerName, headerValue}); + if(buffer[i+2] == '\r'){ + goto headersComplete; + } else{ + i+=2; + break; + } + } + } + } + i = 0; + } + headersComplete:; + i+=4; + std::unordered_map::iterator it = request.headers.find("Content-Length"); + if(it != request.headers.end()) { + const int lenght = std::stoi(it->second); + request.body.resize(lenght, 0); + if(lenght > 0 ){ + std::int_fast32_t remaining = lenght+i-buffer.size(); + if(remaining < 0) { + std::memcpy(&request.body[0], buffer.data()+i, lenght); + std::string response = server->routes.at(route)(request); + client.Send(&response[0], response.size()); + i+=lenght; + } else if(remaining == 0){ + std::memcpy(&request.body[0], buffer.data()+i, lenght); + std::string response = server->routes.at(route)(request); + client.Send(&response[0], response.size()); + break; + } else { + std::memcpy(&request.body[0], buffer.data()+i, lenght-remaining); + std::vector bodyBuffer = client.RecieveUntilFullSync(remaining); + std::memcpy(&request.body[remaining], bodyBuffer.data(), remaining); + std::string response = server->routes.at(route)(request); + client.Send(&response[0], response.size()); + break; + } + } else { + break; + } + } else { + std::string response = server->routes.at(route)(request); + client.Send(&response[0], response.size()); + if(i == buffer.size()) { + break; + } } } } + } catch(SocketClosedException& e) { + disconnected.store(true); } - headersComplete:; - i+=4; - std::unordered_map::iterator it = request.headers.find("Content-Length"); - if(it != request.headers.end()) - { - const int lenght = std::stoi(it->second); - const int remaining = 1024-lenght-i; - request.body.resize(lenght, 0); - if(remaining > 0){ - std::memcpy(&request.body[0], buffer.data()+i, lenght); - } - if(remaining < lenght){ - std::vector bodyBuffer = client.RecieveSync(lenght-remaining); - std::memcpy(&request.body[remaining], bodyBuffer.data(), lenght-remaining); - } - } - std::string response = routes.at(route)(request); - client.Send(&response[0], response.size()); } -ListenerHTTP::~ListenerHTTP() { - if(s != -1) { - close(s); + +ListenerAsyncHTTP::ListenerAsyncHTTP(std::uint16_t port, std::unordered_map> routes): listener(port, routes), thread(&ListenerHTTP::Listen, &listener) { + +} + +ListenerAsyncHTTP::~ListenerAsyncHTTP() { + if(listener.s != -1) { + Stop(); } } + +void ListenerAsyncHTTP::Stop() { + listener.Stop(); + thread.join(); +} \ No newline at end of file diff --git a/interfaces/Crafter.Network-ClientHTTP.cppm b/interfaces/Crafter.Network-ClientHTTP.cppm index 32e89cd..e549723 100644 --- a/interfaces/Crafter.Network-ClientHTTP.cppm +++ b/interfaces/Crafter.Network-ClientHTTP.cppm @@ -26,10 +26,12 @@ import :HTTP; namespace Crafter { export class ClientHTTP { public: + std::string host; + std::uint16_t port; ClientHTTP(const char* host, std::uint16_t port); ClientHTTP(std::string host, std::uint16_t port); - HTTPResponse Send(const char* request, std::uint32_t length) const; - HTTPResponse Send(std::string request) const; + HTTPResponse Send(const char* request, std::uint32_t length); + HTTPResponse Send(std::string request); private: ClientTCP client; }; diff --git a/interfaces/Crafter.Network-ClientTCP.cppm b/interfaces/Crafter.Network-ClientTCP.cppm index 6e7a4e0..e36a54b 100755 --- a/interfaces/Crafter.Network-ClientTCP.cppm +++ b/interfaces/Crafter.Network-ClientTCP.cppm @@ -22,12 +22,21 @@ export module Crafter.Network:ClientTCP; import std; namespace Crafter { + export class SocketClosedException : public std::exception { + public: + const char* what() const noexcept override { + return "Socket closed"; + } +}; + export class ClientTCP { public: + int socketid; ClientTCP(int socket); ClientTCP(const char* host, std::uint16_t port); ClientTCP(std::string host, std::uint16_t port); ~ClientTCP(); + void Stop(); void Send(const void* buffer, std::uint32_t size) const; std::vector RecieveSync() const; std::vector RecieveUntilCloseSync() const; @@ -39,7 +48,5 @@ namespace Crafter { void RecieveAsync(std::uint32_t bufferSize, std::function)> recieveCallback) const; void RecieveUntilFullAsync(std::uint32_t bufferSize, std::function)> recieveCallback) const; void RecieveAsync(std::uint32_t bufferSize, std::function recieveCallback, char* buffer) const; - private: - int socketid; }; } \ No newline at end of file diff --git a/interfaces/Crafter.Network-HTTP.cppm b/interfaces/Crafter.Network-HTTP.cppm index 5fcf41c..b785d32 100644 --- a/interfaces/Crafter.Network-HTTP.cppm +++ b/interfaces/Crafter.Network-HTTP.cppm @@ -35,7 +35,7 @@ namespace Crafter { }; export constexpr std::string CreateResponseHTTP(std::string status) { - return std::format("HTTP/1.1 {}\r\nConnection: keep-alive\r\n\r\n", status); + return std::format("HTTP/1.1 {}\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n", status); } export constexpr std::string CreateResponseHTTP(std::string status, std::unordered_map headers) { @@ -43,7 +43,7 @@ namespace Crafter { for (auto const& [key, val] : headers) { headersString+=std::format("{}: {}\r\n", key, val); } - return std::format("HTTP/1.1 {}\r\nConnection: keep-alive\r\n{}\r\n", status, headersString); + return std::format("HTTP/1.1 {}\r\nConnection: keep-alive\r\nContent-Length: 0\r\n{}\r\n", status, headersString); } export constexpr std::string CreateResponseHTTP(std::string status, std::string body) { diff --git a/interfaces/Crafter.Network-ListenerHTTP.cppm b/interfaces/Crafter.Network-ListenerHTTP.cppm index a95822e..a59cac4 100644 --- a/interfaces/Crafter.Network-ListenerHTTP.cppm +++ b/interfaces/Crafter.Network-ListenerHTTP.cppm @@ -21,21 +21,38 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA export module Crafter.Network:ListenerHTTP; import std; import :HTTP; +import :ClientTCP; namespace Crafter { + export class ListenerHTTP; + class ListenerHTTPClient { + public: + std::atomic disconnected; + ClientTCP client; + std::thread thread; + ListenerHTTP* server; + ListenerHTTPClient(ListenerHTTP* server, int s); + void ListenRoutes(); + }; + export class ListenerHTTP { public: + int s; + std::vector clients; bool running = true; - std::unordered_map> routes; + const std::unordered_map> routes; ListenerHTTP(std::uint16_t port, std::unordered_map> routes); ~ListenerHTTP(); + void Listen(); + void Stop(); + }; + + export class ListenerAsyncHTTP { + public: + ListenerHTTP listener; + std::thread thread; + ListenerAsyncHTTP(std::uint16_t port, std::unordered_map> routes); + ~ListenerAsyncHTTP(); void Stop(); - void ListenSyncSync() const; - void ListenSyncAsync() const; - void ListenAsyncSync() const; - void ListenAsyncAsync() const; - private: - void CallRoute(int client) const; - int s; }; } diff --git a/project.json b/project.json index b598976..37a2743 100644 --- a/project.json +++ b/project.json @@ -74,6 +74,16 @@ "configuration":"lib-shared" } ] + }, + { + "name": "should-send-recieve-keepalive-http", + "implementations": ["tests/ShouldSendRecieveKeepaliveHTTP"], + "dependencies": [ + { + "path":"./project.json", + "configuration":"lib-shared" + } + ] } ] } diff --git a/tests/ShouldRecieveHTTP.cpp b/tests/ShouldRecieveHTTP.cpp index 5bab4b8..401d929 100644 --- a/tests/ShouldRecieveHTTP.cpp +++ b/tests/ShouldRecieveHTTP.cpp @@ -18,7 +18,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include -import Crafter.Thread; import Crafter.Network; import std; using namespace Crafter; @@ -26,27 +25,19 @@ using namespace Crafter; extern "C" { std::string* RunTest() { bool success = false; - ThreadPool::Start(2); - ListenerHTTP listener(8080, {{"/", [&](const HTTPRequest& request) { + ListenerAsyncHTTP listener(8080, {{"/", [&](const HTTPRequest& request) { success = true; return CreateResponseHTTP("200 OK", "Hello World!"); }}}); try { - listener.ListenAsyncSync(); system("curl http://localhost:8080 > /dev/null 2>&1"); - + std::this_thread::sleep_for(std::chrono::seconds(1)); if (success) { - listener.Stop(); - ThreadPool::Stop(); return nullptr; } else { - listener.Stop(); - ThreadPool::Stop(); return new std::string("Did not receive"); } } catch(std::exception& e) { - listener.Stop(); - ThreadPool::Stop(); return new std::string(e.what()); } } diff --git a/tests/ShouldSendHTTP.cpp b/tests/ShouldSendHTTP.cpp index 1395fb8..59810b4 100644 --- a/tests/ShouldSendHTTP.cpp +++ b/tests/ShouldSendHTTP.cpp @@ -16,7 +16,6 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -import Crafter.Thread; import Crafter.Network; import std; using namespace Crafter; diff --git a/tests/ShouldSendRecieveHTTP.cpp b/tests/ShouldSendRecieveHTTP.cpp index 699f0f9..29ecaba 100644 --- a/tests/ShouldSendRecieveHTTP.cpp +++ b/tests/ShouldSendRecieveHTTP.cpp @@ -18,7 +18,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include -import Crafter.Thread; import Crafter.Network; import std; using namespace Crafter; @@ -26,26 +25,18 @@ using namespace Crafter; extern "C" { std::string* RunTest() { bool success = false; - ThreadPool::Start(2); - ListenerHTTP listener(8080, {{"/", [&](const HTTPRequest& request) { + ListenerAsyncHTTP listener(8080, {{"/", [&](const HTTPRequest& request) { return CreateResponseHTTP("200 OK", "Hello World!"); }}}); try { - listener.ListenAsyncSync(); ClientHTTP client("localhost", 8080); HTTPResponse response = client.Send(CreateRequestHTTP("GET", "/", "localhost")); if(response.status == "200 OK" && response.body == "Hello World!") { - listener.Stop(); - ThreadPool::Stop(); return nullptr; } else { - listener.Stop(); - ThreadPool::Stop(); return new std::string(response.status +response.body); } } catch(std::exception& e) { - listener.Stop(); - ThreadPool::Stop(); return new std::string(e.what()); } } diff --git a/tests/ShouldSendRecieveKeepaliveHTTP.cpp b/tests/ShouldSendRecieveKeepaliveHTTP.cpp new file mode 100644 index 0000000..9d6edd3 --- /dev/null +++ b/tests/ShouldSendRecieveKeepaliveHTTP.cpp @@ -0,0 +1,49 @@ +/* +Crafter® Build +Copyright (C) 2025 Catcrafts® +Catcrafts.net + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 3.0 as published by the Free Software Foundation; + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include +#include +import Crafter.Network; +import std; +using namespace Crafter; + +extern "C" { + std::string* RunTest() { + ListenerAsyncHTTP listener(8080, {{"/", [&](const HTTPRequest& request) { + return CreateResponseHTTP("200 OK", "Hello World!"); + }}}); + try { + ClientHTTP client("localhost", 8080); + HTTPResponse response = client.Send(CreateRequestHTTP("GET", "/", "localhost")); + std::this_thread::sleep_for(std::chrono::seconds(1)); + if(response.status == "200 OK" && response.body == "Hello World!") { + response = client.Send(CreateRequestHTTP("GET", "/", "localhost")); + std::this_thread::sleep_for(std::chrono::seconds(1)); + if(response.status == "200 OK" && response.body == "Hello World!") { + return nullptr; + } else { + return new std::string(response.status +response.body); + } + } else { + return new std::string(response.status +response.body); + } + } catch(std::exception& e) { + return new std::string(e.what()); + } + } +} \ No newline at end of file