]> git.leonardobizzoni.com Git - http-lib/commitdiff
First commit: establish connection with remote given its domain name
authorLeonardoBizzoni <leo2002714@gmail.com>
Tue, 6 Aug 2024 11:29:40 +0000 (13:29 +0200)
committerLeonardoBizzoni <leo2002714@gmail.com>
Tue, 6 Aug 2024 11:29:40 +0000 (13:29 +0200)
.clang-format [new file with mode: 0644]
.clangd [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
shell.nix [new file with mode: 0644]
src/error.h [new file with mode: 0644]
src/http.h [new file with mode: 0644]
src/main.cpp [new file with mode: 0644]
src/method.h [new file with mode: 0644]
src/request.h [new file with mode: 0644]
src/response.h [new file with mode: 0644]
src/send.cpp [new file with mode: 0644]

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..36f0dae
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..0abad88
--- /dev/null
@@ -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
+    $<$<CONFIG:Debug>:DEBUG>
+    $<$<CONFIG:OptimizedDebug>:OPTDEBUG>
+    $<$<CONFIG:Release>:RELEASE>)
+
+# Configuration-specific optimizations
+target_compile_options(${PROJECT_NAME} PRIVATE
+    $<$<CONFIG:Debug>:-g>
+    $<$<CONFIG:OptimizedDebug>:-O3>
+    $<$<CONFIG:Release>:-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 (file)
index 0000000..5c24fb8
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,19 @@
+{ pkgs ? import <nixpkgs> { } }:
+
+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 (file)
index 0000000..81d1cba
--- /dev/null
@@ -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 (file)
index 0000000..5ba6b74
--- /dev/null
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <cstdint>
+#include <expected>
+#include <string>
+
+#include "error.h"
+#include "method.h"
+#include "request.h"
+#include "response.h"
+
+#define ERR(error) std::unexpected(error)
+
+namespace http {
+  std::expected<Response, Error> send(Method, const RequestOpts& req);
+
+  std::expected<int8_t, Error> 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 (file)
index 0000000..cf38174
--- /dev/null
@@ -0,0 +1,25 @@
+#include <iostream>
+
+#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 (file)
index 0000000..df21a81
--- /dev/null
@@ -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 (file)
index 0000000..76d80cf
--- /dev/null
@@ -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 (file)
index 0000000..55e8c78
--- /dev/null
@@ -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 (file)
index 0000000..8ec3c13
--- /dev/null
@@ -0,0 +1,83 @@
+#include <chrono>
+#include <iostream>
+#include <unordered_map>
+
+#include "http.h"
+
+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) {
+    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<Response, Error> 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<double> 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