cleanup of source + build system
* seperated most parts into headers & source files * seperated anthracite into libanthracite and anthracite-bin for modules * built demo webAPI module * rewrote some code to do things in the * changed cmakelists to not build in src directory
This commit is contained in:
parent
c4540a1397
commit
54d82b8c66
1
src/.gitignore
vendored
1
src/.gitignore
vendored
|
@ -10,3 +10,4 @@ install_manifest.txt
|
|||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
build/
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
||||
|
||||
project(anthracite)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
||||
|
||||
|
||||
add_custom_target(build-supplemental
|
||||
COMMAND cd ./build && ./version.sh && python3 ./error_gen.py
|
||||
COMMAND cd ../build_supp && ./version.sh && python3 ./error_gen.py
|
||||
COMMAND cp -r ../www .
|
||||
)
|
||||
|
||||
add_custom_target(lint
|
||||
|
@ -32,5 +34,15 @@ add_custom_target(run
|
|||
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
|
||||
)
|
||||
|
||||
add_executable(anthracite-bin main.cpp)
|
||||
FILE(GLOB SOURCES **/*.cpp)
|
||||
|
||||
add_library(anthracite anthracite_main.cpp ${SOURCES})
|
||||
|
||||
add_executable(anthracite-bin file_main.cpp)
|
||||
target_link_libraries(anthracite-bin anthracite)
|
||||
|
||||
add_executable(anthracite-api-bin api_main.cpp)
|
||||
target_link_libraries(anthracite-api-bin anthracite)
|
||||
|
||||
add_dependencies(anthracite-bin build-supplemental)
|
||||
add_dependencies(anthracite-bin anthracite)
|
||||
|
|
81
src/anthracite_main.cpp
Normal file
81
src/anthracite_main.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include "backends/file_backend.hpp"
|
||||
#include "./anthracite_main.hpp"
|
||||
#include <condition_variable>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <span>
|
||||
#include "./log/log.hpp"
|
||||
#include "./socket.hpp"
|
||||
|
||||
using namespace anthracite;
|
||||
|
||||
void log_request_and_response(http::request& req, std::unique_ptr<http::response>& resp);
|
||||
|
||||
constexpr int default_port = 80;
|
||||
constexpr int max_worker_threads = 128;
|
||||
|
||||
void handle_client(socket::anthracite_socket s, backends::backend& b, backends::file_backend& fb, std::mutex& thread_wait_mutex, std::condition_variable& thread_wait_condvar, int& active_threads)
|
||||
{
|
||||
while (true) {
|
||||
std::string raw_request = s.recv_message(http::HEADER_BYTES);
|
||||
if (raw_request == "") {
|
||||
break;
|
||||
}
|
||||
http::request req(raw_request, s.get_client_ip());
|
||||
std::unique_ptr<http::response> resp = req.is_supported_version() ? b.handle_request(req) : fb.handle_error(http::status_codes::HTTP_VERSION_NOT_SUPPORTED);
|
||||
log_request_and_response(req, resp);
|
||||
std::string header = resp->header_to_string();
|
||||
s.send_message(header);
|
||||
s.send_message(resp->content());
|
||||
resp.reset();
|
||||
if (req.close_connection()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
s.close_conn();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(thread_wait_mutex);
|
||||
active_threads--;
|
||||
}
|
||||
thread_wait_condvar.notify_one();
|
||||
}
|
||||
|
||||
//int main(int argc, char** argv)
|
||||
int anthracite_main(int argc, char** argv, backends::backend& be)
|
||||
{
|
||||
log::logger.initialize(log::LOG_LEVEL_INFO);
|
||||
auto args = std::span(argv, size_t(argc));
|
||||
int port_number = default_port;
|
||||
|
||||
if (argc > 1) {
|
||||
port_number = atoi(args[1]);
|
||||
}
|
||||
log::verbose << "Initializing Anthracite" << std::endl;
|
||||
socket::anthracite_socket s(port_number);
|
||||
backends::file_backend fb(argc > 2 ? args[2] : "./www");
|
||||
log::verbose << "Initialization Complete" << std::endl;
|
||||
log::info << "Listening for HTTP connections on port " << port_number << std::endl;
|
||||
|
||||
int active_threads = 0;
|
||||
std::mutex thread_wait_mutex;
|
||||
std::condition_variable thread_wait_condvar;
|
||||
|
||||
while (true) {
|
||||
s.wait_for_conn();
|
||||
std::unique_lock<std::mutex> lock(thread_wait_mutex);
|
||||
thread_wait_condvar.wait(lock, [active_threads] { return active_threads < max_worker_threads; });
|
||||
active_threads++;
|
||||
std::thread(handle_client, s, std::ref(be), std::ref(fb), std::ref(thread_wait_mutex), std::ref(thread_wait_condvar), std::ref(active_threads)).detach();
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void log_request_and_response(http::request& req, std::unique_ptr<http::response>& resp)
|
||||
{
|
||||
log::info << "[" << resp->status_code() << " " + http::status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http::reverse_method_map.find(req.get_method())->second + " " + req.path() << std::endl;
|
||||
}
|
6
src/anthracite_main.hpp
Normal file
6
src/anthracite_main.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "backends/backend.hpp"
|
||||
|
||||
using namespace anthracite;
|
||||
int anthracite_main(int argc, char** argv, backends::backend& be);
|
105
src/api_main.cpp
Normal file
105
src/api_main.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
#include "./anthracite_main.hpp"
|
||||
#include "backends/backend.hpp"
|
||||
#include "http/constants.hpp"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
using namespace anthracite;
|
||||
|
||||
using CallbackType = std::unique_ptr<http::response> (*)(http::request&);
|
||||
class api_backend : public backends::backend {
|
||||
|
||||
class RouteNode {
|
||||
public:
|
||||
|
||||
std::optional<CallbackType> callback;
|
||||
|
||||
RouteNode() : callback(std::nullopt) {}
|
||||
std::unordered_map<std::string, RouteNode> routes;
|
||||
};
|
||||
|
||||
RouteNode root;
|
||||
|
||||
std::unique_ptr<http::response> default_route(http::request& req) {
|
||||
std::unique_ptr<http::response> resp = std::make_unique<http::response>();
|
||||
|
||||
resp->add_body("Not Found");
|
||||
resp->add_header(http::header("Content-Type", "application/json"));
|
||||
resp->add_status(http::status_codes::NOT_FOUND);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
std::unique_ptr<http::response> find_handler(http::request& req) {
|
||||
std::string filename = req.path().substr(1);
|
||||
std::vector<std::string> result;
|
||||
std::stringstream ss (filename);
|
||||
std::string item;
|
||||
|
||||
RouteNode* cur = &root;
|
||||
while (getline (ss, item, '/')) {
|
||||
if (cur->routes.find(item) == cur->routes.end()) {
|
||||
if (cur->routes.find("*") == cur->routes.end()) {
|
||||
break;
|
||||
} else {
|
||||
cur = &cur->routes["*"];
|
||||
}
|
||||
} else {
|
||||
cur = &cur->routes[item];
|
||||
}
|
||||
}
|
||||
|
||||
if (cur->callback.has_value()) {
|
||||
return cur->callback.value()(req);
|
||||
} else {
|
||||
return default_route(req);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
std::unique_ptr<http::response> handle_request(http::request& req) override {
|
||||
return find_handler(req);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
api_backend() {
|
||||
root.routes = std::unordered_map<std::string, RouteNode>();
|
||||
}
|
||||
|
||||
void register_endpoint(std::string pathspec, CallbackType callback) {
|
||||
std::vector<std::string> result;
|
||||
std::stringstream ss (pathspec);
|
||||
std::string item;
|
||||
|
||||
RouteNode* cur = &root;
|
||||
while (getline (ss, item, '/')) {
|
||||
cur->routes[item] = RouteNode{};
|
||||
cur = &cur->routes[item];
|
||||
}
|
||||
|
||||
cur->callback = callback;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<http::response> handle_request(http::request& req) {
|
||||
std::unique_ptr<http::response> resp = std::make_unique<http::response>();
|
||||
|
||||
resp->add_body(R"({"user": "endpoint"}")");
|
||||
resp->add_header(http::header("Content-Type", "application/json"));
|
||||
resp->add_status(http::status_codes::OK);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
auto args = std::span(argv, size_t(argc));
|
||||
api_backend ab;
|
||||
ab.register_endpoint("users/*", handle_request);
|
||||
anthracite_main(argc, argv, ab);
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
#include <memory>
|
||||
#pragma once
|
||||
|
||||
#include "../http/http_request.cpp"
|
||||
#include "../http/http_response.cpp"
|
||||
#include <memory>
|
||||
#include "../http/request.hpp"
|
||||
#include "../http/response.hpp"
|
||||
|
||||
namespace anthracite::backends {
|
||||
|
||||
class backend {
|
||||
public:
|
||||
|
@ -11,5 +14,7 @@ public:
|
|||
backend& operator = (backend const&) = delete;
|
||||
backend(backend&&) = delete;
|
||||
backend& operator=(backend&&) = delete;
|
||||
virtual std::unique_ptr<http_response> handle_request(http_request& req) = 0;
|
||||
virtual std::unique_ptr<http::response> handle_request(http::request& req) = 0;
|
||||
};
|
||||
|
||||
};
|
|
@ -1,26 +1,40 @@
|
|||
#include "./file_backend.hpp"
|
||||
#include "../log/log.hpp"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include "backend.cpp"
|
||||
|
||||
class file_backend : public backend {
|
||||
private:
|
||||
std::unordered_map<std::string,std::string> file_cache;
|
||||
std::string file_dir;
|
||||
|
||||
std::unique_ptr<http_response> handle_request_cache(http_request& req) {
|
||||
namespace anthracite::backends {
|
||||
|
||||
|
||||
std::unique_ptr<http::response> file_backend::handle_request_cache(http::request& req) {
|
||||
std::string filename = req.path() == "/" ? "/index.html" : req.path();
|
||||
filename = file_dir + filename;
|
||||
auto file_info = file_cache.find(filename);
|
||||
|
||||
int status = http_status_codes::OK;
|
||||
int status = http::status_codes::OK;
|
||||
if (file_info == file_cache.end()) {
|
||||
return handle_error(http_status_codes::NOT_FOUND);
|
||||
return handle_error(http::status_codes::NOT_FOUND);
|
||||
}
|
||||
|
||||
return std::make_unique<http_response>(file_info->second, filename, status);
|
||||
std::unique_ptr<http::response> resp = std::make_unique<http::response>();
|
||||
|
||||
std::string file_extension = file_info->first.substr(file_info->first.rfind('.') + 1);
|
||||
std::string content_type = "text/html";
|
||||
|
||||
auto mime_type = http::mime_types.find(file_extension);
|
||||
if (mime_type != http::mime_types.end()) {
|
||||
content_type = mime_type->second;
|
||||
}
|
||||
|
||||
resp->add_body_ref(&file_info->second);
|
||||
resp->add_status(http::status_codes::OK);
|
||||
resp->add_header(http::header("Content-Type", content_type), false);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
void populate_cache_dir(std::string dir) {
|
||||
void file_backend::populate_cache_dir(std::string dir) {
|
||||
std::filesystem::recursive_directory_iterator cur = begin(std::filesystem::recursive_directory_iterator(dir));
|
||||
std::filesystem::recursive_directory_iterator fin = end(std::filesystem::recursive_directory_iterator(dir));
|
||||
|
||||
|
@ -31,37 +45,43 @@ private:
|
|||
std::ifstream stream(filename);
|
||||
buffer << stream.rdbuf();
|
||||
file_cache[filename] = buffer.str();
|
||||
std::cout << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl;
|
||||
log::verbose << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl;
|
||||
++cur;
|
||||
}
|
||||
}
|
||||
|
||||
void populate_cache() {
|
||||
void file_backend::populate_cache() {
|
||||
populate_cache_dir(file_dir);
|
||||
populate_cache_dir("./error_pages/");
|
||||
}
|
||||
|
||||
public:
|
||||
file_backend(std::string dir = "./www") : file_dir(std::move(dir)) {
|
||||
file_backend::file_backend(std::string dir) : file_dir(std::move(dir)) {
|
||||
populate_cache();
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<http_response> handle_request(http_request& req) override {
|
||||
std::unique_ptr<http::response> file_backend::handle_request(http::request& req) {
|
||||
return handle_request_cache(req);
|
||||
}
|
||||
|
||||
std::unique_ptr<http_response> handle_error(const http_status_codes& error) {
|
||||
std::unique_ptr<http::response> file_backend::handle_error(const http::status_codes& error) {
|
||||
std::string filename = "./error_pages/" + std::to_string(error) + ".html";
|
||||
auto file_info = file_cache.find(filename);
|
||||
|
||||
http_status_codes status = error;
|
||||
http::status_codes status = error;
|
||||
if (file_info == file_cache.end()) {
|
||||
status = http_status_codes::NOT_FOUND;
|
||||
status = http::status_codes::NOT_FOUND;
|
||||
filename = "./error_pages/404.html";
|
||||
file_info = file_cache.find(filename);
|
||||
}
|
||||
|
||||
return std::make_unique<http_response>(file_info->second, filename, status);
|
||||
std::unique_ptr<http::response> resp = std::make_unique<http::response>();
|
||||
|
||||
resp->add_body_ref(&file_info->second);
|
||||
resp->add_status(error);
|
||||
resp->add_header(http::header("Content-Type", "text/html"), false);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
22
src/backends/file_backend.hpp
Normal file
22
src/backends/file_backend.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "backend.hpp"
|
||||
|
||||
namespace anthracite::backends {
|
||||
|
||||
class file_backend : public backend {
|
||||
private:
|
||||
std::unordered_map<std::string,std::string> file_cache;
|
||||
std::string file_dir;
|
||||
|
||||
std::unique_ptr<http::response> handle_request_cache(http::request& req);
|
||||
void populate_cache_dir(std::string dir);
|
||||
void populate_cache();
|
||||
public:
|
||||
file_backend(std::string dir = "./www") ;
|
||||
|
||||
std::unique_ptr<http::response> handle_request(http::request& req) override;
|
||||
std::unique_ptr<http::response> handle_error(const http::status_codes& error);
|
||||
};
|
||||
|
||||
};
|
|
@ -66,7 +66,7 @@ error_codes = {
|
|||
}
|
||||
|
||||
|
||||
error_dir = '../error_pages'
|
||||
error_dir = '../build/error_pages'
|
||||
os.makedirs(error_dir, exist_ok=True)
|
||||
|
||||
for code, title in error_codes.items():
|
3
src/build_supp/version.cpp
Normal file
3
src/build_supp/version.cpp
Normal file
|
@ -0,0 +1,3 @@
|
|||
#include <string>
|
||||
const std::string ANTHRACITE_VERSION_STRING = "0.2.0";
|
||||
const std::string ANTHRACITE_FULL_VERSION_STRING = "Anthracite/0.2.0";
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* smart_map.cpp
|
||||
* -------------
|
||||
* Class that wraps unordered_map. Chooses whether to do O(1) hash lookup
|
||||
* or O(N) linear lookup based on benchmark. This is done as O(smallN) can
|
||||
* be faster than O(1)
|
||||
*
|
||||
*/
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
constexpr int benchmark_loops = 100;
|
||||
constexpr int SEED = 570;
|
||||
|
||||
template <typename KeyType, typename ValueType>
|
||||
class smart_map {
|
||||
private:
|
||||
bool use_hmap = false;
|
||||
std::unordered_map<KeyType, ValueType> hmap;
|
||||
|
||||
double assess_hmap(const std::vector<std::pair<KeyType, ValueType>> check) {
|
||||
const auto& start = std::chrono::high_resolution_clock::now();
|
||||
for(int i = 0; i < benchmark_loops; i++) {
|
||||
for(const auto& check_item : check) {
|
||||
assert(check_item.second == hmap[check_item.first]);
|
||||
}
|
||||
}
|
||||
const auto& end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration<double, std::nano>(end - start).count();
|
||||
return duration;
|
||||
}
|
||||
|
||||
double assess_vmap(const std::vector<std::pair<KeyType, ValueType>> check) {
|
||||
const auto& start = std::chrono::high_resolution_clock::now();
|
||||
for(int i = 0; i < benchmark_loops; i++) {
|
||||
for(const auto& check_item : check) {
|
||||
for(const auto& item : hmap) {
|
||||
if(check_item.first == item.first) {
|
||||
assert(check_item.second == item.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration<double, std::nano>(end - start).count();
|
||||
return duration;
|
||||
}
|
||||
|
||||
public:
|
||||
smart_map () = default;
|
||||
|
||||
void assess_datastructure() {
|
||||
std::vector<std::pair<KeyType, ValueType>> vals(hmap.begin(), hmap.end());
|
||||
std::shuffle(vals.begin(), vals.end(), std::default_random_engine(SEED));
|
||||
use_hmap = assess_hmap(vals) > assess_vmap(vals);
|
||||
}
|
||||
|
||||
bool will_use_hmap() {
|
||||
return use_hmap;
|
||||
}
|
||||
|
||||
ValueType* get(const KeyType& key) {
|
||||
if(use_hmap) {
|
||||
if(hmap.find(key) != hmap.end()) {
|
||||
return &hmap[key];
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& item : hmap) {
|
||||
if(item.first == key) {
|
||||
std::string& ref = item.second;
|
||||
return &ref;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void insert(const KeyType key, const ValueType value) {
|
||||
hmap[key] = value;
|
||||
}
|
||||
};
|
10
src/file_main.cpp
Normal file
10
src/file_main.cpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#include "./anthracite_main.hpp"
|
||||
#include "backends/file_backend.hpp"
|
||||
|
||||
using namespace anthracite;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
auto args = std::span(argv, size_t(argc));
|
||||
backends::file_backend fb(argc > 2 ? args[2] : "./www");
|
||||
anthracite_main(argc, argv, fb);
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
constexpr int HTTP_HEADER_BYTES = 8190;
|
||||
namespace anthracite::http {
|
||||
|
||||
enum http_method {
|
||||
constexpr int HEADER_BYTES = 8190;
|
||||
|
||||
enum method {
|
||||
GET,
|
||||
POST,
|
||||
DELETE,
|
||||
|
@ -24,7 +28,7 @@ enum http_method {
|
|||
UNKNOWN
|
||||
};
|
||||
|
||||
enum http_status_codes {
|
||||
enum status_codes {
|
||||
CONTINUE = 100,
|
||||
SWITCHING_PROTOCOLS = 101,
|
||||
PROCESSING = 102,
|
||||
|
@ -90,49 +94,49 @@ enum http_status_codes {
|
|||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, http_method> const http_method_map = {
|
||||
{ "GET", http_method::GET },
|
||||
{ "POST", http_method::POST },
|
||||
{ "DELETE", http_method::DELETE },
|
||||
{ "PUT", http_method::PUT },
|
||||
{ "PATCH", http_method::PATCH },
|
||||
{ "HEAD", http_method::HEAD },
|
||||
{ "OPTIONS", http_method::OPTIONS },
|
||||
{ "CONNECT", http_method::CONNECT },
|
||||
{ "TRACE", http_method::TRACE },
|
||||
{ "COPY", http_method::COPY },
|
||||
{ "LINK", http_method::LINK },
|
||||
{ "UNLINK", http_method::UNLINK },
|
||||
{ "PURGE", http_method::PURGE },
|
||||
{ "LOCK", http_method::LOCK },
|
||||
{ "UNLOCK", http_method::UNLOCK },
|
||||
{ "PROPFIND", http_method::PROPFIND },
|
||||
{ "VIEW", http_method::VIEW },
|
||||
{ "UNKNOWN", http_method::UNKNOWN }
|
||||
static std::unordered_map<std::string, method> const method_map = {
|
||||
{ "GET", method::GET },
|
||||
{ "POST", method::POST },
|
||||
{ "DELETE", method::DELETE },
|
||||
{ "PUT", method::PUT },
|
||||
{ "PATCH", method::PATCH },
|
||||
{ "HEAD", method::HEAD },
|
||||
{ "OPTIONS", method::OPTIONS },
|
||||
{ "CONNECT", method::CONNECT },
|
||||
{ "TRACE", method::TRACE },
|
||||
{ "COPY", method::COPY },
|
||||
{ "LINK", method::LINK },
|
||||
{ "UNLINK", method::UNLINK },
|
||||
{ "PURGE", method::PURGE },
|
||||
{ "LOCK", method::LOCK },
|
||||
{ "UNLOCK", method::UNLOCK },
|
||||
{ "PROPFIND", method::PROPFIND },
|
||||
{ "VIEW", method::VIEW },
|
||||
{ "UNKNOWN", method::UNKNOWN }
|
||||
};
|
||||
|
||||
static std::unordered_map<http_method, std::string> const http_reverse_method_map = {
|
||||
{ http_method::GET, "GET" },
|
||||
{ http_method::POST, "POST" },
|
||||
{ http_method::DELETE, "DELETE" },
|
||||
{ http_method::PUT, "PUT" },
|
||||
{ http_method::PATCH, "PATCH" },
|
||||
{ http_method::HEAD, "HEAD" },
|
||||
{ http_method::OPTIONS, "OPTIONS" },
|
||||
{ http_method::CONNECT, "CONNECT" },
|
||||
{ http_method::TRACE, "TRACE" },
|
||||
{ http_method::COPY, "COPY" },
|
||||
{ http_method::LINK, "LINK" },
|
||||
{ http_method::UNLINK, "UNLINK" },
|
||||
{ http_method::PURGE, "PURGE" },
|
||||
{ http_method::LOCK, "LOCK" },
|
||||
{ http_method::UNLOCK, "UNLOCK" },
|
||||
{ http_method::PROPFIND, "PROPFIND" },
|
||||
{ http_method::VIEW, "VIEW" },
|
||||
{ http_method::UNKNOWN, "UNKNOWN" }
|
||||
static std::unordered_map<method, std::string> const reverse_method_map = {
|
||||
{ method::GET, "GET" },
|
||||
{ method::POST, "POST" },
|
||||
{ method::DELETE, "DELETE" },
|
||||
{ method::PUT, "PUT" },
|
||||
{ method::PATCH, "PATCH" },
|
||||
{ method::HEAD, "HEAD" },
|
||||
{ method::OPTIONS, "OPTIONS" },
|
||||
{ method::CONNECT, "CONNECT" },
|
||||
{ method::TRACE, "TRACE" },
|
||||
{ method::COPY, "COPY" },
|
||||
{ method::LINK, "LINK" },
|
||||
{ method::UNLINK, "UNLINK" },
|
||||
{ method::PURGE, "PURGE" },
|
||||
{ method::LOCK, "LOCK" },
|
||||
{ method::UNLOCK, "UNLOCK" },
|
||||
{ method::PROPFIND, "PROPFIND" },
|
||||
{ method::VIEW, "VIEW" },
|
||||
{ method::UNKNOWN, "UNKNOWN" }
|
||||
};
|
||||
|
||||
static std::unordered_map<int, std::string> const http_status_map = {
|
||||
static std::unordered_map<int, std::string> const status_map = {
|
||||
{ 100, "CONTINUE" },
|
||||
{ 101, "SWITCHING PROTOCOLS" },
|
||||
{ 200, "OK" },
|
||||
|
@ -196,6 +200,7 @@ static std::unordered_map<std::string, std::string> const mime_types = {
|
|||
{ "css", "text/css" },
|
||||
|
||||
{ "js", "application/javascript" },
|
||||
{ "json", "application/json" },
|
||||
{ "pdf", "application/pdf" },
|
||||
|
||||
{ "ico", "image/x-icon" },
|
||||
|
@ -212,13 +217,13 @@ static std::unordered_map<std::string, std::string> const mime_types = {
|
|||
{ "wmv", "video/x-ms-wmv" },
|
||||
};
|
||||
|
||||
enum http_version { HTTP_0_9,
|
||||
enum version { HTTP_0_9,
|
||||
HTTP_1_0,
|
||||
HTTP_1_1,
|
||||
HTTP_2_0,
|
||||
HTTP_3_0 };
|
||||
|
||||
static std::unordered_map<std::string, http_version> const http_version_map = {
|
||||
static std::unordered_map<std::string, version> const version_map = {
|
||||
// This is because HTTP 0.9 didn't specify version in the header
|
||||
{ "", HTTP_0_9 },
|
||||
{ "HTTP/0.9", HTTP_0_9 },
|
||||
|
@ -228,7 +233,7 @@ static std::unordered_map<std::string, http_version> const http_version_map = {
|
|||
{ "HTTP/3.0", HTTP_3_0 }
|
||||
};
|
||||
|
||||
static std::unordered_map<http_version, std::string> const http_reverse_version_map = {
|
||||
static std::unordered_map<version, std::string> const reverse_version_map = {
|
||||
{ HTTP_0_9, "HTTP/0.9" },
|
||||
{ HTTP_1_0, "HTTP/1.0" },
|
||||
{ HTTP_1_1, "HTTP/1.1" },
|
||||
|
@ -236,6 +241,4 @@ static std::unordered_map<http_version, std::string> const http_reverse_version_
|
|||
{ HTTP_3_0, "HTTP/3.0" }
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
|
@ -1,5 +1,8 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace anthracite::http {
|
||||
|
||||
class name_value {
|
||||
private:
|
||||
std::string _name;
|
||||
|
@ -26,13 +29,13 @@ public:
|
|||
virtual std::string to_string() { return ""; }
|
||||
};
|
||||
|
||||
class http_header : public name_value {
|
||||
class header : public name_value {
|
||||
public:
|
||||
http_header()
|
||||
header()
|
||||
: name_value()
|
||||
{
|
||||
}
|
||||
http_header(std::string name, std::string value)
|
||||
header(std::string name, std::string value)
|
||||
: name_value(name, value)
|
||||
{
|
||||
}
|
||||
|
@ -53,3 +56,5 @@ public:
|
|||
|
||||
std::string to_string() override { return name() + "=" + value(); }
|
||||
};
|
||||
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "constants.cpp"
|
||||
#include "header_query.cpp"
|
||||
#include "../socket.cpp"
|
||||
#include "../build/version.cpp"
|
|
@ -1,61 +0,0 @@
|
|||
#include "http.hpp"
|
||||
|
||||
class http_response {
|
||||
private:
|
||||
int _status_code;
|
||||
std::string& _content;
|
||||
std::string _filename;
|
||||
std::unordered_map<std::string, http_header> _headers; // kinda goofy, whatever
|
||||
|
||||
public:
|
||||
http_response(std::string& content, std::string filename, int status_code = http_status_codes::OK)
|
||||
: _content(content)
|
||||
, _status_code(status_code)
|
||||
, _filename(std::move(filename))
|
||||
{
|
||||
}
|
||||
|
||||
int status_code() { return _status_code; }
|
||||
|
||||
void add_header(http_header header, bool override_existing = true)
|
||||
{
|
||||
if (override_existing || _headers.find(header.name()) == _headers.end()) {
|
||||
_headers[header.name()] = header;
|
||||
}
|
||||
}
|
||||
|
||||
std::string& content()
|
||||
{
|
||||
return _content;
|
||||
}
|
||||
|
||||
std::string header_to_string()
|
||||
{
|
||||
std::string response = "";
|
||||
response += "HTTP/1.1 " + std::to_string(_status_code) + " " + http_status_map.find(_status_code)->second + "\r\n";
|
||||
std::string content_type = "text/html";
|
||||
std::string file_extension = _filename.substr(_filename.rfind('.') + 1);
|
||||
auto mime_type = mime_types.find(file_extension);
|
||||
if (mime_type != mime_types.end()) {
|
||||
content_type = mime_type->second;
|
||||
}
|
||||
|
||||
add_header(http_header("Content-Type", content_type), false);
|
||||
add_header(http_header("Content-Length", std::to_string(_content.length())), false);
|
||||
add_header(http_header("Server", ANTHRACITE_FULL_VERSION_STRING), false);
|
||||
add_header(http_header("Origin-Server", ANTHRACITE_FULL_VERSION_STRING), false);
|
||||
|
||||
for (auto header : _headers) {
|
||||
response += header.second.to_string();
|
||||
}
|
||||
|
||||
response += "\r\n";
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
std::string to_string()
|
||||
{
|
||||
return header_to_string() + _content;
|
||||
}
|
||||
};
|
|
@ -1,25 +1,11 @@
|
|||
#include "http.hpp"
|
||||
#include "request.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "../log/log.hpp"
|
||||
#include <stdio.h>
|
||||
|
||||
class http_request {
|
||||
private:
|
||||
enum parser_state { METHOD,
|
||||
PATH,
|
||||
QUERY_PARAM_NAME,
|
||||
QUERY_PARAM_VALUE,
|
||||
VERSION,
|
||||
HEADER_NAME,
|
||||
HEADER_VALUE,
|
||||
BODY_CONTENT };
|
||||
http_method _method;
|
||||
http_version _http_version;
|
||||
std::string _path;
|
||||
std::string _client_ipaddr;
|
||||
std::string _body_content;
|
||||
std::unordered_map<std::string, http_header> _headers; // kinda goofy, whatever
|
||||
std::unordered_map<std::string, query_param> _query_params; // kinda goofy, whatever
|
||||
namespace anthracite::http {
|
||||
|
||||
public:
|
||||
http_request(std::string& raw_data, std::string client_ip)
|
||||
request::request(std::string& raw_data, const std::string& client_ip)
|
||||
: _path(""), _client_ipaddr(client_ip)
|
||||
{
|
||||
|
||||
|
@ -31,10 +17,10 @@ public:
|
|||
switch (state) {
|
||||
case METHOD: {
|
||||
if (raw_data[i] == ' ') {
|
||||
if (http_method_map.find(scratch) == http_method_map.end()) {
|
||||
_method = http_method::UNKNOWN;
|
||||
if (method_map.find(scratch) == method_map.end()) {
|
||||
_method = method::UNKNOWN;
|
||||
} else {
|
||||
_method = http_method_map.find(scratch)->second;
|
||||
_method = method_map.find(scratch)->second;
|
||||
}
|
||||
scratch = "";
|
||||
state = PATH;
|
||||
|
@ -86,7 +72,7 @@ public:
|
|||
|
||||
case VERSION: {
|
||||
if (raw_data[i] == '\n') {
|
||||
_http_version = http_version_map.find(scratch)->second;
|
||||
_http_version = version_map.find(scratch)->second;
|
||||
scratch = "";
|
||||
state = HEADER_NAME;
|
||||
} else if (raw_data[i] != '\r') {
|
||||
|
@ -113,7 +99,7 @@ public:
|
|||
|
||||
case HEADER_VALUE: {
|
||||
if (raw_data[i] == '\n') {
|
||||
_headers[scratch] = http_header(scratch, scratch_2);
|
||||
_headers[scratch] = header(scratch, scratch_2);
|
||||
scratch = "";
|
||||
scratch_2 = "";
|
||||
state = HEADER_NAME;
|
||||
|
@ -129,21 +115,22 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
std::string path() { return _path; }
|
||||
std::string request::path() { return _path; }
|
||||
|
||||
http_method method() { return _method; }
|
||||
method request::get_method() { return _method; }
|
||||
|
||||
std::string client_ip() { return _client_ipaddr; }
|
||||
std::string request::client_ip() { return _client_ipaddr; }
|
||||
|
||||
http_version get_http_version() {
|
||||
version request::get_http_version() {
|
||||
return _http_version;
|
||||
}
|
||||
|
||||
bool is_supported_version() {
|
||||
return _http_version == HTTP_1_1 || _http_version == HTTP_1_0;
|
||||
bool request::is_supported_version() {
|
||||
//log::err << reverse_version_map.find(_http_version)->second << std::endl;
|
||||
return _http_version == HTTP_1_1 || _http_version == HTTP_1_0;
|
||||
}
|
||||
|
||||
bool close_connection() {
|
||||
bool request::close_connection() {
|
||||
const auto& header = _headers.find("Connection");
|
||||
const bool found = header != _headers.end();
|
||||
|
||||
|
@ -154,16 +141,16 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
std::string to_string()
|
||||
std::string request::to_string()
|
||||
{
|
||||
std::string response = "";
|
||||
response += http_reverse_method_map.find(_method)->second + " " + _path + "?";
|
||||
response += reverse_method_map.find(_method)->second + " " + _path + "?";
|
||||
|
||||
for (auto qp : _query_params) {
|
||||
response += qp.second.to_string() + "&";
|
||||
}
|
||||
|
||||
response += " " + http_reverse_version_map.find(_http_version)->second + "\r\n";
|
||||
response += " " + reverse_version_map.find(_http_version)->second + "\r\n";
|
||||
|
||||
for (auto header : _headers) {
|
||||
response += header.second.to_string();
|
||||
|
@ -174,4 +161,5 @@ public:
|
|||
|
||||
return response;
|
||||
}
|
||||
|
||||
};
|
39
src/http/request.hpp
Normal file
39
src/http/request.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "./header_query.hpp"
|
||||
#include "./constants.hpp"
|
||||
|
||||
namespace anthracite::http {
|
||||
|
||||
class request {
|
||||
private:
|
||||
enum parser_state { METHOD,
|
||||
PATH,
|
||||
QUERY_PARAM_NAME,
|
||||
QUERY_PARAM_VALUE,
|
||||
VERSION,
|
||||
HEADER_NAME,
|
||||
HEADER_VALUE,
|
||||
BODY_CONTENT };
|
||||
method _method;
|
||||
version _http_version;
|
||||
std::string _path;
|
||||
std::string _client_ipaddr;
|
||||
std::string _body_content;
|
||||
std::unordered_map<std::string, header> _headers; // kinda goofy, whatever
|
||||
std::unordered_map<std::string, query_param> _query_params; // kinda goofy, whatever
|
||||
|
||||
public:
|
||||
request(std::string& raw_data, const std::string& client_ip);
|
||||
std::string path();
|
||||
method get_method();
|
||||
std::string client_ip();
|
||||
version get_http_version();
|
||||
bool is_supported_version();
|
||||
bool close_connection();
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
};
|
58
src/http/response.cpp
Normal file
58
src/http/response.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#include "response.hpp"
|
||||
#include "../build_supp/version.cpp"
|
||||
|
||||
namespace anthracite::http {
|
||||
|
||||
response::response() {};
|
||||
|
||||
int response::status_code() { return _status_code; }
|
||||
|
||||
void response::add_body(const std::string body) {
|
||||
_content_noref = body;
|
||||
_content = &_content_noref;
|
||||
}
|
||||
|
||||
void response::add_body_ref(std::string* body) {
|
||||
_content = body;
|
||||
}
|
||||
|
||||
void response::add_header(header header, bool override_existing)
|
||||
{
|
||||
if (override_existing || _headers.find(header.name()) == _headers.end()) {
|
||||
_headers[header.name()] = header;
|
||||
}
|
||||
}
|
||||
|
||||
void response::add_status(int code) {
|
||||
_status_code = code;
|
||||
}
|
||||
|
||||
std::string& response::content()
|
||||
{
|
||||
return *_content;
|
||||
}
|
||||
|
||||
std::string response::header_to_string()
|
||||
{
|
||||
std::string response = "";
|
||||
response += "HTTP/1.1 " + std::to_string(_status_code) + " " + status_map.find(_status_code)->second + "\r\n";
|
||||
|
||||
add_header(header("Content-Length", std::to_string(_content->length())), false);
|
||||
add_header(header("Server", ANTHRACITE_FULL_VERSION_STRING), false);
|
||||
add_header(header("Origin-Server", ANTHRACITE_FULL_VERSION_STRING), false);
|
||||
|
||||
for (auto header : _headers) {
|
||||
response += header.second.to_string();
|
||||
}
|
||||
|
||||
response += "\r\n";
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
std::string response::to_string()
|
||||
{
|
||||
return header_to_string() + *_content;
|
||||
}
|
||||
|
||||
};
|
31
src/http/response.hpp
Normal file
31
src/http/response.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "header_query.hpp"
|
||||
#include "constants.hpp"
|
||||
#include <optional>
|
||||
|
||||
namespace anthracite::http {
|
||||
|
||||
class response {
|
||||
private:
|
||||
int _status_code;
|
||||
std::string* _content;
|
||||
std::string _content_noref;
|
||||
std::unordered_map<std::string, header> _headers; // kinda goofy, whatever
|
||||
|
||||
public:
|
||||
response();
|
||||
int status_code();
|
||||
|
||||
void add_body(const std::string body);
|
||||
void add_body_ref(std::string* body);
|
||||
void add_status(int);
|
||||
void add_header(header header, bool override_existing = true);
|
||||
std::string& content();
|
||||
std::string header_to_string();
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
};
|
24
src/log/log.cpp
Normal file
24
src/log/log.cpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "./log.hpp"
|
||||
|
||||
namespace anthracite::log {
|
||||
enum LOG_LEVEL Logger::_level = LOG_LEVEL_NONE;
|
||||
|
||||
// TODO: implement logger as a singleton to prevent duplicates
|
||||
Logger::Logger() = default;
|
||||
void Logger::initialize(enum LOG_LEVEL level) {
|
||||
_level = level;
|
||||
}
|
||||
|
||||
LogBuf::LogBuf(std::ostream& output_stream, const std::string& tag, enum LOG_LEVEL level) : _output_stream(output_stream), _tag(tag), _level(level) {}
|
||||
|
||||
int LogBuf::sync() {
|
||||
if (this->_level <= logger._level) {
|
||||
std::cout << "[" << this ->_tag << "] " << this->str();
|
||||
std::cout.flush();
|
||||
}
|
||||
this->str("");
|
||||
return 0;
|
||||
}
|
||||
};
|
53
src/log/log.hpp
Normal file
53
src/log/log.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace anthracite::log {
|
||||
enum LOG_LEVEL {
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR = 1,
|
||||
LOG_LEVEL_WARN = 2,
|
||||
LOG_LEVEL_INFO = 3,
|
||||
LOG_LEVEL_VERBOSE = 4,
|
||||
LOG_LEVEL_DEBUG = 5
|
||||
};
|
||||
|
||||
class Logger {
|
||||
friend class LogBuf;
|
||||
static enum LOG_LEVEL _level;
|
||||
public:
|
||||
|
||||
Logger();
|
||||
void initialize(enum LOG_LEVEL level);
|
||||
};
|
||||
|
||||
|
||||
class LogBuf : public std::stringbuf
|
||||
{
|
||||
std::string _tag;
|
||||
std::ostream& _output_stream;
|
||||
enum LOG_LEVEL _level;
|
||||
public:
|
||||
LogBuf(std::ostream& output_stream, const std::string& tag, enum LOG_LEVEL level);
|
||||
int sync() override;
|
||||
};
|
||||
|
||||
static class Logger logger{};
|
||||
|
||||
static class LogBuf errBuf{std::cerr, "EROR", LOG_LEVEL_ERROR};
|
||||
static std::ostream err(&errBuf);
|
||||
|
||||
static class LogBuf warnBuf{std::cerr, "WARN", LOG_LEVEL_WARN};
|
||||
static std::ostream warn(&warnBuf);
|
||||
|
||||
static class LogBuf infoBuf{std::cout, "INFO", LOG_LEVEL_INFO};
|
||||
static std::ostream info(&infoBuf);
|
||||
|
||||
static class LogBuf verboseBuf{std::cout, "VERB", LOG_LEVEL_VERBOSE};
|
||||
static std::ostream verbose(&verboseBuf);
|
||||
|
||||
static class LogBuf debugBuf{std::cout, "DEBG", LOG_LEVEL_DEBUG};
|
||||
static std::ostream debug(&debugBuf);
|
||||
};
|
79
src/main.cpp
79
src/main.cpp
|
@ -1,79 +0,0 @@
|
|||
#include "backends/file_backend.cpp"
|
||||
#include <condition_variable>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <netinet/in.h>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <sys/socket.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
|
||||
void log_request_and_response(http_request& req, std::unique_ptr<http_response>& resp);
|
||||
|
||||
constexpr int default_port = 80;
|
||||
constexpr int max_worker_threads = 128;
|
||||
|
||||
void handle_client(anthracite_socket s, backend& b, file_backend& fb, std::mutex& thread_wait_mutex, std::condition_variable& thread_wait_condvar, int& active_threads)
|
||||
{
|
||||
while (true) {
|
||||
std::string raw_request = s.recv_message(HTTP_HEADER_BYTES);
|
||||
if (raw_request == "") {
|
||||
break;
|
||||
}
|
||||
http_request req(raw_request, s.get_client_ip());
|
||||
std::unique_ptr<http_response> resp = req.is_supported_version() ? b.handle_request(req) : fb.handle_error(http_status_codes::HTTP_VERSION_NOT_SUPPORTED);
|
||||
log_request_and_response(req, resp);
|
||||
std::string header = resp->header_to_string();
|
||||
s.send_message(header);
|
||||
s.send_message(resp->content());
|
||||
resp.reset();
|
||||
if (req.close_connection()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
s.close_conn();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(thread_wait_mutex);
|
||||
active_threads--;
|
||||
}
|
||||
thread_wait_condvar.notify_one();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
auto args = std::span(argv, size_t(argc));
|
||||
int port_number = default_port;
|
||||
|
||||
if (argc > 1) {
|
||||
port_number = atoi(args[1]);
|
||||
}
|
||||
|
||||
std::cout << "Initializing Anthracite" << std::endl;
|
||||
anthracite_socket s(port_number);
|
||||
file_backend fb(argc > 2 ? args[2] : "./www");
|
||||
std::cout << "Initialization Complete" << std::endl;
|
||||
std::cout << "Listening for HTTP connections on port " << port_number << std::endl;
|
||||
|
||||
int active_threads = 0;
|
||||
std::mutex thread_wait_mutex;
|
||||
std::condition_variable thread_wait_condvar;
|
||||
|
||||
while (true) {
|
||||
s.wait_for_conn();
|
||||
std::unique_lock<std::mutex> lock(thread_wait_mutex);
|
||||
thread_wait_condvar.wait(lock, [active_threads] { return active_threads < max_worker_threads; });
|
||||
active_threads++;
|
||||
std::thread(handle_client, s, std::ref(fb), std::ref(fb), std::ref(thread_wait_mutex), std::ref(thread_wait_condvar), std::ref(active_threads)).detach();
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void log_request_and_response(http_request& req, std::unique_ptr<http_response>& resp)
|
||||
{
|
||||
std::cout << "[" << resp->status_code() << " " + http_status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http_reverse_method_map.find(req.method())->second + " " + req.path() << std::endl;
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
#include <arpa/inet.h>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
#include <malloc.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace anthracite::socket {
|
||||
|
||||
constexpr int MAX_QUEUE_LENGTH = 100;
|
||||
|
||||
|
@ -19,10 +19,13 @@ private:
|
|||
std::string client_ip;
|
||||
struct sockaddr_in client_addr {};
|
||||
socklen_t client_addr_len {};
|
||||
static constexpr struct timeval timeout_tv {
|
||||
.tv_sec = 5, .tv_usec = 0
|
||||
};
|
||||
|
||||
public:
|
||||
anthracite_socket(int port, int max_queue = MAX_QUEUE_LENGTH)
|
||||
: server_socket(socket(AF_INET, SOCK_STREAM, 0))
|
||||
: server_socket(::socket(AF_INET, SOCK_STREAM, 0))
|
||||
, client_ip("")
|
||||
{
|
||||
struct sockaddr_in address {};
|
||||
|
@ -30,23 +33,23 @@ public:
|
|||
address.sin_port = htons(port);
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
int x = 1;
|
||||
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
|
||||
bind(server_socket, (struct sockaddr*)&address, sizeof(address));
|
||||
int reuse_opt = 1;
|
||||
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse_opt, sizeof(reuse_opt));
|
||||
bind(server_socket, reinterpret_cast<struct sockaddr*>(&address), sizeof(address));
|
||||
|
||||
::listen(server_socket, max_queue);
|
||||
listen(server_socket, max_queue);
|
||||
}
|
||||
|
||||
void wait_for_conn()
|
||||
{
|
||||
client_ip = "";
|
||||
client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
|
||||
char ip_str[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, INET_ADDRSTRLEN);
|
||||
client_ip = std::string(ip_str);
|
||||
client_socket = accept(server_socket, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_len);
|
||||
std::array<char, INET_ADDRSTRLEN> ip_str { 0 };
|
||||
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str.data(), INET_ADDRSTRLEN);
|
||||
client_ip = std::string(ip_str.data());
|
||||
}
|
||||
|
||||
std::string get_client_ip()
|
||||
const std::string& get_client_ip()
|
||||
{
|
||||
return client_ip;
|
||||
}
|
||||
|
@ -71,18 +74,17 @@ public:
|
|||
return "";
|
||||
}
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
|
||||
char response[buffer_size + 1];
|
||||
int result = recv(client_socket, response, sizeof(response), 0);
|
||||
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout_tv, sizeof timeout_tv);
|
||||
std::vector<char> response(buffer_size + 1);
|
||||
ssize_t result = recv(client_socket, response.data(), buffer_size + 1, 0);
|
||||
|
||||
if (result < 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
response[buffer_size] = '\0';
|
||||
return std::string(response);
|
||||
return { response.data() };
|
||||
}
|
||||
};
|
||||
|
||||
};
|
Loading…
Reference in a new issue