From f3ec62c79b2222fac055eba4cd412edf98ea5f4b Mon Sep 17 00:00:00 2001 From: LeonardoBizzoni Date: Fri, 9 Aug 2024 10:41:53 +0200 Subject: [PATCH] HTTP response parse --- src/error.h | 1 + src/http.h | 9 ++++-- src/main.cpp | 5 +++ src/request.h | 23 ++++++++------ src/response.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ src/response.h | 28 +++++++++++++++++ src/send.cpp | 50 ++++++++++++++++++++---------- 7 files changed, 166 insertions(+), 30 deletions(-) create mode 100644 src/response.cpp diff --git a/src/error.h b/src/error.h index 81d1cba..64c4009 100644 --- a/src/error.h +++ b/src/error.h @@ -6,5 +6,6 @@ namespace http { SocketConnection, DNSResolution, ServerNotFound, + InvalidRead, }; } diff --git a/src/http.h b/src/http.h index 8219975..8e1204c 100644 --- a/src/http.h +++ b/src/http.h @@ -8,7 +8,7 @@ #include #include -#include +#include #include "error.h" #include "method.h" @@ -16,10 +16,13 @@ #include "response.h" #define ERR(error) std::unexpected(error) -#define NEW_LINE "\r\n" +#define NEW_LINE std::string_view("\r\n") namespace http { std::expected sendreq(Method, const RequestOpts& req); - std::expected connect_to(const std::string& domain_name, const uint16_t port = 80); + std::expected connect_to(const std::string_view& domain_name, const uint16_t port = 80); + + std::string build_request(Method method, const RequestOpts &req); + std::expected read_raw_response(const int8_t socketfd); } // namespace http diff --git a/src/main.cpp b/src/main.cpp index 24f0f21..f3f5b69 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,11 @@ int main() { case http::Error::ServerNotFound: { std::cout << "Server not found" << std::endl; } break; + case http::Error::InvalidRead: { + std::cout << "Invalid read" << std::endl; + } break; } + } else { + std::cout << resp1.value() << std::endl; } } diff --git a/src/request.h b/src/request.h index 284ab95..25c3681 100644 --- a/src/request.h +++ b/src/request.h @@ -1,19 +1,22 @@ #pragma once -#include "http.h" +#include +#include namespace http { + struct http_version{ + uint8_t major = 1; + uint8_t minor = 1; + }; + struct RequestOpts { uint16_t port = 80; - std::string domain_name; - std::string host = domain_name; - std::string query = "/"; - std::string accept = "*/*"; - std::string body = ""; + std::string_view domain_name; + std::string_view host = domain_name; + std::string_view query = "/"; + std::string_view accept = "*/*"; + std::string_view body = ""; - struct { - uint8_t major = 1; - uint8_t minor = 1; - } http_version; + http_version version; }; } // namespace http diff --git a/src/response.cpp b/src/response.cpp new file mode 100644 index 0000000..ab8edc4 --- /dev/null +++ b/src/response.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include "http.h" + +namespace http { + std::expected Response::build(std::string_view raw_response) noexcept { + Response resp; + + auto lines_view = raw_response | std::views::split(NEW_LINE); + auto lines_view_iter = lines_view.begin(); + + auto words = *lines_view_iter++ | std::views::split(' '); + auto word_iter = words.begin(); + std::string_view version(*word_iter); + auto number = version.substr(version.find_first_of('/') + 1); + auto major = number.substr(0, number.find_first_of('.')); + auto minor = number.substr(number.find_first_of('.') + 1); + + try { + resp.version = {.major = (uint8_t)std::stoi(major.data()), + .minor = (uint8_t)std::stoi(minor.data())}; + } catch (...) { + resp.version = {.major = 0, .minor = 0}; + } + + try { + std::string_view status(*++word_iter); + resp.status = status_map[std::stoi(status.data())]; + } catch (...) { + resp.status = Status::PARSE_ERROR; + } + + for (std::string_view line; lines_view_iter != lines_view.end() && + !(line = std::string_view(*lines_view_iter)).empty(); + ++lines_view_iter) { + auto words = line | std::views::split(std::string_view{": "}); + auto it = words.begin(); + std::string field_name((*it).begin(), (*it).end()); + ++it; + + resp.fields[field_name] = std::string((*it).begin(), (*it).end()); + } + + while (++lines_view_iter != lines_view.end()) { + resp.body += std::string((*lines_view_iter).begin(), (*lines_view_iter).end()) + "\n"; + } + + return resp; + } +} // namespace http + +std::ostream &operator<<(std::ostream &os, const http::Status &status) { + switch (status) { + case http::Status::PARSE_ERROR: + return os << "parse error"; + case http::Status::OK: + return os << "ok"; + case http::Status::NOT_FOUND: + return os << "not found"; + default: + return os; + } +} + +std::ostream &operator<<(std::ostream &os, const http::Response &resp) { + os << "HTTP version: " << (int)resp.version.major << "." << (int)resp.version.minor << "\n"; + os << "Status: " << resp.status; + + for (const auto entry : resp.fields) { + os << "\n" << entry.first << ": " << entry.second; + } + + if (!resp.body.empty()) { + os << "\nBody: " << resp.body; + } + + return os; +} diff --git a/src/response.h b/src/response.h index 55e8c78..1517932 100644 --- a/src/response.h +++ b/src/response.h @@ -1,12 +1,40 @@ #pragma once +#include +#include +#include +#include +#include + +#include "error.h" +#include "request.h" + namespace http { enum class Status { + PARSE_ERROR, OK = 200, NOT_FOUND = 404, }; + static std::unordered_map status_map = { + {200, Status::OK}, + {404, Status::NOT_FOUND}, + }; + struct Response { + http_version version; Status status; + std::string body = ""; + std::unordered_map fields; + + public: + static std::expected build(std::string_view raw_response) noexcept; + ~Response() {} + + private: + Response() {} }; } // namespace http + +std::ostream &operator<<(std::ostream &os, const http::Status &status); +std::ostream &operator<<(std::ostream &os, const http::Response &resp); diff --git a/src/send.cpp b/src/send.cpp index 934cf0d..88f239a 100644 --- a/src/send.cpp +++ b/src/send.cpp @@ -1,25 +1,30 @@ +#include +#include #include #include #include +#include #include "http.h" +#include "request.h" #define BUFFSIZE 1024 static std::unordered_map ip_map; namespace http { - std::expected connect_to(const std::string &domain_name, const uint16_t port) { + std::expected connect_to(const std::string_view &domain_name, + const uint16_t port) { struct addrinfo hints = {0}, *addr_list; hints.ai_family = AF_UNSPEC; // Either IPv4 or IPv6 hints.ai_socktype = SOCK_STREAM; // TCP only - if (getaddrinfo(domain_name.c_str(), std::to_string(port).c_str(), &hints, &addr_list)) { + if (getaddrinfo(domain_name.data(), std::to_string(port).c_str(), &hints, &addr_list)) { return ERR(Error::DNSResolution); } int8_t remote_socketfd; - if (auto it = ip_map.find(domain_name); it != ip_map.end()) { + if (auto it = ip_map.find(domain_name.data()); it != ip_map.end()) { remote_socketfd = socket(it->second.ai_family, it->second.ai_socktype, it->second.ai_protocol); if (remote_socketfd < 0) { @@ -59,7 +64,7 @@ namespace http { return ERR(Error::ServerNotFound); } - ip_map[domain_name] = *remote; + ip_map[domain_name.data()] = *remote; freeaddrinfo(addr_list); } @@ -73,9 +78,22 @@ namespace http { return ERR(maybe_socketfd.error()); } + std::string msg = build_request(method, req); + send((int)maybe_socketfd.value(), msg.c_str(), msg.size(), 0); + + auto maybe_response = read_raw_response(maybe_socketfd.value()); + if (!maybe_response.has_value()) { + return ERR(maybe_response.error()); + } + + close(maybe_socketfd.value()); + return Response::build(maybe_response.value()); + } + + std::string build_request(Method method, const RequestOpts &req) { std::stringstream ss; - ss << method << " " << req.query << " HTTP/" << (int)req.http_version.major << "." - << (int)req.http_version.minor << NEW_LINE; + ss << method << " " << req.query << " HTTP/" << (int)req.version.major << "." + << (int)req.version.minor << NEW_LINE; ss << "Host: " << req.host << NEW_LINE; ss << "Accept: " << req.accept << NEW_LINE; @@ -85,25 +103,23 @@ namespace http { ss << NEW_LINE; } - std::string msg = ss.str(); - std::cout << "Request:\n===================\n" << msg << "\n===================\n" << std::endl; - send((int)maybe_socketfd.value(), msg.c_str(), msg.size(), 0); + return ss.str(); + } - msg = ""; + std::expected read_raw_response(const int8_t socketfd) { + std::stringstream ss; char buffer[BUFFSIZE] = ""; ssize_t bytes_read = 0; - while ((bytes_read = read((int)maybe_socketfd.value(), buffer, BUFFSIZE - 1)) > 0) { + + while ((bytes_read = read((int)socketfd, buffer, BUFFSIZE - 1)) > 0) { if (bytes_read == -1) { - std::cerr << "\t\tError while reading!" << std::endl; + return ERR(Error::InvalidRead); } buffer[bytes_read] = '\0'; - msg += buffer; + ss << buffer; } - std::cout << "Response:\n===================\n" << msg << "\n===================" << std::endl; - - close(maybe_socketfd.value()); - return Response(); + return ss.str(); } } // namespace http -- 2.52.0