]> git.leonardobizzoni.com Git - http-lib/commitdiff
HTTP response parse
authorLeonardoBizzoni <leo2002714@gmail.com>
Fri, 9 Aug 2024 08:41:53 +0000 (10:41 +0200)
committerLeonardoBizzoni <leo2002714@gmail.com>
Fri, 9 Aug 2024 08:41:53 +0000 (10:41 +0200)
src/error.h
src/http.h
src/main.cpp
src/request.h
src/response.cpp [new file with mode: 0644]
src/response.h
src/send.cpp

index 81d1cba56b91ee051e54f3d86acf86c3752eed8c..64c4009d36c056608cca14eea8d105b040b9f5bf 100644 (file)
@@ -6,5 +6,6 @@ namespace http {
     SocketConnection,
     DNSResolution,
     ServerNotFound,
+    InvalidRead,
   };
 }
index 8219975aab91d45ec6f0745d59113939758fbdcd..8e1204c8937b2aba747480fdc2d9600c5535bd7f 100644 (file)
@@ -8,7 +8,7 @@
 
 #include <cstdint>
 #include <expected>
-#include <string>
+#include <string_view>
 
 #include "error.h"
 #include "method.h"
 #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<Response, Error> sendreq(Method, const RequestOpts& req);
 
-  std::expected<int8_t, Error> connect_to(const std::string& domain_name, const uint16_t port = 80);
+  std::expected<int8_t, Error> connect_to(const std::string_view& domain_name, const uint16_t port = 80);
+
+  std::string build_request(Method method, const RequestOpts &req);
+  std::expected<std::string, Error> read_raw_response(const int8_t socketfd);
 }  // namespace http
index 24f0f2113de6be3201957471afa7a1254b9b62cc..f3f5b6902906c5da93e01fdcdcc77c17351ab9ea 100644 (file)
@@ -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;
   }
 }
index 284ab9553584efc21917563ab9ce7b3e2a8721be..25c368119ffa300830436a102c67282d560c6656 100644 (file)
@@ -1,19 +1,22 @@
 #pragma once
 
-#include "http.h"
+#include <cstdint>
+#include <string_view>
 
 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 (file)
index 0000000..ab8edc4
--- /dev/null
@@ -0,0 +1,80 @@
+#include <iostream>
+#include <ranges>
+#include <string_view>
+
+#include "http.h"
+
+namespace http {
+  std::expected<Response, Error> 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;
+}
index 55e8c785771b14a32473f0d97ff1245eebcaa108..1517932ae6e0c1c3995c8703789af4dc3e30eca3 100644 (file)
@@ -1,12 +1,40 @@
 #pragma once
 
+#include <cstdint>
+#include <expected>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+#include "error.h"
+#include "request.h"
+
 namespace http {
   enum class Status {
+    PARSE_ERROR,
     OK = 200,
     NOT_FOUND = 404,
   };
 
+  static std::unordered_map<uint16_t, Status> status_map = {
+      {200, Status::OK},
+      {404, Status::NOT_FOUND},
+  };
+
   struct Response {
+    http_version version;
     Status status;
+    std::string body = "";
+    std::unordered_map<std::string, std::string> fields;
+
+  public:
+    static std::expected<Response, Error> 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);
index 934cf0da865c337f1d2a9482dafc2aa871e9c219..88f239a59ce84a5a2ef5a4c949e431d36ec5ecb1 100644 (file)
@@ -1,25 +1,30 @@
+#include <algorithm>
+#include <cstdint>
 #include <iostream>
 #include <sstream>
 #include <unordered_map>
+#include <vector>
 
 #include "http.h"
+#include "request.h"
 
 #define BUFFSIZE 1024
 
 static std::unordered_map<std::string, struct addrinfo> ip_map;
 
 namespace http {
-  std::expected<int8_t, Error> connect_to(const std::string &domain_name, const uint16_t port) {
+  std::expected<int8_t, Error> 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<std::string, Error> 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