#include "test_server.hpp" #include #include #include #include #include #include #include #include #include namespace cepis::testing { namespace { std::string reason_phrase(int status) { switch (status) { case 200: return "OK"; case 400: return "Bad Request"; case 404: return "Not Found"; case 429: return "Too Many Requests"; case 500: return "Internal Server Error"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Timeout"; default: return "Status"; } } bool write_all(int fd, const std::string& data) { std::size_t sent = 0; while (sent < data.size()) { const ssize_t written = ::send(fd, data.data() + sent, data.size() - sent, MSG_NOSIGNAL); if (written <= 0) { return false; } sent += static_cast(written); } return true; } } // namespace Server::Server() { listen_fd_ = ::socket(AF_INET, SOCK_STREAM, 0); if (listen_fd_ < 0) { throw std::runtime_error("socket: " + std::string(std::strerror(errno))); } const int enable = 1; ::setsockopt(listen_fd_, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); sockaddr_in address{}; address.sin_family = AF_INET; address.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK); address.sin_port = 0; if (::bind(listen_fd_, reinterpret_cast(&address), sizeof(address)) != 0) { ::close(listen_fd_); throw std::runtime_error("bind: " + std::string(std::strerror(errno))); } if (::listen(listen_fd_, 32) != 0) { ::close(listen_fd_); throw std::runtime_error("listen: " + std::string(std::strerror(errno))); } sockaddr_in bound{}; socklen_t length = sizeof(bound); if (::getsockname(listen_fd_, reinterpret_cast(&bound), &length) != 0) { ::close(listen_fd_); throw std::runtime_error("getsockname: " + std::string(std::strerror(errno))); } port_ = ::ntohs(bound.sin_port); thread_ = std::thread([this] { serve(); }); } Server::~Server() { stopping_.store(true); if (thread_.joinable()) { thread_.join(); } if (listen_fd_ >= 0) { ::close(listen_fd_); } } std::string Server::url() const { return "http://127.0.0.1:" + std::to_string(port_); } void Server::respond(std::string path, Reply reply) { respond(std::move(path), std::vector{std::move(reply)}); } void Server::respond(std::string path, std::vector replies) { const std::scoped_lock guard(mutex_); routes_[std::move(path)] = std::move(replies); } void Server::handle(std::function handler) { const std::scoped_lock guard(mutex_); handler_ = std::move(handler); } int Server::count(std::string_view prefix) const { const std::scoped_lock guard(mutex_); int total = 0; for (const std::string& call : calls_) { if (call.rfind(prefix, 0) == 0) { ++total; } } return total; } std::vector Server::calls() const { const std::scoped_lock guard(mutex_); return calls_; } Reply Server::next(const std::string& path) { std::function handler; { const std::scoped_lock guard(mutex_); calls_.push_back(path); handler = handler_; if (!handler) { const auto found = routes_.find(path); if (found == routes_.end()) { return Reply{404, R"({"erro":"cep_nao_encontrado"})", {}}; } std::vector& queue = found->second; if (queue.size() > 1) { Reply reply = queue.front(); queue.erase(queue.begin()); return reply; } return queue.front(); } } return handler(path); } void Server::serve() { while (!stopping_.load()) { pollfd waiting{listen_fd_, POLLIN, 0}; const int ready = ::poll(&waiting, 1, 10); if (ready <= 0) { continue; } const int client = ::accept(listen_fd_, nullptr, nullptr); if (client < 0) { continue; } std::string request; char buffer[4096]; while (request.find("\r\n\r\n") == std::string::npos) { const ssize_t read_bytes = ::recv(client, buffer, sizeof(buffer), 0); if (read_bytes <= 0) { break; } request.append(buffer, static_cast(read_bytes)); } const std::size_t first_space = request.find(' '); const std::size_t second_space = first_space == std::string::npos ? std::string::npos : request.find(' ', first_space + 1); if (second_space == std::string::npos) { ::close(client); continue; } const std::string path = request.substr(first_space + 1, second_space - first_space - 1); const Reply reply = next(path); std::string out = "HTTP/1.1 "; out += std::to_string(reply.status); out += ' '; out += reason_phrase(reply.status); out += "\r\n"; for (const auto& [name, value] : reply.headers) { out += name; out += ": "; out += value; out += "\r\n"; } out += "Content-Length: "; out += std::to_string(reply.body.size()); out += "\r\n"; out += "Connection: close\r\n\r\n"; out += reply.body; write_all(client, out); ::shutdown(client, SHUT_WR); ::close(client); } } } // namespace cepis::testing