From 81fc5488128c33aad1103620e523a4352b6301c6 Mon Sep 17 00:00:00 2001 From: LeonardoBizzoni Date: Tue, 6 Aug 2024 13:29:40 +0200 Subject: [PATCH 1/1] First commit: establish connection with remote given its domain name --- .clang-format | 22 +++++++++++++ .clangd | 2 ++ CMakeLists.txt | 54 ++++++++++++++++++++++++++++++++ shell.nix | 19 ++++++++++++ src/error.h | 10 ++++++ src/http.h | 24 +++++++++++++++ src/main.cpp | 25 +++++++++++++++ src/method.h | 16 ++++++++++ src/request.h | 12 ++++++++ src/response.h | 12 ++++++++ src/send.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 279 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 CMakeLists.txt create mode 100644 shell.nix create mode 100644 src/error.h create mode 100644 src/http.h create mode 100644 src/main.cpp create mode 100644 src/method.h create mode 100644 src/request.h create mode 100644 src/response.h create mode 100644 src/send.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..36f0dae --- /dev/null +++ b/.clang-format @@ -0,0 +1,22 @@ +BasedOnStyle: Google +IndentWidth: 2 +UseTab: Always +NamespaceIndentation: All +ColumnLimit: 100 +BreakBeforeBraces: Custom +AccessModifierOffset: -2 +BraceWrapping: + AfterNamespace: false + AfterClass: false + AfterControlStatement: false + AfterFunction: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +AllowShortLoopsOnASingleLine: true +PenaltyBreakBeforeFirstCallParameter: 1000 +PenaltyBreakComment: 300 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 1000 +PenaltyExcessCharacter: 1000 +PenaltyReturnTypeOnItsOwnLine: 1000 diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..165d95d --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-D__cpp_concepts=202002L] \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0abad88 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.29) +project(http CXX) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Debug") +endif() + +# Set output directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_ARCHITE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# Set output directories +set(OUTPUT_DIR "${PROJECT_SOURCE_DIR}/bin/${CMAKE_BUILD_TYPE}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + +file(GLOB SRC "src/*.cpp" "src/*.hpp" "src/*.h") +add_executable(${PROJECT_NAME} ${SRC}) + +target_sources(${PROJECT_NAME} PRIVATE ${PLATFORM_SRC}) +target_compile_definitions(${PROJECT_NAME} PRIVATE ROOTDIR="${CMAKE_SOURCE_DIR}") + +# Set output directories +set_target_properties(${PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${OUTPUT_DIR}" + ARCHITE_OUTPUT_DIRECTORY "${OUTPUT_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${OUTPUT_DIR}") + +# Configuration-specific settings +target_compile_definitions(${PROJECT_NAME} PRIVATE + $<$:DEBUG> + $<$:OPTDEBUG> + $<$:RELEASE>) + +# Configuration-specific optimizations +target_compile_options(${PROJECT_NAME} PRIVATE + $<$:-g> + $<$:-O3> + $<$:-O3>) + + + + + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Workaround for std::expected not available in clang + add_compile_options( + -stdlib=libstdc++ -D__cpp_concepts=202002 -Wno-builtin-macro-redefined + ) +endif() diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..5c24fb8 --- /dev/null +++ b/shell.nix @@ -0,0 +1,19 @@ +{ pkgs ? import { } }: + +pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + man-pages + + gnumake + cmake + clang-tools + cmake-language-server + + tree-sitter-grammars.tree-sitter-c + tree-sitter-grammars.tree-sitter-cpp + ]; + + shellHook = '' + ${pkgs.onefetch}/bin/onefetch + ''; +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..81d1cba --- /dev/null +++ b/src/error.h @@ -0,0 +1,10 @@ +#pragma once + +namespace http { + enum class Error { + SocketCreation, + SocketConnection, + DNSResolution, + ServerNotFound, + }; +} diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..5ba6b74 --- /dev/null +++ b/src/http.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "error.h" +#include "method.h" +#include "request.h" +#include "response.h" + +#define ERR(error) std::unexpected(error) + +namespace http { + std::expected send(Method, const RequestOpts& req); + + std::expected connect_to(const std::string& domain_name, const uint16_t port = 80); +} // namespace http diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cf38174 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,25 @@ +#include + +#include "http.h" + +int main() { + auto resp1 = http::send(http::Method::GET, {.domain_name = "google.com"}); + resp1 = http::send(http::Method::GET, {.domain_name = "example.com", .port = 0}); + + if (!resp1.has_value()) { + switch (resp1.error()) { + case http::Error::SocketCreation: { + std::cout << "Socket creation" << std::endl; + } break; + case http::Error::SocketConnection: { + std::cout << "Socket connection" << std::endl; + } break; + case http::Error::DNSResolution: { + std::cout << "DNS resolution" << std::endl; + } break; + case http::Error::ServerNotFound: { + std::cout << "Server not found" << std::endl; + } break; + } + } +} diff --git a/src/method.h b/src/method.h new file mode 100644 index 0000000..df21a81 --- /dev/null +++ b/src/method.h @@ -0,0 +1,16 @@ +#pragma once + +namespace http { + enum class Method { + GET, + HEAD, + POST, + PUT, + DELETE, + CONNECT, + OPTIONS, + TRACE, + PATCH, + UPDATE, + }; +} diff --git a/src/request.h b/src/request.h new file mode 100644 index 0000000..76d80cf --- /dev/null +++ b/src/request.h @@ -0,0 +1,12 @@ +#pragma once + +#include "http.h" + +namespace http { + struct RequestOpts { + const std::string domain_name; + const uint16_t port = 80; + const std::string path = "/"; + const std::string body = ""; + }; +} diff --git a/src/response.h b/src/response.h new file mode 100644 index 0000000..55e8c78 --- /dev/null +++ b/src/response.h @@ -0,0 +1,12 @@ +#pragma once + +namespace http { + enum class Status { + OK = 200, + NOT_FOUND = 404, + }; + + struct Response { + Status status; + }; +} // namespace http diff --git a/src/send.cpp b/src/send.cpp new file mode 100644 index 0000000..8ec3c13 --- /dev/null +++ b/src/send.cpp @@ -0,0 +1,83 @@ +#include +#include +#include + +#include "http.h" + +static std::unordered_map ip_map; + +namespace http { + std::expected connect_to(const std::string &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)) { + return ERR(Error::DNSResolution); + } + + int8_t remote_socketfd; + if (auto it = ip_map.find(domain_name); it != ip_map.end()) { + remote_socketfd = + socket(it->second.ai_family, it->second.ai_socktype, it->second.ai_protocol); + if (remote_socketfd < 0) { + close(remote_socketfd); + return ERR(Error::SocketConnection); + } + } else { + struct addrinfo *remote = 0; + for (struct addrinfo *addr = addr_list; addr; addr = addr->ai_next) { + remote_socketfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (remote_socketfd < 0) { + close(remote_socketfd); + continue; + } + + struct timeval timeout; + timeout.tv_sec = 2; + timeout.tv_usec = 0; + + if (setsockopt(remote_socketfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout) < 0 || + setsockopt(remote_socketfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof timeout) < 0) { + close(remote_socketfd); + continue; + } + + // Connected with remote server + if (connect(remote_socketfd, addr->ai_addr, addr->ai_addrlen) < 0) { + close(remote_socketfd); + } else { + remote = addr; + break; + } + } + + if (!remote) { + freeaddrinfo(addr_list); + return ERR(Error::ServerNotFound); + } + + ip_map[domain_name] = *remote; + freeaddrinfo(addr_list); + } + + return remote_socketfd; + } + + std::expected send(Method method, const RequestOpts &req) { + auto start = std::chrono::system_clock::now(); + auto maybe_socketfd = connect_to(req.domain_name, req.port); + auto end = std::chrono::system_clock::now(); + + std::chrono::duration elapsed_seconds = end - start; + std::cout << "elapsed time: " << elapsed_seconds.count() << "s\t"; + + if (!maybe_socketfd.has_value()) { + return ERR(maybe_socketfd.error()); + } + + std::cout << "All good" << std::endl; + close(maybe_socketfd.value()); + return Response(); + } +} // namespace http -- 2.52.0