Missing sending the `Response` to the client.
There is also an error when its given a query string alongside a
path eg. `example.com/eg?meaning_of_life=42`. This puts inside
`Request.query` `/eg?meaning_of_life=42` instead of just `/eg`.
--- /dev/null
+#pragma once
+
+#include <cstdint>
+#include <string_view>
+#include <unordered_map>
+
+#define Err(error) std::unexpected(error)
+
+namespace http {
+ constexpr std::string_view NEW_LINE = "\r\n";
+
+ enum class Status {
+ CONTINUE = 100,
+ SWITCHING_PROTOCOLS = 101,
+ PROCESSING = 102,
+
+ OK = 200,
+ CREATED,
+ ACCEPTED,
+ NON_AUTHORITATIVE_INFORMATION,
+ NO_CONTENT,
+ PARTIAL_CONTENT,
+ ALREADY_REPORTED,
+ IM_USED,
+
+ MULTIPLE_CHOICES = 300,
+ MOVED_PERMANENTLY,
+ FOUND,
+ SEE_OTHER,
+ NOT_MODIFIED,
+ USE_PROXY,
+ UNUSED,
+ TEMPORARY_REDIRECT,
+ PERMANENT_REDIRECT,
+
+ BAD_REQUEST = 400,
+ UNAUTHORIZED,
+ PAYMENT_REQUIRED,
+ FORBIDDEN,
+ NOT_FOUND,
+ METHOD_NOT_ALLOWED,
+ NOT_ACCEPTABLE,
+ PROXY_AUTHENTICATION_REQUIRED,
+ REQUEST_TIMEOUT,
+ CONFLICT,
+ GONE,
+ LENGTH_REQUIRED,
+ PRECONDITION_FAILED,
+ PAYLOAD_TOO_LARGE,
+ URI_TOO_LONG,
+ UNSUPPORTED_MEDIA_TYPE,
+ RANGE_NOT_SATISFIABLE,
+ EXPECTATION_FAILED,
+ IM_A_TEAPOT,
+ MISDIRECTED_REQUEST = 421,
+ UNPROCESSABLE_CONTENT,
+ LOCKED,
+ FAILED_DEPENDENCY,
+ TOO_EARLY,
+ UPGRADE_REQUIRED,
+ PRECONDITION_REQUIRED = 428,
+ TOO_MANY_REQUESTS,
+ REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+ UNAVAILABLE_FOR_LEGAL_REASONS = 451,
+
+ INTERNAL_SERVER_ERROR = 500,
+ NOT_IMPLEMENTED,
+ BAD_GATEWAY,
+ SERVICE_UNAVAILABLE,
+ GATEWAY_TIMEOUT,
+ HTTP_VERSION_NOT_SUPPORTED,
+ VARIANT_ALSO_NEGOTIATES,
+ INSUFFICIENT_STORAGE,
+ LOOP_DETECTED,
+ NOT_EXTENDED,
+ NETWORK_AUTHENTICATION_REQUIRED,
+ };
+
+ const std::unordered_map<uint16_t, Status> status_map = {
+ {100, Status::CONTINUE},
+ {101, Status::SWITCHING_PROTOCOLS},
+ {102, Status::PROCESSING},
+
+ {200, Status::OK},
+ {201, Status::CREATED},
+ {202, Status::ACCEPTED},
+ {203, Status::NON_AUTHORITATIVE_INFORMATION},
+ {204, Status::NO_CONTENT},
+ {205, Status::PARTIAL_CONTENT},
+ {208, Status::ALREADY_REPORTED},
+ {226, Status::IM_USED},
+
+ {300, Status::MULTIPLE_CHOICES},
+ {301, Status::MOVED_PERMANENTLY},
+ {302, Status::FOUND},
+ {303, Status::SEE_OTHER},
+ {304, Status::NOT_MODIFIED},
+ {305, Status::USE_PROXY},
+ {306, Status::UNUSED},
+ {307, Status::TEMPORARY_REDIRECT},
+ {308, Status::PERMANENT_REDIRECT},
+
+ {400, Status::BAD_REQUEST},
+ {401, Status::UNAUTHORIZED},
+ {402, Status::PAYMENT_REQUIRED},
+ {403, Status::FORBIDDEN},
+ {404, Status::NOT_FOUND},
+ {405, Status::METHOD_NOT_ALLOWED},
+ {406, Status::NOT_ACCEPTABLE},
+ {407, Status::PROXY_AUTHENTICATION_REQUIRED},
+ {408, Status::REQUEST_TIMEOUT},
+ {409, Status::CONFLICT},
+ {410, Status::GONE},
+ {411, Status::LENGTH_REQUIRED},
+ {412, Status::PRECONDITION_FAILED},
+ {413, Status::PAYLOAD_TOO_LARGE},
+ {414, Status::URI_TOO_LONG},
+ {415, Status::UNSUPPORTED_MEDIA_TYPE},
+ {416, Status::RANGE_NOT_SATISFIABLE},
+ {417, Status::EXPECTATION_FAILED},
+ {418, Status::IM_A_TEAPOT},
+ {421, Status::MISDIRECTED_REQUEST},
+ {422, Status::UNPROCESSABLE_CONTENT},
+ {423, Status::LOCKED},
+ {424, Status::FAILED_DEPENDENCY},
+ {425, Status::TOO_EARLY},
+ {426, Status::UPGRADE_REQUIRED},
+ {428, Status::PRECONDITION_REQUIRED},
+ {429, Status::TOO_MANY_REQUESTS},
+ {431, Status::REQUEST_HEADER_FIELDS_TOO_LARGE},
+ {451, Status::UNAVAILABLE_FOR_LEGAL_REASONS},
+
+ {500, Status::INTERNAL_SERVER_ERROR},
+ {501, Status::NOT_IMPLEMENTED},
+ {502, Status::BAD_GATEWAY},
+ {503, Status::SERVICE_UNAVAILABLE},
+ {504, Status::GATEWAY_TIMEOUT},
+ {505, Status::HTTP_VERSION_NOT_SUPPORTED},
+ {506, Status::VARIANT_ALSO_NEGOTIATES},
+ {507, Status::INSUFFICIENT_STORAGE},
+ {508, Status::LOOP_DETECTED},
+ {510, Status::NOT_EXTENDED},
+ {511, Status::NETWORK_AUTHENTICATION_REQUIRED},
+ };
+}
SocketConnection,
DNSResolution,
ServerNotFound,
- InvalidRead,
InvalidResponse,
- InvalidResponseHTTPVersion,
+ InvalidRequest,
+ InvalidRequestMethod,
+ InvalidRequestPath,
+ InvalidQueryRequest,
+ InvalidHTTPVersion,
InvalidResponseStatusCode,
};
}
#include <future>
+#include "const_definitions.h"
#include "error.h"
#include "method.h"
#include "request.h"
#include "response.h"
#include "listener.h"
-#define Err(error) std::unexpected(error)
-
namespace http {
- constexpr std::string_view NEW_LINE = "\r\n";
-
std::expected<Response, Error> send(Method, const Request& req);
std::expected<int8_t, Error> connect(const std::string_view& domain_name, const uint16_t port = 80);
--- /dev/null
+#pragma once
+
+#include <cstdint>
+
+namespace http {
+ struct http_version {
+ uint8_t major = 1;
+ uint8_t minor = 1;
+ };
+} // namespace http
#include <unistd.h>
#include <cstdio>
-#include <iostream>
+#include "const_definitions.h"
#include "error.h"
#include "http.h"
std::expected<void, Error> Listener::serve() {
socklen_t addrsize = sizeof(this->addr);
- int8_t clientfd = ::accept(this->socketfd, (struct sockaddr *)&this->addr, &addrsize);
- if (clientfd < 0) {
- return Err(Error::SocketConnection);
- }
- auto raw_req = read_raw_message(clientfd);
- if (raw_req.has_value()) {
- std::cout << raw_req.value() << std::endl;
+ while (true) {
+ int8_t clientfd = ::accept(this->socketfd, (struct sockaddr *)&this->addr, &addrsize);
+ if (clientfd < 0) {
+ continue;
+ }
+
+ Response resp;
+ auto raw_req = read_raw_message(clientfd);
+ if (raw_req.has_value()) {
+ auto maybe_req = Request::build(raw_req.value());
+
+ if (maybe_req.has_value()) {
+ try {
+ resp = this->routes.at(maybe_req.value().query)(maybe_req.value());
+ } catch (...) {
+ }
+ }
+ }
+
+ // Send `resp` to client
+
+ ::shutdown(clientfd, SHUT_RDWR);
+ ::close(clientfd);
}
- ::shutdown(clientfd, SHUT_RDWR);
- ::close(clientfd);
return {};
}
} // namespace http
#include "error.h"
#include "request.h"
+#include "response.h"
namespace http {
struct Listener {
uint16_t backlog = 4096);
public:
- std::unordered_map<std::string, std::function<void(const Request &)>> routes;
+ std::unordered_map<std::string, std::function<Response(const Request &)>> routes;
private:
int8_t socketfd;
#include <iostream>
#include "http.h"
+#include "request.h"
+#include "response.h"
int main() {
- auto server = http::Listener::create_on_local(8800);
+ auto server = http::Listener::on_local(8800);
+ server.value().routes["/"] = [](const http::Request &req) -> http::Response {
+ std::cout << req << std::endl;
+ return http::Response();
+ };
+ server.value().routes["/prova"] = [](const http::Request &req) -> http::Response {
+ std::cout << "Hello, World!" << std::endl;
+ auto _ = req.body;
+ return http::Response();
+ };
server.value().serve();
-
- server.value().routes["prova"] = [](const http::Request &req) { std::cout << req << std::endl; };
-
- http::Request request;
- server.value().routes["prova"](request);
}
#pragma once
#include <ostream>
+#include <unordered_map>
namespace http {
enum class Method {
GET,
PATCH,
UPDATE,
};
+
+ const std::unordered_map<std::string, Method> method_map = {
+ {"get", Method::GET}, {"head", Method::HEAD}, {"post", Method::POST},
+ {"put", Method::PUT}, {"delete", Method::DELETE}, {"connect", Method::CONNECT},
+ {"options", Method::OPTIONS}, {"trace", Method::TRACE}, {"patch", Method::PATCH},
+ {"update", Method::UPDATE},
+ };
} // namespace http
std::ostream &operator<<(std::ostream &os, const http::Method &method);
#include "request.h"
+#include <algorithm>
+#include <cctype>
#include <iomanip>
+#include <iostream>
+#include <iterator>
+#include <ranges>
+
+#include "const_definitions.h"
+#include "method.h"
std::ostream &operator<<(std::ostream &os, const http::Request &req) {
- return os << "Request { domain: " << std::quoted(req.domain_name) << ", port: " << req.port
- << ", query: " << std::quoted(req.query) << ", body: " << std::quoted(req.body) << " }";
+ os << "Request { domain: " << std::quoted(req.domain_name) << ", port: " << req.port
+ << ", query: " << std::quoted(req.query) << ", method: " << req.method
+ << ", version: " << (int)req.version.major << "." << (int)req.version.minor << ", headers: [ ";
+
+ for (const auto &header : req.optheaders) {
+ os << header.first << " = " << header.second << ",";
+ }
+
+ os << " ] }";
+ return os;
}
+
+namespace http {
+ std::expected<Request, Error> Request::build(std::string_view raw_request) noexcept {
+ if (raw_request.empty()) {
+ return Err(Error::InvalidRequest);
+ }
+
+ Request req;
+
+ auto lines_view = raw_request | 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 method_view(*word_iter++);
+ std::string method(method_view);
+ std::transform(method_view.cbegin(), method_view.cend(), method.begin(),
+ [](unsigned char ch) { return std::tolower(ch); });
+
+ try {
+ req.method = method_map.at(method);
+ } catch (...) {
+ return Err(Error::InvalidRequestMethod);
+ }
+
+ std::string_view query(*word_iter++);
+ std::cout << "\t" << query << std::endl;
+ if (query.empty() || !query.starts_with('/')) {
+ return Err(Error::InvalidQueryRequest);
+ } else {
+ req.query = query;
+ }
+
+ 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 {
+ req.version = {.major = (uint8_t)std::stoi(major.data()),
+ .minor = (uint8_t)std::stoi(minor.data())};
+ } catch (...) {
+ return Err(Error::InvalidHTTPVersion);
+ }
+
+ 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;
+
+ if ((*it)[0] == ' ') {
+ req.optheaders[field_name] = std::string((*it).begin() + 1, (*it).end());
+ } else {
+ req.optheaders[field_name] = std::string((*it).begin(), (*it).end());
+ }
+ }
+
+ while (++lines_view_iter != lines_view.end()) {
+ req.body += std::string((*lines_view_iter).begin(), (*lines_view_iter).end());
+
+ if (std::next(lines_view_iter) != lines_view.end()) {
+ req.body += "\n";
+ }
+ }
+
+ return req;
+ }
+} // namespace http
#pragma once
#include <cstdint>
-#include <string_view>
+#include <expected>
#include <ostream>
+#include <string_view>
#include <unordered_map>
-namespace http {
- struct http_version {
- uint8_t major = 1;
- uint8_t minor = 1;
- };
+#include "error.h"
+#include "http_version.h"
+#include "method.h"
+namespace http {
struct Request {
+ public:
+ static std::expected<Request, Error> build(std::string_view raw_response) noexcept;
+
+ public:
uint16_t port = 80;
std::string_view domain_name;
- std::string_view query = "/";
- std::string_view body = "";
+ std::string query = "/";
+ std::string body = "";
+ Method method;
http_version version = {.major = 1, .minor = 1};
std::unordered_map<std::string, std::string> optheaders = {};
+#include "response.h"
+#include <expected>
#include <iostream>
#include <ranges>
-#include <string_view>
#include <utility>
-#include "http.h"
-
namespace http {
std::expected<Response, Error> Response::build(std::string_view raw_response) noexcept {
if (raw_response.empty()) {
resp.version = {.major = (uint8_t)std::stoi(major.data()),
.minor = (uint8_t)std::stoi(minor.data())};
} catch (...) {
- return Err(Error::InvalidResponseHTTPVersion);
+ return Err(Error::InvalidHTTPVersion);
}
try {
std::string_view status(*++word_iter);
- resp.status = status_map[std::stoi(status.data())];
+ resp.status = status_map.at(std::stoi(status.data()));
} catch (...) {
return Err(Error::InvalidResponseStatusCode);
}
#pragma once
-#include <cstdint>
#include <expected>
#include <string>
-#include <string_view>
-#include <unordered_map>
+#include "const_definitions.h"
#include "error.h"
-#include "request.h"
+#include "http_version.h"
namespace http {
- enum class Status {
- CONTINUE = 100,
- SWITCHING_PROTOCOLS = 101,
- PROCESSING = 102,
-
- OK = 200,
- CREATED,
- ACCEPTED,
- NON_AUTHORITATIVE_INFORMATION,
- NO_CONTENT,
- PARTIAL_CONTENT,
- ALREADY_REPORTED,
- IM_USED,
-
- MULTIPLE_CHOICES = 300,
- MOVED_PERMANENTLY,
- FOUND,
- SEE_OTHER,
- NOT_MODIFIED,
- USE_PROXY,
- UNUSED,
- TEMPORARY_REDIRECT,
- PERMANENT_REDIRECT,
-
- BAD_REQUEST = 400,
- UNAUTHORIZED,
- PAYMENT_REQUIRED,
- FORBIDDEN,
- NOT_FOUND,
- METHOD_NOT_ALLOWED,
- NOT_ACCEPTABLE,
- PROXY_AUTHENTICATION_REQUIRED,
- REQUEST_TIMEOUT,
- CONFLICT,
- GONE,
- LENGTH_REQUIRED,
- PRECONDITION_FAILED,
- PAYLOAD_TOO_LARGE,
- URI_TOO_LONG,
- UNSUPPORTED_MEDIA_TYPE,
- RANGE_NOT_SATISFIABLE,
- EXPECTATION_FAILED,
- IM_A_TEAPOT,
- MISDIRECTED_REQUEST = 421,
- UNPROCESSABLE_CONTENT,
- LOCKED,
- FAILED_DEPENDENCY,
- TOO_EARLY,
- UPGRADE_REQUIRED,
- PRECONDITION_REQUIRED = 428,
- TOO_MANY_REQUESTS,
- REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
- UNAVAILABLE_FOR_LEGAL_REASONS = 451,
-
- INTERNAL_SERVER_ERROR = 500,
- NOT_IMPLEMENTED,
- BAD_GATEWAY,
- SERVICE_UNAVAILABLE,
- GATEWAY_TIMEOUT,
- HTTP_VERSION_NOT_SUPPORTED,
- VARIANT_ALSO_NEGOTIATES,
- INSUFFICIENT_STORAGE,
- LOOP_DETECTED,
- NOT_EXTENDED,
- NETWORK_AUTHENTICATION_REQUIRED,
- };
-
- static std::unordered_map<uint16_t, Status> status_map = {
- {100, Status::CONTINUE},
- {101, Status::SWITCHING_PROTOCOLS},
- {102, Status::PROCESSING},
-
- {200, Status::OK},
- {201, Status::CREATED},
- {202, Status::ACCEPTED},
- {203, Status::NON_AUTHORITATIVE_INFORMATION},
- {204, Status::NO_CONTENT},
- {205, Status::PARTIAL_CONTENT},
- {208, Status::ALREADY_REPORTED},
- {226, Status::IM_USED},
-
- {300, Status::MULTIPLE_CHOICES},
- {301, Status::MOVED_PERMANENTLY},
- {302, Status::FOUND},
- {303, Status::SEE_OTHER},
- {304, Status::NOT_MODIFIED},
- {305, Status::USE_PROXY},
- {306, Status::UNUSED},
- {307, Status::TEMPORARY_REDIRECT},
- {308, Status::PERMANENT_REDIRECT},
-
- {400, Status::BAD_REQUEST},
- {401, Status::UNAUTHORIZED},
- {402, Status::PAYMENT_REQUIRED},
- {403, Status::FORBIDDEN},
- {404, Status::NOT_FOUND},
- {405, Status::METHOD_NOT_ALLOWED},
- {406, Status::NOT_ACCEPTABLE},
- {407, Status::PROXY_AUTHENTICATION_REQUIRED},
- {408, Status::REQUEST_TIMEOUT},
- {409, Status::CONFLICT},
- {410, Status::GONE},
- {411, Status::LENGTH_REQUIRED},
- {412, Status::PRECONDITION_FAILED},
- {413, Status::PAYLOAD_TOO_LARGE},
- {414, Status::URI_TOO_LONG},
- {415, Status::UNSUPPORTED_MEDIA_TYPE},
- {416, Status::RANGE_NOT_SATISFIABLE},
- {417, Status::EXPECTATION_FAILED},
- {418, Status::IM_A_TEAPOT},
- {421, Status::MISDIRECTED_REQUEST},
- {422, Status::UNPROCESSABLE_CONTENT},
- {423, Status::LOCKED},
- {424, Status::FAILED_DEPENDENCY},
- {425, Status::TOO_EARLY},
- {426, Status::UPGRADE_REQUIRED},
- {428, Status::PRECONDITION_REQUIRED},
- {429, Status::TOO_MANY_REQUESTS},
- {431, Status::REQUEST_HEADER_FIELDS_TOO_LARGE},
- {451, Status::UNAVAILABLE_FOR_LEGAL_REASONS},
-
- {500, Status::INTERNAL_SERVER_ERROR},
- {501, Status::NOT_IMPLEMENTED},
- {502, Status::BAD_GATEWAY},
- {503, Status::SERVICE_UNAVAILABLE},
- {504, Status::GATEWAY_TIMEOUT},
- {505, Status::HTTP_VERSION_NOT_SUPPORTED},
- {506, Status::VARIANT_ALSO_NEGOTIATES},
- {507, Status::INSUFFICIENT_STORAGE},
- {508, Status::LOOP_DETECTED},
- {510, Status::NOT_EXTENDED},
- {511, Status::NETWORK_AUTHENTICATION_REQUIRED},
- };
-
struct Response {
http_version version;
Status status;
std::unordered_map<std::string, std::string> fields;
public:
- static std::expected<Response, Error> build(std::string_view raw_response) noexcept;
+ Response() : version({1,1}), status(Status::NOT_ACCEPTABLE) {}
~Response() {}
- private:
- Response() {}
+ static std::expected<Response, Error> build(std::string_view raw_response) noexcept;
};
} // namespace http