SocketConnection,
DNSResolution,
ServerNotFound,
+ InvalidRead,
};
}
#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
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;
}
}
#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
--- /dev/null
+#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;
+}
#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);
+#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) {
return ERR(Error::ServerNotFound);
}
- ip_map[domain_name] = *remote;
+ ip_map[domain_name.data()] = *remote;
freeaddrinfo(addr_list);
}
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;
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