Compare commits
No commits in common. "main" and "0.2.0" have entirely different histories.
|
@ -1,19 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
if [[ ! -d "/home/nickorlow/programming/personal/anthracite" ]]; then
|
|
||||||
echo "Cannot find source directory; Did you move it?"
|
|
||||||
echo "(Looking for "/home/nickorlow/programming/personal/anthracite")"
|
|
||||||
echo 'Cannot force reload with this script - use "direnv reload" manually and then try again'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# rebuild the cache forcefully
|
|
||||||
_nix_direnv_force_reload=1 direnv exec "/home/nickorlow/programming/personal/anthracite" true
|
|
||||||
|
|
||||||
# Update the mtime for .envrc.
|
|
||||||
# This will cause direnv to reload again - but without re-building.
|
|
||||||
touch "/home/nickorlow/programming/personal/anthracite/.envrc"
|
|
||||||
|
|
||||||
# Also update the timestamp of whatever profile_rc we have.
|
|
||||||
# This makes sure that we know we are up to date.
|
|
||||||
touch -r "/home/nickorlow/programming/personal/anthracite/.envrc" "/home/nickorlow/programming/personal/anthracite/.direnv"/*.rc
|
|
|
@ -1 +0,0 @@
|
||||||
/nix/store/gr8ifjf51b4w3v62vvinq4s8w97pn3ag-nix-shell-env
|
|
|
@ -1,56 +0,0 @@
|
||||||
name: Docker Build & Publish
|
|
||||||
|
|
||||||
on: [push, workflow_dispatch]
|
|
||||||
#test11111
|
|
||||||
env:
|
|
||||||
CTR_REGISTRY: git.nickorlow.com
|
|
||||||
IMAGE_NAME: "anthracite"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Docker buildx
|
|
||||||
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
|
|
||||||
|
|
||||||
- name: Get Release Tag
|
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Write Version
|
|
||||||
run: |
|
|
||||||
echo Building with version number $RELEASE_VERSION
|
|
||||||
echo $RELEASE_VERSION > src/build/version.txt
|
|
||||||
|
|
||||||
- name: Log into registry ${{ env.CTR_REGISTRY }}
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
|
|
||||||
with:
|
|
||||||
registry: ${{ env.CTR_REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract Docker metadata
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
|
||||||
with:
|
|
||||||
images: ${{ env.CTR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
id: build-and-push
|
|
||||||
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
2
.github/workflows/docker-publish.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
- name: Write Version
|
- name: Write Version
|
||||||
run: |
|
run: |
|
||||||
echo Building with version number $RELEASE_VERSION
|
echo Building with version number $RELEASE_VERSION
|
||||||
echo $RELEASE_VERSION > build_supp/version.txt
|
echo $RELEASE_VERSION > src/build/version.txt
|
||||||
|
|
||||||
- name: Log into registry ${{ env.REGISTRY }}
|
- name: Log into registry ${{ env.REGISTRY }}
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
|
|
6
.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
build/
|
anthracite
|
||||||
build_supp/version.cpp
|
src/error_pages/
|
||||||
.cache/
|
src/build/version.cpp
|
||||||
|
|
30
CHANGELOG.md
|
@ -1,33 +1,3 @@
|
||||||
# 0.3.0
|
|
||||||
- SSL support via OpenSSL
|
|
||||||
- Added "Thread Manager" class to allow for multiple (or custom) threading models (process per thread, event loop)
|
|
||||||
- Default (and only included) threading model is now event loop
|
|
||||||
- Rewrote request parser for readability and speed
|
|
||||||
- Rewrote socket system for readability and speed
|
|
||||||
- Added improved logging with different log levels
|
|
||||||
- Separated anthracite into libanthracite and anthracite-bin to allow for other projects to implement anthracite (example in ./src/api_main.cpp)
|
|
||||||
- Cleaned up code and seperated most code into headers & source
|
|
||||||
- Revamped build system to use CMake properly
|
|
||||||
- Moved CI/CD over to Forgejo
|
|
||||||
- Added simple config file system (will be completely replaced by v1.0)
|
|
||||||
- General system stability improvements were made to enhance the user's experience
|
|
||||||
|
|
||||||
## HTTP Request Parser Rewrite
|
|
||||||
|
|
||||||
The following benchmark (source in ./tests/speed_tests.cpp) shows the speed
|
|
||||||
improvements made between 0.2.0 and 0.3.0, as well as comparison to boost's
|
|
||||||
parsing library.
|
|
||||||
|
|
||||||
It should probably be noted that Boost's parser can do a lot more than mine
|
|
||||||
and is likely slower for good reason. Also, these were single runs but
|
|
||||||
subsequent runs showed similar results.
|
|
||||||
|
|
||||||
| Parser Tested | RPS |
|
|
||||||
|--------------------|--------------|
|
|
||||||
| Anthracite 0.2.0 | 688,042 |
|
|
||||||
| Anthracite 0.3.0 | 27,027,000 |
|
|
||||||
| Boost Beast | 1,023,230 |
|
|
||||||
|
|
||||||
# 0.2.0 Fifth Pre-Release
|
# 0.2.0 Fifth Pre-Release
|
||||||
- Added true HTTP/1.1 support with persistent connections
|
- Added true HTTP/1.1 support with persistent connections
|
||||||
- Added HTTP/1.0 vs HTTP/1.1 test to benchmarking suite
|
- Added HTTP/1.0 vs HTTP/1.1 test to benchmarking suite
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
|
||||||
|
|
||||||
project(anthracite)
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
find_package(OpenSSL REQUIRED)
|
|
||||||
|
|
||||||
configure_file(build_supp/version.txt version.txt COPYONLY)
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
COMMAND ../build_supp/version.sh
|
|
||||||
DEPENDS version.txt
|
|
||||||
OUTPUT ${CMAKE_BINARY_DIR}/version.cpp
|
|
||||||
COMMENT "Generated supplemental build files (version)"
|
|
||||||
)
|
|
||||||
|
|
||||||
add_custom_target(build-supplemental
|
|
||||||
COMMAND cd ../build_supp && python3 ./error_gen.py
|
|
||||||
COMMAND mkdir -p www && cp -r ../default_www/regular/* ./www/
|
|
||||||
COMMAND cp ../build_supp/default_config.cfg ./anthracite.cfg
|
|
||||||
DEPENDS ../default_www/regular/* build_supp/error_gen.py ${CMAKE_BINARY_DIR}/version.cpp
|
|
||||||
COMMENT "Generated supplemental build files (default www dir + default config + error pages)"
|
|
||||||
)
|
|
||||||
|
|
||||||
FILE(GLOB LIB_SOURCES lib/*.cpp lib/**/*.cpp)
|
|
||||||
add_library(anthracite ${LIB_SOURCES} ${CMAKE_BINARY_DIR}/version.cpp)
|
|
||||||
add_dependencies(anthracite build-supplemental)
|
|
||||||
target_link_libraries(anthracite OpenSSL::SSL OpenSSL::Crypto)
|
|
||||||
target_include_directories(anthracite PUBLIC ${OPENSSL_INCLUDE_DIR})
|
|
||||||
|
|
||||||
add_executable(anthracite-bin src/file_main.cpp)
|
|
||||||
target_link_libraries(anthracite-bin anthracite)
|
|
||||||
add_dependencies(anthracite-bin build-supplemental)
|
|
||||||
add_dependencies(anthracite-bin anthracite)
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(
|
|
||||||
googletest
|
|
||||||
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_MakeAvailable(googletest)
|
|
||||||
|
|
||||||
file(GLOB TESTS_SRC CONFIGURE_DEPENDS "tests/*.cpp")
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
add_custom_target(test_files
|
|
||||||
COMMAND cp -r ../tests/test_files .
|
|
||||||
DEPENDS ../tests/test_files/*
|
|
||||||
COMMENT "Copied test resource files"
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(
|
|
||||||
tests
|
|
||||||
${TESTS_SRC}
|
|
||||||
)
|
|
||||||
add_dependencies(tests anthracite)
|
|
||||||
add_dependencies(tests test_files)
|
|
||||||
|
|
||||||
target_link_libraries(
|
|
||||||
tests
|
|
||||||
GTest::gtest_main
|
|
||||||
)
|
|
||||||
target_link_libraries(
|
|
||||||
tests
|
|
||||||
anthracite
|
|
||||||
)
|
|
||||||
|
|
||||||
include(GoogleTest)
|
|
||||||
gtest_discover_tests(tests)
|
|
29
Dockerfile
|
@ -1,23 +1,12 @@
|
||||||
FROM alpine AS build-env
|
FROM alpine as build-env
|
||||||
|
|
||||||
RUN apk add --no-cache build-base python3 cmake openssl-dev
|
RUN apk add --no-cache build-base python3
|
||||||
COPY ./src ./src
|
COPY ./src/ .
|
||||||
COPY ./lib ./lib
|
RUN make build-release
|
||||||
COPY ./tests ./tests
|
|
||||||
COPY ./default_www ./default_www
|
|
||||||
COPY ./build_supp ./build_supp
|
|
||||||
COPY ./CMakeLists.txt .
|
|
||||||
|
|
||||||
|
|
||||||
RUN mkdir build
|
|
||||||
WORKDIR build
|
|
||||||
RUN cmake -DCMAKE_BUILD_TYPE=Release ..
|
|
||||||
RUN make anthracite-bin
|
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
RUN apk add --no-cache libgcc libstdc++
|
RUN apk add --no-cache build-base
|
||||||
COPY --from=build-env /build/anthracite-bin /anthracite-bin
|
COPY --from=build-env /anthracite /anthracite
|
||||||
COPY --from=build-env /build/error_pages /error_pages
|
COPY --from=build-env /www /www
|
||||||
COPY --from=build-env /build_supp/default_config.cfg /anthracite.cfg
|
COPY --from=build-env /error_pages /error_pages
|
||||||
COPY /default_www/docker /www
|
CMD ["/anthracite"]
|
||||||
CMD ["/anthracite-bin"]
|
|
||||||
|
|
38
README.md
|
@ -1,28 +1,28 @@
|
||||||
# Anthracite
|
# Anthracite
|
||||||
|
A simple web server written in C++. Supports HTTP 1.0 & 1.1.
|
||||||
Anthracite is an extensible, low-dependency, fast web server.
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
To build/develop Anthracite, you must have C++20, OpenSSL, CMake, Make, and Python3 installed.
|
To build/develop Anthracite, you must have C++20, Make, and Python3 installed.
|
||||||
|
|
||||||
Create a `build/` directory, run `cmake ..`, and then `make` to build.
|
You can run Anthracite with: `make run`
|
||||||
|
|
||||||
## Features
|
## Todo
|
||||||
|
- [x] HTTP/1.0
|
||||||
- HTTP/1.0 & HTTP/1.1 Support
|
- [x] Serve HTML Pages
|
||||||
- SSL via OpenSSL
|
- [x] Properly parse HTTP requests
|
||||||
- Event loop thread management
|
- [x] Add module-based backend system for handling requests
|
||||||
- libanthracite library for reating custom webservers
|
- [x] Multithreading
|
||||||
- Configuration through configuration file
|
- [x] HTTP/1.1
|
||||||
- Minimal dependencies (only OpenSSL & stantart library so far)
|
- [ ] Improve benchmarking infrastructure
|
||||||
|
- [ ] Faster parsing
|
||||||
## Roadmap
|
- [ ] Fix glaring security issues
|
||||||
- HTTP/2
|
- [ ] Proper error handling
|
||||||
- HTTP/3
|
- [ ] User configuration
|
||||||
- More threading modes
|
- [ ] Build out module-based backend system for handling requests
|
||||||
- Proxy backend
|
- [ ] HTTP/2
|
||||||
- Security/Error handling audit
|
- [ ] Enhance logging
|
||||||
|
- [ ] Cleanup (this one will never truly be done)
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
log_level INFO
|
|
||||||
|
|
||||||
http 8080 1000 blocking
|
|
||||||
|
|
||||||
event_loop 6 10000
|
|
||||||
www_dir ./www
|
|
|
@ -1 +0,0 @@
|
||||||
0.3.0
|
|
|
@ -1,9 +0,0 @@
|
||||||
<head>
|
|
||||||
<title>Anthracite</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
||||||
</head>
|
|
||||||
<center>
|
|
||||||
<h1>Anthracite is Running in Docker!</h1>
|
|
||||||
<p>If you are seeing this page, then Anthracite is configured correctly!</p>
|
|
||||||
<p>Add files to the "www" directory in your build step to begin serving your website.</p>
|
|
||||||
</center>
|
|
Before Width: | Height: | Size: 319 B |
|
@ -1,9 +0,0 @@
|
||||||
<head>
|
|
||||||
<title>Anthracite</title>
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
||||||
</head>
|
|
||||||
<center>
|
|
||||||
<h1>Anthracite is Running in Docker Compose!</h1>
|
|
||||||
<p>If you are seeing this page, then Anthracite is configured correctly!</p>
|
|
||||||
<p>Bind-mount a directory to "/www" to begin serving your website.</p>
|
|
||||||
</center>
|
|
Before Width: | Height: | Size: 319 B |
|
@ -1,12 +0,0 @@
|
||||||
services:
|
|
||||||
anthracite-web:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- type: bind
|
|
||||||
source: ./default_www/docker_compose/
|
|
||||||
target: /www
|
|
||||||
- type: bind
|
|
||||||
source: ./build_supp/default_config.cfg
|
|
||||||
target: /anthracite.cfg
|
|
|
@ -1,2 +0,0 @@
|
||||||
git ls-files -- '*.cpp' '*.h' | xargs clang-format -i -style=file
|
|
||||||
git diff --exit-code --color
|
|
|
@ -1,91 +0,0 @@
|
||||||
#include "./file_backend.hpp"
|
|
||||||
#include "../log/log.hpp"
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
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;
|
|
||||||
if (file_info == file_cache.end()) {
|
|
||||||
return handle_error(http::status_codes::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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));
|
|
||||||
|
|
||||||
while (cur != fin) {
|
|
||||||
auto p = cur->path();
|
|
||||||
std::string filename = p.string();
|
|
||||||
std::stringstream buffer;
|
|
||||||
std::ifstream stream(filename);
|
|
||||||
buffer << stream.rdbuf();
|
|
||||||
file_cache[filename] = buffer.str();
|
|
||||||
log::verbose << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl;
|
|
||||||
++cur;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void file_backend::populate_cache()
|
|
||||||
{
|
|
||||||
populate_cache_dir(file_dir);
|
|
||||||
populate_cache_dir("./error_pages/");
|
|
||||||
}
|
|
||||||
|
|
||||||
file_backend::file_backend(std::string dir)
|
|
||||||
: file_dir(std::move(dir))
|
|
||||||
{
|
|
||||||
populate_cache();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<http::response> file_backend::handle_request(http::request& req)
|
|
||||||
{
|
|
||||||
return handle_request_cache(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
if (file_info == file_cache.end()) {
|
|
||||||
status = http::status_codes::NOT_FOUND;
|
|
||||||
filename = "./error_pages/404.html";
|
|
||||||
file_info = file_cache.find(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,22 +0,0 @@
|
||||||
#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);
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,200 +0,0 @@
|
||||||
#include "request.hpp"
|
|
||||||
#include "../log/log.hpp"
|
|
||||||
#include "constants.hpp"
|
|
||||||
#include <cstring>
|
|
||||||
#include <map>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
namespace anthracite::http {
|
|
||||||
|
|
||||||
void request::parse_header(std::string& raw_line)
|
|
||||||
{
|
|
||||||
auto delim_pos = raw_line.find_first_of(':');
|
|
||||||
auto value_pos = raw_line.find_first_not_of(' ', delim_pos + 1);
|
|
||||||
|
|
||||||
std::string header_name = raw_line.substr(0, delim_pos);
|
|
||||||
std::string header_val = raw_line.substr(value_pos);
|
|
||||||
|
|
||||||
_headers[header_name] = header_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void request::parse_query_param(std::string& raw_param)
|
|
||||||
{
|
|
||||||
auto delim_pos = raw_param.find_first_of('=');
|
|
||||||
auto value_pos = delim_pos + 1;
|
|
||||||
|
|
||||||
std::string query_name = raw_param.substr(0, delim_pos);
|
|
||||||
std::string query_val = raw_param.substr(value_pos);
|
|
||||||
|
|
||||||
_query_params[query_name] = query_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void request::parse_path(char* raw_path)
|
|
||||||
{
|
|
||||||
char* saveptr = nullptr;
|
|
||||||
char* tok = strtok_r(raw_path, "?", &saveptr);
|
|
||||||
|
|
||||||
if (tok) {
|
|
||||||
_path = tok;
|
|
||||||
}
|
|
||||||
|
|
||||||
tok = strtok_r(nullptr, "?", &saveptr);
|
|
||||||
while (tok) {
|
|
||||||
std::string rtok(tok);
|
|
||||||
parse_query_param(rtok);
|
|
||||||
tok = strtok_r(nullptr, "?", &saveptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void request::parse_request_line(char* raw_line)
|
|
||||||
{
|
|
||||||
request_line_parser_state state = METHOD;
|
|
||||||
|
|
||||||
char* saveptr = nullptr;
|
|
||||||
char* tok = strtok_r(raw_line, " \r", &saveptr);
|
|
||||||
|
|
||||||
while (tok) {
|
|
||||||
switch (state) {
|
|
||||||
case METHOD: {
|
|
||||||
auto search = method_map.find(tok);
|
|
||||||
if (search != method_map.end()) {
|
|
||||||
_method = search->second;
|
|
||||||
} else {
|
|
||||||
_method = method::UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = PATH;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
case PATH: {
|
|
||||||
std::string str_tok(tok);
|
|
||||||
parse_path(tok);
|
|
||||||
state = VERSION;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
case VERSION: {
|
|
||||||
auto search = version_map.find(tok);
|
|
||||||
if (search != version_map.end()) {
|
|
||||||
_http_version = search->second;
|
|
||||||
} else {
|
|
||||||
_http_version = version::HTTP_1_0;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
tok = strtok_r(nullptr, " \r", &saveptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request::request(std::string& raw_data, const std::string& client_ip)
|
|
||||||
: _path("")
|
|
||||||
, _client_ipaddr(client_ip)
|
|
||||||
{
|
|
||||||
|
|
||||||
parser_state state = REQUEST_LINE;
|
|
||||||
|
|
||||||
char* saveptr = nullptr;
|
|
||||||
char* tok = strtok_r(raw_data.data(), "\r\n", &saveptr);
|
|
||||||
|
|
||||||
while (tok && state != BODY_CONTENT) {
|
|
||||||
switch (state) {
|
|
||||||
case REQUEST_LINE: {
|
|
||||||
parse_request_line(tok);
|
|
||||||
state = HEADERS;
|
|
||||||
tok = strtok_r(nullptr, "\n", &saveptr);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
case HEADERS: {
|
|
||||||
if (tok[0] == '\r') {
|
|
||||||
state = BODY_CONTENT;
|
|
||||||
} else {
|
|
||||||
std::string rtok(tok);
|
|
||||||
rtok.pop_back();
|
|
||||||
parse_header(rtok);
|
|
||||||
tok = strtok_r(nullptr, "\n", &saveptr);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
case BODY_CONTENT:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tok = strtok_r(nullptr, "", &saveptr);
|
|
||||||
if (tok) {
|
|
||||||
_body_content = std::string(tok);
|
|
||||||
}
|
|
||||||
// if (getline(line_stream, line, '\0')) {
|
|
||||||
// _body_content = line;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string request::path() { return _path; }
|
|
||||||
|
|
||||||
method request::get_method() { return _method; }
|
|
||||||
|
|
||||||
std::string request::client_ip() { return _client_ipaddr; }
|
|
||||||
|
|
||||||
version request::get_http_version()
|
|
||||||
{
|
|
||||||
return _http_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool request::is_supported_version()
|
|
||||||
{
|
|
||||||
return _http_version == HTTP_1_1 || _http_version == HTTP_1_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool request::close_connection()
|
|
||||||
{
|
|
||||||
const auto& header = _headers.find("Connection");
|
|
||||||
const bool found = header != _headers.end();
|
|
||||||
|
|
||||||
if (found && header->second == "close") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string request::to_string()
|
|
||||||
{
|
|
||||||
std::string response = "";
|
|
||||||
response += reverse_method_map.find(_method)->second + " " + _path;
|
|
||||||
|
|
||||||
if (_query_params.size() > 0) {
|
|
||||||
response += "?";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto qp_map = std::map(_query_params.begin(), _query_params.end());
|
|
||||||
auto qp = qp_map.begin();
|
|
||||||
while (qp != qp_map.end()) {
|
|
||||||
response += qp->first + "=" + qp->second;
|
|
||||||
if (++qp != qp_map.end()) {
|
|
||||||
response += "&";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response += " " + reverse_version_map.find(_http_version)->second + "\r\n";
|
|
||||||
|
|
||||||
if (_headers.size() == 0) {
|
|
||||||
response += "\r\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto hd_map = std::map(_headers.begin(), _headers.end());
|
|
||||||
auto hd = hd_map.begin();
|
|
||||||
while (hd != hd_map.end()) {
|
|
||||||
response += hd->first + ": " + hd->second + "\r\n";
|
|
||||||
if (++hd == hd_map.end()) {
|
|
||||||
response += "\r\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response += _body_content;
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,46 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include "./header_query.hpp"
|
|
||||||
#include "./constants.hpp"
|
|
||||||
|
|
||||||
namespace anthracite::http {
|
|
||||||
|
|
||||||
class request {
|
|
||||||
private:
|
|
||||||
enum request_line_parser_state {
|
|
||||||
METHOD, PATH, VERSION
|
|
||||||
};
|
|
||||||
|
|
||||||
enum parser_state {
|
|
||||||
REQUEST_LINE,
|
|
||||||
HEADERS,
|
|
||||||
BODY_CONTENT
|
|
||||||
};
|
|
||||||
|
|
||||||
method _method;
|
|
||||||
version _http_version;
|
|
||||||
std::string _path;
|
|
||||||
std::string _client_ipaddr;
|
|
||||||
std::string _body_content;
|
|
||||||
std::unordered_map<std::string, std::string> _headers;
|
|
||||||
std::unordered_map<std::string, std::string> _query_params;
|
|
||||||
|
|
||||||
void parse_request_line(char* raw_line);
|
|
||||||
void parse_header(std::string& raw_line);
|
|
||||||
void parse_path(char* raw_path);
|
|
||||||
void parse_query_param(std::string& raw_param);
|
|
||||||
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,62 +0,0 @@
|
||||||
#include "response.hpp"
|
|
||||||
#include "../version.hpp"
|
|
||||||
#include "./constants.hpp"
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include "header_query.hpp"
|
|
||||||
|
|
||||||
namespace anthracite::http {
|
|
||||||
|
|
||||||
class response {
|
|
||||||
private:
|
|
||||||
int _status_code;
|
|
||||||
std::string* _content;
|
|
||||||
std::string _content_noref;
|
|
||||||
std::unordered_map<std::string, header> _headers;
|
|
||||||
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,36 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Logger::log_request_and_response(http::request& req, std::unique_ptr<http::response>& resp, uint32_t micros)
|
|
||||||
{
|
|
||||||
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() << " in " << micros << " usecs" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
char thread_name[100];
|
|
||||||
pthread_getname_np(pthread_self(), thread_name, 100);
|
|
||||||
_output_stream << "[" << this->_tag << "] [" << syscall(SYS_gettid) << ":" << thread_name << "] " << this->str();
|
|
||||||
_output_stream.flush();
|
|
||||||
}
|
|
||||||
this->str("");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,60 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <ostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include "../http/request.hpp"
|
|
||||||
#include "../http/response.hpp"
|
|
||||||
|
|
||||||
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);
|
|
||||||
void log_request_and_response(http::request& req, std::unique_ptr<http::response>& resp, uint32_t micros);
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,126 +0,0 @@
|
||||||
#include "./openssl_socket.hpp"
|
|
||||||
#include "../log/log.hpp"
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <array>
|
|
||||||
#include <exception>
|
|
||||||
#include <iostream>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <string>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace anthracite::socket {
|
|
||||||
|
|
||||||
openssl_listener::openssl_listener(std::string& key_path, std::string& cert_path, int port, int max_queue, bool nonblocking)
|
|
||||||
: listener(port, max_queue, nonblocking)
|
|
||||||
{
|
|
||||||
const SSL_METHOD* method = TLS_server_method();
|
|
||||||
|
|
||||||
_context = SSL_CTX_new(method);
|
|
||||||
|
|
||||||
if (!_context) {
|
|
||||||
log::err << "Unable to initialize SSL" << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_CTX_use_certificate_file(_context, cert_path.c_str(), SSL_FILETYPE_PEM) <= 0) {
|
|
||||||
log::err << "Unable to open Cert file at: " << cert_path << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_CTX_use_PrivateKey_file(_context, key_path.c_str(), SSL_FILETYPE_PEM) <= 0) {
|
|
||||||
log::err << "Unable to open Key file at: " << key_path << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool openssl_listener::wait_for_conn(server** client_sock_p)
|
|
||||||
{
|
|
||||||
struct sockaddr_in client_addr {};
|
|
||||||
socklen_t client_addr_len;
|
|
||||||
|
|
||||||
int csock = accept(_sock_fd, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_len);
|
|
||||||
|
|
||||||
if (csock > 0) {
|
|
||||||
std::array<char, INET6_ADDRSTRLEN> ip_str { 0 };
|
|
||||||
|
|
||||||
if (inet_ntop(AF_INET, &client_addr.sin_addr, ip_str.data(), INET_ADDRSTRLEN) == NULL) {
|
|
||||||
if (inet_ntop(AF_INET6, &client_addr.sin_addr, ip_str.data(), INET6_ADDRSTRLEN) == NULL) {
|
|
||||||
log::warn << "Unable to decode client's IP address" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SSL* ssl = SSL_new(_context);
|
|
||||||
|
|
||||||
if (ssl == NULL) {
|
|
||||||
for (int i = 0; i < 5 && close(csock) != 0; ++i)
|
|
||||||
;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_set_fd(ssl, csock) == 0) {
|
|
||||||
SSL_free(ssl);
|
|
||||||
for (int i = 0; i < 5 && close(csock) != 0; ++i)
|
|
||||||
;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SSL_accept(ssl) <= 0) {
|
|
||||||
log::warn << "Unable to open SSL connection with client" << std::endl;
|
|
||||||
SSL_free(ssl);
|
|
||||||
for (int i = 0; i < 5 && close(csock) != 0; ++i)
|
|
||||||
;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string client_ip = std::string(ip_str.data());
|
|
||||||
*client_sock_p = new openssl_server(csock, client_ip, _nonblocking, ssl);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openssl_listener::~openssl_listener() {}
|
|
||||||
|
|
||||||
openssl_server::openssl_server(int sock_fd, std::string client_ip, bool nonblocking, SSL* ssl)
|
|
||||||
: server(sock_fd, client_ip, nonblocking)
|
|
||||||
, _ssl(ssl)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
openssl_server::~openssl_server()
|
|
||||||
{
|
|
||||||
SSL_shutdown(_ssl);
|
|
||||||
SSL_free(_ssl);
|
|
||||||
}
|
|
||||||
|
|
||||||
void openssl_server::send_message(const std::string& msg)
|
|
||||||
{
|
|
||||||
SSL_write(_ssl, &msg[0], msg.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string openssl_server::recv_message(int buffer_size)
|
|
||||||
{
|
|
||||||
// Ignored because it's nonfatal, just slower
|
|
||||||
int nodelay_opt = 1;
|
|
||||||
(void)setsockopt(_sock_fd, SOL_TCP, TCP_NODELAY, &nodelay_opt, sizeof(nodelay_opt));
|
|
||||||
|
|
||||||
std::vector<char> response(buffer_size + 1);
|
|
||||||
ssize_t result = SSL_read(_ssl, response.data(), buffer_size + 1);
|
|
||||||
|
|
||||||
if (result < 1) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
response[buffer_size] = '\0';
|
|
||||||
return { response.data() };
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "./socket.hpp"
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
|
|
||||||
namespace anthracite::socket {
|
|
||||||
class openssl_server : public server{
|
|
||||||
private:
|
|
||||||
SSL* _ssl;
|
|
||||||
public:
|
|
||||||
openssl_server(int sock_fd, std::string client_ip, bool nonblocking, SSL* ssl);
|
|
||||||
~openssl_server();
|
|
||||||
|
|
||||||
void send_message(const std::string& msg) override;
|
|
||||||
std::string recv_message(int buffer_size) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class openssl_listener : public listener {
|
|
||||||
private:
|
|
||||||
SSL_CTX* _context;
|
|
||||||
|
|
||||||
public:
|
|
||||||
openssl_listener(std::string& key_path, std::string& cert_path, int port, int max_queue_length, bool nonblocking);
|
|
||||||
~openssl_listener();
|
|
||||||
|
|
||||||
bool wait_for_conn(server** client_sock_) override;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,144 +0,0 @@
|
||||||
#include "./socket.hpp"
|
|
||||||
#include "../log/log.hpp"
|
|
||||||
#include "assert.h"
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <array>
|
|
||||||
#include <exception>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <string>
|
|
||||||
#include <sys/epoll.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace anthracite::socket {
|
|
||||||
|
|
||||||
socket::socket(bool nonblocking)
|
|
||||||
: _nonblocking(nonblocking)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
listener::listener(int port, int max_queue, bool nonblocking)
|
|
||||||
: socket(nonblocking)
|
|
||||||
, _port(port)
|
|
||||||
{
|
|
||||||
_sock_fd = ::socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
|
|
||||||
if (_sock_fd == -1) {
|
|
||||||
log::err << "Listener was unable to open a socket" << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in address {};
|
|
||||||
address.sin_family = AF_INET;
|
|
||||||
address.sin_port = htons(_port);
|
|
||||||
address.sin_addr.s_addr = INADDR_ANY;
|
|
||||||
|
|
||||||
int reuse_opt = 1;
|
|
||||||
if (setsockopt(_sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_opt, sizeof(reuse_opt)) < 0) {
|
|
||||||
log::err << "Listener was unable to set SO_REUSEADDR" << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bind(_sock_fd, reinterpret_cast<struct sockaddr*>(&address), sizeof(address)) != 0) {
|
|
||||||
log::err << "Listener was unable to bind to address" << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fcntl(_sock_fd, F_SETFL, O_NONBLOCK) == -1) {
|
|
||||||
log::err << "Listener was unable to fcntl(O_NONBLOCK)" << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listen(_sock_fd, max_queue) == -1) {
|
|
||||||
log::err << "Listener was unable to begin listening" << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool listener::wait_for_conn(server** client_sock_p)
|
|
||||||
{
|
|
||||||
struct sockaddr_in client_addr {};
|
|
||||||
socklen_t client_addr_len;
|
|
||||||
|
|
||||||
int csock = accept(_sock_fd, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_len);
|
|
||||||
|
|
||||||
if (csock > 0) {
|
|
||||||
std::array<char, INET6_ADDRSTRLEN> ip_str { 0 };
|
|
||||||
|
|
||||||
if (inet_ntop(AF_INET, &client_addr.sin_addr, ip_str.data(), INET_ADDRSTRLEN) == NULL) {
|
|
||||||
if (inet_ntop(AF_INET6, &client_addr.sin_addr, ip_str.data(), INET6_ADDRSTRLEN) == NULL) {
|
|
||||||
log::warn << "Unable to decode client's IP address" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string client_ip = std::string(ip_str.data());
|
|
||||||
*client_sock_p = new server(csock, client_ip, _nonblocking);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server::server(int sock_fd, std::string client_ip, bool nonblocking)
|
|
||||||
: _sock_fd(sock_fd)
|
|
||||||
, _client_ip(std::move(client_ip))
|
|
||||||
, socket(nonblocking)
|
|
||||||
{
|
|
||||||
if (_nonblocking) {
|
|
||||||
if (fcntl(_sock_fd, F_SETFL, O_NONBLOCK) == -1) {
|
|
||||||
log::err << "Server was unable to fcntl(O_NONBLOCK)" << std::endl;
|
|
||||||
throw std::exception();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void server::send_message(const std::string& msg)
|
|
||||||
{
|
|
||||||
// Ignored because if we fail to send, it probably means
|
|
||||||
// a HUP will occur and it'll be closed. TODO: Just close
|
|
||||||
// it here and add a return value
|
|
||||||
(void)send(_sock_fd, &msg[0], msg.length(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string server::recv_message(int buffer_size)
|
|
||||||
{
|
|
||||||
// Ignored because it's nonfatal, just slower
|
|
||||||
int nodelay_opt = 1;
|
|
||||||
(void)setsockopt(_sock_fd, SOL_TCP, TCP_NODELAY, &nodelay_opt, sizeof(nodelay_opt));
|
|
||||||
|
|
||||||
std::vector<char> response(buffer_size + 1);
|
|
||||||
ssize_t result = recv(_sock_fd, response.data(), buffer_size + 1, 0);
|
|
||||||
|
|
||||||
if (result < 1) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
response[buffer_size] = '\0';
|
|
||||||
return { response.data() };
|
|
||||||
}
|
|
||||||
|
|
||||||
server::~server()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 5 && close(_sock_fd) != 0; ++i)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
listener::~listener()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 5 && close(_sock_fd) != 0; ++i)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& server::client_ip()
|
|
||||||
{
|
|
||||||
return _client_ip;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,51 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <string>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
namespace anthracite::socket {
|
|
||||||
|
|
||||||
class socket {
|
|
||||||
protected:
|
|
||||||
bool _nonblocking;
|
|
||||||
socket(bool nonblocking);
|
|
||||||
public:
|
|
||||||
socket(){}
|
|
||||||
virtual ~socket(){}
|
|
||||||
};
|
|
||||||
|
|
||||||
class server : public socket {
|
|
||||||
protected:
|
|
||||||
int _sock_fd;
|
|
||||||
std::string _client_ip;
|
|
||||||
public:
|
|
||||||
server(int sock_fd, std::string client_ip, bool nonblocking);
|
|
||||||
~server();
|
|
||||||
|
|
||||||
virtual void send_message(const std::string& msg);
|
|
||||||
virtual std::string recv_message(int buffer_size);
|
|
||||||
const std::string& client_ip();
|
|
||||||
|
|
||||||
int fd() { return _sock_fd; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class listener : public socket {
|
|
||||||
protected:
|
|
||||||
uint16_t _port;
|
|
||||||
int _sock_fd;
|
|
||||||
public:
|
|
||||||
listener(int port, int max_queue_length, bool nonblocking);
|
|
||||||
~listener();
|
|
||||||
|
|
||||||
virtual bool wait_for_conn(server** client_sock_p);
|
|
||||||
|
|
||||||
int fd() { return _sock_fd; }
|
|
||||||
int port() { return _port; }
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
|
@ -1,138 +0,0 @@
|
||||||
#include "./event_loop.hpp"
|
|
||||||
#include "../log/log.hpp"
|
|
||||||
#include "assert.h"
|
|
||||||
#include "signal.h"
|
|
||||||
#include "sys/epoll.h"
|
|
||||||
#include <chrono>
|
|
||||||
#include <mutex>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <syncstream>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
using std::chrono::duration;
|
|
||||||
using std::chrono::duration_cast;
|
|
||||||
using std::chrono::high_resolution_clock;
|
|
||||||
using std::chrono::milliseconds;
|
|
||||||
|
|
||||||
namespace anthracite::thread_mgr {
|
|
||||||
|
|
||||||
event_loop::event_loop(std::vector<socket::listener*>& listen_sockets, backends::backend& backend, int max_threads, int max_clients)
|
|
||||||
: thread_mgr(backend)
|
|
||||||
, _error_backend("./www")
|
|
||||||
, _max_threads(max_threads)
|
|
||||||
, _listen_sockets(listen_sockets)
|
|
||||||
, _max_clients(max_clients)
|
|
||||||
, _nonblocking(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool event_loop::event_handler(socket::server* sock)
|
|
||||||
{
|
|
||||||
std::string raw_request = sock->recv_message(http::HEADER_BYTES);
|
|
||||||
|
|
||||||
if (raw_request == "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
http::request req(raw_request, sock->client_ip());
|
|
||||||
std::unique_ptr<http::response> resp = req.is_supported_version() ? _backend.handle_request(req) : _error_backend.handle_error(http::status_codes::HTTP_VERSION_NOT_SUPPORTED);
|
|
||||||
std::string header = resp->header_to_string();
|
|
||||||
sock->send_message(header);
|
|
||||||
sock->send_message(resp->content());
|
|
||||||
|
|
||||||
if (req.close_connection()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_loop::worker_thread_loop(int threadno)
|
|
||||||
{
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "worker " << threadno;
|
|
||||||
pthread_setname_np(pthread_self(), ss.str().c_str());
|
|
||||||
|
|
||||||
struct epoll_event* events = new struct epoll_event[_max_clients];
|
|
||||||
int timeout_ms = 1000;
|
|
||||||
|
|
||||||
if (_nonblocking) {
|
|
||||||
timeout_ms = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::osyncstream(log::info) << "Starting worker thread " << threadno << std::endl;
|
|
||||||
|
|
||||||
int epoll_fd = epoll_create(1);
|
|
||||||
|
|
||||||
for (socket::listener* sl : _listen_sockets) {
|
|
||||||
struct epoll_event event;
|
|
||||||
event.events = EPOLLIN | EPOLLEXCLUSIVE;
|
|
||||||
event.data.ptr = sl;
|
|
||||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sl->fd(), &event);
|
|
||||||
if (threadno == 0) {
|
|
||||||
std::osyncstream(log::info) << "Listening started on port " << sl->port() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (_run) {
|
|
||||||
int ready_fds = epoll_wait(epoll_fd, events, _max_clients, timeout_ms);
|
|
||||||
|
|
||||||
if (ready_fds > 0) {
|
|
||||||
for (int i = 0; i < ready_fds; i++) {
|
|
||||||
socket::socket* sockptr = reinterpret_cast<socket::socket*>(events[i].data.ptr);
|
|
||||||
socket::server* server_ptr = dynamic_cast<socket::server*>(sockptr);
|
|
||||||
|
|
||||||
if (server_ptr != nullptr) {
|
|
||||||
if (!event_handler(server_ptr)) {
|
|
||||||
delete server_ptr;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
socket::listener* listen_ptr = dynamic_cast<socket::listener*>(sockptr);
|
|
||||||
if (listen_ptr != nullptr) {
|
|
||||||
socket::server* server_sock;
|
|
||||||
while (listen_ptr->wait_for_conn(&server_sock)) {
|
|
||||||
struct epoll_event event;
|
|
||||||
event.events = EPOLLIN | EPOLLEXCLUSIVE;
|
|
||||||
event.data.ptr = server_sock;
|
|
||||||
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_sock->fd(), &event);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::osyncstream(log::err) << "Had socket type that wasn't listener or server" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] events;
|
|
||||||
|
|
||||||
std::osyncstream(log::info) << "Stopping worker thread " << threadno << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_loop::start()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lg(_run_lock);
|
|
||||||
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
log::info << "Starting event_loop Thread Manager" << std::endl;
|
|
||||||
|
|
||||||
_run = true;
|
|
||||||
|
|
||||||
std::vector<std::thread> worker_threads;
|
|
||||||
|
|
||||||
for (int i = 0; i < _max_threads; i++) {
|
|
||||||
auto thread = std::thread(&event_loop::worker_thread_loop, this, i);
|
|
||||||
worker_threads.push_back(std::move(thread));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::thread& t : worker_threads) {
|
|
||||||
t.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void event_loop::stop()
|
|
||||||
{
|
|
||||||
_run = false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
#include "./thread_mgr.hpp"
|
|
||||||
#include "../socket/socket.hpp"
|
|
||||||
#include "../backends/file_backend.hpp"
|
|
||||||
#include <mutex>
|
|
||||||
#include <vector>
|
|
||||||
#include "../socket/socket.hpp"
|
|
||||||
|
|
||||||
namespace anthracite::thread_mgr {
|
|
||||||
class event_loop : public virtual thread_mgr {
|
|
||||||
std::mutex _event_mtx;
|
|
||||||
backends::file_backend _error_backend;
|
|
||||||
std::vector<socket::listener*>& _listen_sockets;
|
|
||||||
bool _nonblocking;
|
|
||||||
std::mutex _run_lock;
|
|
||||||
int _max_threads;
|
|
||||||
int _max_clients;
|
|
||||||
|
|
||||||
void worker_thread_loop(int threadno);
|
|
||||||
bool event_handler(socket::server*);
|
|
||||||
|
|
||||||
public:
|
|
||||||
event_loop(std::vector<socket::listener*>&, backends::backend& backend, int max_workers, int max_clients);
|
|
||||||
void start() override;
|
|
||||||
void stop() override;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../backends/backend.hpp"
|
|
||||||
|
|
||||||
namespace anthracite::thread_mgr {
|
|
||||||
class thread_mgr {
|
|
||||||
protected:
|
|
||||||
bool _run;
|
|
||||||
backends::backend& _backend;
|
|
||||||
public:
|
|
||||||
thread_mgr(backends::backend& backend): _backend(backend) {}
|
|
||||||
virtual ~thread_mgr() = default;
|
|
||||||
virtual void start() = 0;
|
|
||||||
virtual void stop() = 0;
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,6 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
extern const std::string ANTHRACITE_VERSION_STRING;
|
|
||||||
extern const std::string ANTHRACITE_FULL_VERSION_STRING;
|
|
11
shell.nix
|
@ -1,11 +0,0 @@
|
||||||
{ pkgs ? import <nixpkgs> {} }:
|
|
||||||
pkgs.mkShell {
|
|
||||||
nativeBuildInputs = [ pkgs.pkg-config pkgs.openssl pkgs.libgcc pkgs.boost pkgs.cmake pkgs.python312 pkgs.cmake pkgs.gnumake ];
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
export OPENSSL_DIR="${pkgs.openssl.dev}"
|
|
||||||
export PKG_CONFIG_PATH="${pkgs.openssl.dev}/lib/pkgconfig"
|
|
||||||
export OPENSSL_NO_VENDOR=1
|
|
||||||
export OPENSSL_LIB_DIR="${pkgs.lib.getLib pkgs.openssl}/lib"
|
|
||||||
'';
|
|
||||||
}
|
|
34
src/Makefile
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
.PHONY: format lint build build-release build-docker run debug
|
||||||
|
|
||||||
|
build-supplemental:
|
||||||
|
cd ./build && ./version.sh && python3 ./error_gen.py
|
||||||
|
|
||||||
|
build: build-supplemental
|
||||||
|
g++ main.cpp --std=c++20 -g -o ./anthracite
|
||||||
|
|
||||||
|
build-release: build-supplemental
|
||||||
|
g++ main.cpp --std=c++20 -O3 -o ./anthracite
|
||||||
|
|
||||||
|
build-docker:
|
||||||
|
docker build . -t anthracite
|
||||||
|
|
||||||
|
run: build
|
||||||
|
./anthracite 8080
|
||||||
|
|
||||||
|
run-test: build
|
||||||
|
./anthracite 8080 ./test_www
|
||||||
|
|
||||||
|
debug: build
|
||||||
|
gdb --args ./anthracite 8080
|
||||||
|
|
||||||
|
debug-test: build
|
||||||
|
gdb --args ./anthracite 8080 ./test_www
|
||||||
|
|
||||||
|
format:
|
||||||
|
clang-format *.cpp -i
|
||||||
|
|
||||||
|
lint:
|
||||||
|
clang-tidy *.cpp
|
||||||
|
|
||||||
|
lint-fix:
|
||||||
|
clang-tidy *.cpp -fix -fix-errors
|
|
@ -1,10 +1,7 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "../http/request.hpp"
|
|
||||||
#include "../http/response.hpp"
|
|
||||||
|
|
||||||
namespace anthracite::backends {
|
#include "../http/http_request.cpp"
|
||||||
|
#include "../http/http_response.cpp"
|
||||||
|
|
||||||
class backend {
|
class backend {
|
||||||
public:
|
public:
|
||||||
|
@ -14,7 +11,5 @@ public:
|
||||||
backend& operator = (backend const&) = delete;
|
backend& operator = (backend const&) = delete;
|
||||||
backend(backend&&) = delete;
|
backend(backend&&) = delete;
|
||||||
backend& operator=(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;
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
67
src/backends/file_backend.cpp
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#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) {
|
||||||
|
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;
|
||||||
|
if (file_info == file_cache.end()) {
|
||||||
|
return handle_error(http_status_codes::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<http_response>(file_info->second, filename, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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));
|
||||||
|
|
||||||
|
while (cur != fin) {
|
||||||
|
auto p = cur->path();
|
||||||
|
std::string filename = p.string();
|
||||||
|
std::stringstream buffer;
|
||||||
|
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;
|
||||||
|
++cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void populate_cache() {
|
||||||
|
populate_cache_dir(file_dir);
|
||||||
|
populate_cache_dir("./error_pages/");
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
file_backend(std::string dir = "./www") : file_dir(std::move(dir)) {
|
||||||
|
populate_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<http_response> handle_request(http_request& req) override {
|
||||||
|
return handle_request_cache(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<http_response> 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;
|
||||||
|
if (file_info == file_cache.end()) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -16,7 +16,7 @@ def generate_error_page(error_code, error_title):
|
||||||
<h1>{error_code} - {error_title}</h1>
|
<h1>{error_code} - {error_title}</h1>
|
||||||
<hr>
|
<hr>
|
||||||
<p>Anthracite/{version}</p>
|
<p>Anthracite/{version}</p>
|
||||||
<p><small><a href="https://git.nickorlow.com/nickorlow/anthracite">This is Open Source Software</small></a></p>
|
<p><small><a href="https://github.com/nickorlow/anthracite">This is Open Source Software</small></a></p>
|
||||||
</center>
|
</center>
|
||||||
</body>
|
</body>
|
||||||
</html>"""
|
</html>"""
|
||||||
|
@ -66,7 +66,7 @@ error_codes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
error_dir = '../build/error_pages'
|
error_dir = '../error_pages'
|
||||||
os.makedirs(error_dir, exist_ok=True)
|
os.makedirs(error_dir, exist_ok=True)
|
||||||
|
|
||||||
for code, title in error_codes.items():
|
for code, title in error_codes.items():
|
|
@ -1,4 +1,3 @@
|
||||||
echo "#include <string>" > version.cpp
|
echo "#include <string>" > version.cpp
|
||||||
echo "#include \"../lib/version.hpp\"" >> version.cpp
|
|
||||||
echo "const std::string ANTHRACITE_VERSION_STRING = \"$(cat version.txt)\";" >> version.cpp
|
echo "const std::string ANTHRACITE_VERSION_STRING = \"$(cat version.txt)\";" >> version.cpp
|
||||||
echo "const std::string ANTHRACITE_FULL_VERSION_STRING = \"Anthracite/$(cat version.txt)\";" >> version.cpp
|
echo "const std::string ANTHRACITE_FULL_VERSION_STRING = \"Anthracite/$(cat version.txt)\";" >> version.cpp
|
1
src/build/version.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0.2.0
|
91
src/datastructures/smart_map.cpp
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,304 +0,0 @@
|
||||||
#include "../lib/backends/file_backend.hpp"
|
|
||||||
#include "../lib/log/log.hpp"
|
|
||||||
#include "../lib/socket/openssl_socket.hpp"
|
|
||||||
#include "../lib/socket/socket.hpp"
|
|
||||||
#include "../lib/thread_mgr/event_loop.hpp"
|
|
||||||
#include "signal.h"
|
|
||||||
#include "getopt.h"
|
|
||||||
#include <fstream>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
struct event_loop_config {
|
|
||||||
int max_workers;
|
|
||||||
int max_clients;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct server_config {
|
|
||||||
std::unordered_map<int, anthracite::socket::listener*> listeners;
|
|
||||||
std::optional<event_loop_config> event_loop;
|
|
||||||
std::string serve_dir = "./www";
|
|
||||||
enum anthracite::log::LOG_LEVEL log_level = anthracite::log::LOG_LEVEL_INFO;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<anthracite::thread_mgr::thread_mgr> server = nullptr;
|
|
||||||
|
|
||||||
extern "C" void signalHandler(int signum)
|
|
||||||
{
|
|
||||||
anthracite::log::warn << "Caught signal SIGIN, exiting Anthracite" << std::endl;
|
|
||||||
if (server != nullptr) {
|
|
||||||
server->stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool read_config(server_config& config, const std::string& config_path)
|
|
||||||
{
|
|
||||||
std::ifstream config_stream(config_path);
|
|
||||||
if (!config_stream.is_open()) {
|
|
||||||
anthracite::log::err << "Unable to open configuration file at '" << config_path << "', ensure that the file exists and permissions are set correctly" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
for (int lineno = 1; std::getline(config_stream, line); lineno++) {
|
|
||||||
bool parse_failed = false;
|
|
||||||
|
|
||||||
std::stringstream ss_line(line);
|
|
||||||
std::string directive;
|
|
||||||
ss_line >> directive;
|
|
||||||
|
|
||||||
if (ss_line.fail()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (directive == "http" || directive == "https") {
|
|
||||||
// http PORT QUEUE_LEN NONBLOCK
|
|
||||||
// https PORT QUEUE_LEN NONBLOCK CRT_PATH KEY_PATH
|
|
||||||
int port;
|
|
||||||
int queue_len;
|
|
||||||
std::string block;
|
|
||||||
std::string crt_path;
|
|
||||||
std::string key_path;
|
|
||||||
ss_line >> port >> queue_len >> block;
|
|
||||||
|
|
||||||
if (directive == "https") {
|
|
||||||
ss_line >> crt_path >> key_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool nonblocking = false;
|
|
||||||
if (block == "blocking") {
|
|
||||||
nonblocking = false;
|
|
||||||
} else if (block == "nonblocking") {
|
|
||||||
nonblocking = true;
|
|
||||||
} else {
|
|
||||||
anthracite::log::err << "BLOCK is not a string of value blocking or nonblocking";
|
|
||||||
parse_failed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_failed |= ss_line.fail();
|
|
||||||
parse_failed |= !ss_line.eof();
|
|
||||||
|
|
||||||
if (parse_failed) {
|
|
||||||
anthracite::log::err << "Invalid http/https config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "Format is: " << std::endl;
|
|
||||||
anthracite::log::err << "http PORT QUEUE_LENGTH BLOCK" << std::endl;
|
|
||||||
anthracite::log::err << "https PORT QUEUE_LENGTH BLOCK CRT_PATH KEY_PATH" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "PORT, QUEUE_LENGTH are integers" << std::endl;
|
|
||||||
anthracite::log::err << "BLOCK is a string of value blocking or nonblocking" << std::endl;
|
|
||||||
anthracite::log::err << "CRT_PATH and KEY_PATH are strings for the path or the certificate and key files respectively" << std::endl;
|
|
||||||
anthracite::log::err << std::endl
|
|
||||||
<< "Line was: " << std::endl
|
|
||||||
<< line << std::endl;
|
|
||||||
anthracite::log::err << "Check for trailing whitespace!" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.listeners.contains(port)) {
|
|
||||||
anthracite::log::err << "Invalid http/https config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << "Port " << port << " is already being used" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (directive == "https") {
|
|
||||||
config.listeners[port] = new anthracite::socket::openssl_listener(key_path, crt_path, port, queue_len, nonblocking);
|
|
||||||
} else {
|
|
||||||
config.listeners[port] = new anthracite::socket::listener(port, queue_len, nonblocking);
|
|
||||||
}
|
|
||||||
} else if (directive == "log_level") {
|
|
||||||
// log_level LEVEL
|
|
||||||
std::string log_level;
|
|
||||||
ss_line >> log_level;
|
|
||||||
|
|
||||||
parse_failed |= ss_line.fail();
|
|
||||||
parse_failed |= !ss_line.eof();
|
|
||||||
|
|
||||||
if (log_level == "DEBUG") {
|
|
||||||
config.log_level = anthracite::log::LOG_LEVEL::LOG_LEVEL_DEBUG;
|
|
||||||
} else if (log_level == "VERBOSE") {
|
|
||||||
config.log_level = anthracite::log::LOG_LEVEL::LOG_LEVEL_VERBOSE;
|
|
||||||
} else if (log_level == "INFO") {
|
|
||||||
config.log_level = anthracite::log::LOG_LEVEL::LOG_LEVEL_INFO;
|
|
||||||
} else if (log_level == "WARN") {
|
|
||||||
config.log_level = anthracite::log::LOG_LEVEL::LOG_LEVEL_WARN;
|
|
||||||
} else if (log_level == "ERROR") {
|
|
||||||
config.log_level = anthracite::log::LOG_LEVEL::LOG_LEVEL_ERROR;
|
|
||||||
} else {
|
|
||||||
parse_failed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parse_failed) {
|
|
||||||
anthracite::log::err << "Invalid log_level config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "Format is: " << std::endl;
|
|
||||||
anthracite::log::err << "log_level LEVEL" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "LEVEL is string of value DEBUG, VERBOSE, INFO, WARN, ERROR" << std::endl;
|
|
||||||
anthracite::log::err << std::endl
|
|
||||||
<< "Line was: " << std::endl
|
|
||||||
<< line << std::endl;
|
|
||||||
anthracite::log::err << "Check for trailing whitespace!" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (directive == "event_loop") {
|
|
||||||
// event_loop MAX_WORKERS MAX_CLIENS
|
|
||||||
int max_workers;
|
|
||||||
int max_clients;
|
|
||||||
|
|
||||||
ss_line >> max_workers >> max_clients;
|
|
||||||
|
|
||||||
parse_failed |= ss_line.fail();
|
|
||||||
parse_failed |= !ss_line.eof();
|
|
||||||
|
|
||||||
if (parse_failed) {
|
|
||||||
anthracite::log::err << "Invalid event_loop config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "Format is: " << std::endl;
|
|
||||||
anthracite::log::err << "event_loop MAX_WORKERS MAX_CLIENTS" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "MAX_WORKERS is the maximum number of worker threads" << std::endl;
|
|
||||||
anthracite::log::err << "MAX_CLIENTS is the maximum number of concurrent clients" << std::endl;
|
|
||||||
anthracite::log::err << std::endl
|
|
||||||
<< "Line was: " << std::endl
|
|
||||||
<< line << std::endl;
|
|
||||||
anthracite::log::err << "Check for trailing whitespace!" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max_workers <= 0) {
|
|
||||||
anthracite::log::err << "Invalid event_loop config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << "MAX_WORKERS must be a positive, nonzero number" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max_clients <= 0) {
|
|
||||||
anthracite::log::err << "Invalid event_loop config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << "MAX_CLIENTS must be a positive, nonzero number" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.event_loop.has_value()) {
|
|
||||||
anthracite::log::err << "Invalid event_loop config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << "A thread manager was already specified. You may only specify one at a time as of now." << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eww
|
|
||||||
config.event_loop = { .max_workers = max_workers, .max_clients = max_clients };
|
|
||||||
} else if (directive == "www_dir") {
|
|
||||||
std::string www_dir;
|
|
||||||
ss_line >> www_dir;
|
|
||||||
|
|
||||||
parse_failed |= ss_line.fail();
|
|
||||||
parse_failed |= !ss_line.eof();
|
|
||||||
|
|
||||||
if (parse_failed) {
|
|
||||||
anthracite::log::err << "Invalid www_dir config on line " << lineno << " of configuration file" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "Format is: " << std::endl;
|
|
||||||
anthracite::log::err << "www_dir PATH" << std::endl;
|
|
||||||
anthracite::log::err << std::endl;
|
|
||||||
anthracite::log::err << "PATH is a path to a directory containing files to serve" << std::endl;
|
|
||||||
anthracite::log::err << std::endl
|
|
||||||
<< "Line was: " << std::endl
|
|
||||||
<< line << std::endl;
|
|
||||||
anthracite::log::err << "Check for trailing whitespace!" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
anthracite::log::err << "Invalid configuration. Unknown directive " << directive << " on line " << lineno << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.event_loop.has_value()) {
|
|
||||||
anthracite::log::err << "Invalid configuration. Missing a thread manager. Try adding an event_loop directive." << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.listeners.size() == 0) {
|
|
||||||
anthracite::log::err << "Invalid configuration. Missing listeners. Try adding a http or https directive." << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
signal(SIGINT, signalHandler);
|
|
||||||
anthracite::log::logger.initialize(anthracite::log::LOG_LEVEL_INFO);
|
|
||||||
if (pthread_setname_np(pthread_self(), "main") != 0) {
|
|
||||||
anthracite::log::err << "Failed to set thread name via pthread_setname_np" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
int opt_index = 0;
|
|
||||||
option options[] = {
|
|
||||||
{ "help", no_argument, 0, 'h' },
|
|
||||||
{ "config", required_argument, 0, 'c' }
|
|
||||||
};
|
|
||||||
|
|
||||||
char c;
|
|
||||||
std::string config_path = "./anthracite.cfg";
|
|
||||||
bool config_set = false;
|
|
||||||
while ((c = getopt_long(argc, argv, "hc:", options, &opt_index)) != -1) {
|
|
||||||
switch (c) {
|
|
||||||
case 'h': {
|
|
||||||
std::cerr << "Anthracite Help" << std::endl;
|
|
||||||
std::cerr << std::endl;
|
|
||||||
std::cerr << "-h, --help Prints this help menu " << std::endl;
|
|
||||||
std::cerr << std::endl;
|
|
||||||
std::cerr << "-c, --config string (optional) Specifies the path of the configuration" << std::endl;
|
|
||||||
std::cerr << " file. Default is './anthracite.cfg'" << std::endl;
|
|
||||||
std::cerr << std::endl;
|
|
||||||
return 0;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
case 'c': {
|
|
||||||
if (config_set) {
|
|
||||||
anthracite::log::err << "You cannot specify multiple configuration files" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
config_set = true;
|
|
||||||
config_path = std::string(optarg);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anthracite::log::info << "Loading configuration file at path '" << config_path << "'" << std::endl;
|
|
||||||
|
|
||||||
server_config cfg;
|
|
||||||
if (!read_config(cfg, config_path)) {
|
|
||||||
anthracite::log::err << "Failed reading configuration file at path '" << config_path << "'" << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
anthracite::log::logger.initialize(cfg.log_level);
|
|
||||||
|
|
||||||
anthracite::log::info << "Serving files in directory " << cfg.serve_dir << std::endl;
|
|
||||||
anthracite::backends::file_backend fb(cfg.serve_dir);
|
|
||||||
|
|
||||||
std::vector<anthracite::socket::listener*> listeners;
|
|
||||||
|
|
||||||
for (auto lp : cfg.listeners) {
|
|
||||||
listeners.push_back(lp.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
server = std::make_shared<anthracite::thread_mgr::event_loop>(listeners, fb, cfg.event_loop->max_workers, cfg.event_loop->max_clients);
|
|
||||||
|
|
||||||
anthracite::log::info << "Starting Anthracite, a very high performance web server" << std::endl;
|
|
||||||
|
|
||||||
server->start();
|
|
||||||
|
|
||||||
for (auto listener : listeners) {
|
|
||||||
delete listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
anthracite::log::info << "Stopping Anthracite, a very high performance web server" << std::endl;
|
|
||||||
}
|
|
|
@ -1,13 +1,9 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace anthracite::http {
|
constexpr int HTTP_HEADER_BYTES = 8190;
|
||||||
|
|
||||||
constexpr int HEADER_BYTES = 8190;
|
enum http_method {
|
||||||
|
|
||||||
enum method {
|
|
||||||
GET,
|
GET,
|
||||||
POST,
|
POST,
|
||||||
DELETE,
|
DELETE,
|
||||||
|
@ -28,7 +24,7 @@ enum method {
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
};
|
};
|
||||||
|
|
||||||
enum status_codes {
|
enum http_status_codes {
|
||||||
CONTINUE = 100,
|
CONTINUE = 100,
|
||||||
SWITCHING_PROTOCOLS = 101,
|
SWITCHING_PROTOCOLS = 101,
|
||||||
PROCESSING = 102,
|
PROCESSING = 102,
|
||||||
|
@ -94,49 +90,49 @@ enum status_codes {
|
||||||
NETWORK_AUTHENTICATION_REQUIRED = 511
|
NETWORK_AUTHENTICATION_REQUIRED = 511
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<std::string, method> const method_map = {
|
static std::unordered_map<std::string, http_method> const http_method_map = {
|
||||||
{ "GET", method::GET },
|
{ "GET", http_method::GET },
|
||||||
{ "POST", method::POST },
|
{ "POST", http_method::POST },
|
||||||
{ "DELETE", method::DELETE },
|
{ "DELETE", http_method::DELETE },
|
||||||
{ "PUT", method::PUT },
|
{ "PUT", http_method::PUT },
|
||||||
{ "PATCH", method::PATCH },
|
{ "PATCH", http_method::PATCH },
|
||||||
{ "HEAD", method::HEAD },
|
{ "HEAD", http_method::HEAD },
|
||||||
{ "OPTIONS", method::OPTIONS },
|
{ "OPTIONS", http_method::OPTIONS },
|
||||||
{ "CONNECT", method::CONNECT },
|
{ "CONNECT", http_method::CONNECT },
|
||||||
{ "TRACE", method::TRACE },
|
{ "TRACE", http_method::TRACE },
|
||||||
{ "COPY", method::COPY },
|
{ "COPY", http_method::COPY },
|
||||||
{ "LINK", method::LINK },
|
{ "LINK", http_method::LINK },
|
||||||
{ "UNLINK", method::UNLINK },
|
{ "UNLINK", http_method::UNLINK },
|
||||||
{ "PURGE", method::PURGE },
|
{ "PURGE", http_method::PURGE },
|
||||||
{ "LOCK", method::LOCK },
|
{ "LOCK", http_method::LOCK },
|
||||||
{ "UNLOCK", method::UNLOCK },
|
{ "UNLOCK", http_method::UNLOCK },
|
||||||
{ "PROPFIND", method::PROPFIND },
|
{ "PROPFIND", http_method::PROPFIND },
|
||||||
{ "VIEW", method::VIEW },
|
{ "VIEW", http_method::VIEW },
|
||||||
{ "UNKNOWN", method::UNKNOWN }
|
{ "UNKNOWN", http_method::UNKNOWN }
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<method, std::string> const reverse_method_map = {
|
static std::unordered_map<http_method, std::string> const http_reverse_method_map = {
|
||||||
{ method::GET, "GET" },
|
{ http_method::GET, "GET" },
|
||||||
{ method::POST, "POST" },
|
{ http_method::POST, "POST" },
|
||||||
{ method::DELETE, "DELETE" },
|
{ http_method::DELETE, "DELETE" },
|
||||||
{ method::PUT, "PUT" },
|
{ http_method::PUT, "PUT" },
|
||||||
{ method::PATCH, "PATCH" },
|
{ http_method::PATCH, "PATCH" },
|
||||||
{ method::HEAD, "HEAD" },
|
{ http_method::HEAD, "HEAD" },
|
||||||
{ method::OPTIONS, "OPTIONS" },
|
{ http_method::OPTIONS, "OPTIONS" },
|
||||||
{ method::CONNECT, "CONNECT" },
|
{ http_method::CONNECT, "CONNECT" },
|
||||||
{ method::TRACE, "TRACE" },
|
{ http_method::TRACE, "TRACE" },
|
||||||
{ method::COPY, "COPY" },
|
{ http_method::COPY, "COPY" },
|
||||||
{ method::LINK, "LINK" },
|
{ http_method::LINK, "LINK" },
|
||||||
{ method::UNLINK, "UNLINK" },
|
{ http_method::UNLINK, "UNLINK" },
|
||||||
{ method::PURGE, "PURGE" },
|
{ http_method::PURGE, "PURGE" },
|
||||||
{ method::LOCK, "LOCK" },
|
{ http_method::LOCK, "LOCK" },
|
||||||
{ method::UNLOCK, "UNLOCK" },
|
{ http_method::UNLOCK, "UNLOCK" },
|
||||||
{ method::PROPFIND, "PROPFIND" },
|
{ http_method::PROPFIND, "PROPFIND" },
|
||||||
{ method::VIEW, "VIEW" },
|
{ http_method::VIEW, "VIEW" },
|
||||||
{ method::UNKNOWN, "UNKNOWN" }
|
{ http_method::UNKNOWN, "UNKNOWN" }
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<int, std::string> const status_map = {
|
static std::unordered_map<int, std::string> const http_status_map = {
|
||||||
{ 100, "CONTINUE" },
|
{ 100, "CONTINUE" },
|
||||||
{ 101, "SWITCHING PROTOCOLS" },
|
{ 101, "SWITCHING PROTOCOLS" },
|
||||||
{ 200, "OK" },
|
{ 200, "OK" },
|
||||||
|
@ -200,7 +196,6 @@ static std::unordered_map<std::string, std::string> const mime_types = {
|
||||||
{ "css", "text/css" },
|
{ "css", "text/css" },
|
||||||
|
|
||||||
{ "js", "application/javascript" },
|
{ "js", "application/javascript" },
|
||||||
{ "json", "application/json" },
|
|
||||||
{ "pdf", "application/pdf" },
|
{ "pdf", "application/pdf" },
|
||||||
|
|
||||||
{ "ico", "image/x-icon" },
|
{ "ico", "image/x-icon" },
|
||||||
|
@ -217,13 +212,13 @@ static std::unordered_map<std::string, std::string> const mime_types = {
|
||||||
{ "wmv", "video/x-ms-wmv" },
|
{ "wmv", "video/x-ms-wmv" },
|
||||||
};
|
};
|
||||||
|
|
||||||
enum version { HTTP_0_9,
|
enum http_version { HTTP_0_9,
|
||||||
HTTP_1_0,
|
HTTP_1_0,
|
||||||
HTTP_1_1,
|
HTTP_1_1,
|
||||||
HTTP_2_0,
|
HTTP_2_0,
|
||||||
HTTP_3_0 };
|
HTTP_3_0 };
|
||||||
|
|
||||||
static std::unordered_map<std::string, version> const version_map = {
|
static std::unordered_map<std::string, http_version> const http_version_map = {
|
||||||
// This is because HTTP 0.9 didn't specify version in the header
|
// This is because HTTP 0.9 didn't specify version in the header
|
||||||
{ "", HTTP_0_9 },
|
{ "", HTTP_0_9 },
|
||||||
{ "HTTP/0.9", HTTP_0_9 },
|
{ "HTTP/0.9", HTTP_0_9 },
|
||||||
|
@ -233,7 +228,7 @@ static std::unordered_map<std::string, version> const version_map = {
|
||||||
{ "HTTP/3.0", HTTP_3_0 }
|
{ "HTTP/3.0", HTTP_3_0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<version, std::string> const reverse_version_map = {
|
static std::unordered_map<http_version, std::string> const http_reverse_version_map = {
|
||||||
{ HTTP_0_9, "HTTP/0.9" },
|
{ HTTP_0_9, "HTTP/0.9" },
|
||||||
{ HTTP_1_0, "HTTP/1.0" },
|
{ HTTP_1_0, "HTTP/1.0" },
|
||||||
{ HTTP_1_1, "HTTP/1.1" },
|
{ HTTP_1_1, "HTTP/1.1" },
|
||||||
|
@ -241,4 +236,6 @@ static std::unordered_map<version, std::string> const reverse_version_map = {
|
||||||
{ HTTP_3_0, "HTTP/3.0" }
|
{ HTTP_3_0, "HTTP/3.0" }
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
#pragma once
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace anthracite::http {
|
|
||||||
|
|
||||||
class name_value {
|
class name_value {
|
||||||
private:
|
private:
|
||||||
std::string _name;
|
std::string _name;
|
||||||
|
@ -29,13 +26,13 @@ public:
|
||||||
virtual std::string to_string() { return ""; }
|
virtual std::string to_string() { return ""; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class header : public name_value {
|
class http_header : public name_value {
|
||||||
public:
|
public:
|
||||||
header()
|
http_header()
|
||||||
: name_value()
|
: name_value()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
header(std::string name, std::string value)
|
http_header(std::string name, std::string value)
|
||||||
: name_value(name, value)
|
: name_value(name, value)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -56,5 +53,3 @@ public:
|
||||||
|
|
||||||
std::string to_string() override { return name() + "=" + value(); }
|
std::string to_string() override { return name() + "=" + value(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
|
8
src/http/http.hpp
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "constants.cpp"
|
||||||
|
#include "header_query.cpp"
|
||||||
|
#include "../socket.cpp"
|
||||||
|
#include "../build/version.cpp"
|
177
src/http/http_request.cpp
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
#include "http.hpp"
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
public:
|
||||||
|
http_request(std::string& raw_data, std::string client_ip)
|
||||||
|
: _path(""), _client_ipaddr(client_ip)
|
||||||
|
{
|
||||||
|
|
||||||
|
parser_state state = METHOD;
|
||||||
|
|
||||||
|
std::string scratch = "";
|
||||||
|
std::string scratch_2 = "";
|
||||||
|
for (int i = 0; i < raw_data.length(); i++) {
|
||||||
|
switch (state) {
|
||||||
|
case METHOD: {
|
||||||
|
if (raw_data[i] == ' ') {
|
||||||
|
if (http_method_map.find(scratch) == http_method_map.end()) {
|
||||||
|
_method = http_method::UNKNOWN;
|
||||||
|
} else {
|
||||||
|
_method = http_method_map.find(scratch)->second;
|
||||||
|
}
|
||||||
|
scratch = "";
|
||||||
|
state = PATH;
|
||||||
|
} else {
|
||||||
|
scratch += raw_data[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case PATH: {
|
||||||
|
switch (raw_data[i]) {
|
||||||
|
case ' ':
|
||||||
|
state = VERSION;
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
state = QUERY_PARAM_NAME;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_path += raw_data[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case QUERY_PARAM_NAME: {
|
||||||
|
if (raw_data[i] == ' ') {
|
||||||
|
scratch = "";
|
||||||
|
state = VERSION;
|
||||||
|
} else if (raw_data[i] == '=') {
|
||||||
|
state = QUERY_PARAM_VALUE;
|
||||||
|
} else {
|
||||||
|
scratch += raw_data[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case QUERY_PARAM_VALUE: {
|
||||||
|
if (raw_data[i] == ' ') {
|
||||||
|
_query_params[scratch] = query_param(scratch, scratch_2);
|
||||||
|
scratch = "";
|
||||||
|
scratch_2 = "";
|
||||||
|
state = VERSION;
|
||||||
|
} else if (raw_data[i] == '&') {
|
||||||
|
_query_params[scratch] = query_param(scratch, scratch_2);
|
||||||
|
scratch = "";
|
||||||
|
scratch_2 = "";
|
||||||
|
state = QUERY_PARAM_NAME;
|
||||||
|
} else {
|
||||||
|
scratch_2 += raw_data[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case VERSION: {
|
||||||
|
if (raw_data[i] == '\n') {
|
||||||
|
_http_version = http_version_map.find(scratch)->second;
|
||||||
|
scratch = "";
|
||||||
|
state = HEADER_NAME;
|
||||||
|
} else if (raw_data[i] != '\r') {
|
||||||
|
scratch += raw_data[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case HEADER_NAME: {
|
||||||
|
if (raw_data[i] == '\n') {
|
||||||
|
scratch = "";
|
||||||
|
scratch_2 = "";
|
||||||
|
state = BODY_CONTENT;
|
||||||
|
break;
|
||||||
|
} else if (raw_data[i] == ' ') {
|
||||||
|
scratch = "";
|
||||||
|
break;
|
||||||
|
} else if (raw_data[i] == ':') {
|
||||||
|
state = HEADER_VALUE;
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
scratch += raw_data[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case HEADER_VALUE: {
|
||||||
|
if (raw_data[i] == '\n') {
|
||||||
|
_headers[scratch] = http_header(scratch, scratch_2);
|
||||||
|
scratch = "";
|
||||||
|
scratch_2 = "";
|
||||||
|
state = HEADER_NAME;
|
||||||
|
} else if (raw_data[i] != '\r') {
|
||||||
|
scratch_2 += raw_data[i];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case BODY_CONTENT: {
|
||||||
|
_body_content += raw_data[i];
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path() { return _path; }
|
||||||
|
|
||||||
|
http_method method() { return _method; }
|
||||||
|
|
||||||
|
std::string client_ip() { return _client_ipaddr; }
|
||||||
|
|
||||||
|
http_version get_http_version() {
|
||||||
|
return _http_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_supported_version() {
|
||||||
|
return _http_version == HTTP_1_1 || _http_version == HTTP_1_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool close_connection() {
|
||||||
|
const auto& header = _headers.find("Connection");
|
||||||
|
const bool found = header != _headers.end();
|
||||||
|
|
||||||
|
if(found && header->second.value() == "keep-alive") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_string()
|
||||||
|
{
|
||||||
|
std::string response = "";
|
||||||
|
response += http_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";
|
||||||
|
|
||||||
|
for (auto header : _headers) {
|
||||||
|
response += header.second.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
response += "\r\n";
|
||||||
|
response += _body_content;
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
};
|
61
src/http/http_response.cpp
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
};
|
79
src/main.cpp
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#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;
|
||||||
|
}
|
88
src/socket.cpp
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <exception>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
constexpr int MAX_QUEUE_LENGTH = 100;
|
||||||
|
|
||||||
|
class anthracite_socket {
|
||||||
|
private:
|
||||||
|
int server_socket;
|
||||||
|
int client_socket {};
|
||||||
|
std::string client_ip;
|
||||||
|
struct sockaddr_in client_addr {};
|
||||||
|
socklen_t client_addr_len {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
anthracite_socket(int port, int max_queue = MAX_QUEUE_LENGTH)
|
||||||
|
: server_socket(socket(AF_INET, SOCK_STREAM, 0))
|
||||||
|
, client_ip("")
|
||||||
|
{
|
||||||
|
struct sockaddr_in address {};
|
||||||
|
address.sin_family = AF_INET;
|
||||||
|
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));
|
||||||
|
|
||||||
|
::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_client_ip()
|
||||||
|
{
|
||||||
|
return client_ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_conn()
|
||||||
|
{
|
||||||
|
close(client_socket);
|
||||||
|
client_socket = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_message(std::string& msg)
|
||||||
|
{
|
||||||
|
if (client_socket == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send(client_socket, &msg[0], msg.length(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string recv_message(int buffer_size)
|
||||||
|
{
|
||||||
|
if (client_socket == -1) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (result < 1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
response[buffer_size] = '\0';
|
||||||
|
return std::string(response);
|
||||||
|
}
|
||||||
|
};
|
BIN
src/test_www/images/emma.bmp
Normal file
After Width: | Height: | Size: 496 KiB |
Before Width: | Height: | Size: 319 B After Width: | Height: | Size: 319 B |
BIN
src/test_www/images/favicon_anim.ico
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/test_www/images/lola.jpeg
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
src/test_www/images/tini.png
Normal file
After Width: | Height: | Size: 42 KiB |
|
@ -1,6 +1,6 @@
|
||||||
<head>
|
<head>
|
||||||
<title>Anthracite</title>
|
<title>Anthracite</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/images/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<center>
|
<center>
|
||||||
<h1>Anthracite is Running!</h1>
|
<h1>Anthracite is Running!</h1>
|
18
src/test_www/test.css
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.cool-style {
|
||||||
|
background: linear-gradient(to right, #ef5350, #f48fb1, #7e57c2, #2196f3, #26c6da, #43a047, #eeff41, #f9a825, #ff5722);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
25
src/test_www/test.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Anthracite</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/images/favicon_anim.ico">
|
||||||
|
<link rel="stylesheet" href="test.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<center>
|
||||||
|
<h1 class="cool-style">Test Page!</h1>
|
||||||
|
<h2>Dogs</h2>
|
||||||
|
<div class="content">
|
||||||
|
<img src="/images/tini.png" alt="A border collie" />
|
||||||
|
<img src="/images/lola.jpeg" alt="A border collie" />
|
||||||
|
<img src="/images/emma.bmp" alt="A corgi" />
|
||||||
|
</div>
|
||||||
|
<h2>Trains</h2>
|
||||||
|
<div>
|
||||||
|
<video controls>
|
||||||
|
<source src="videos/train_vid.mp4" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
src/test_www/videos/train_vid.mp4
Normal file
9
src/www/index.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<head>
|
||||||
|
<title>Anthracite</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/images/favicon.ico">
|
||||||
|
</head>
|
||||||
|
<center>
|
||||||
|
<h1>Anthracite is Running!</h1>
|
||||||
|
<p>If you are seeing this page, then Anthracite is configured correctly!</p>
|
||||||
|
<p>Add files to the "www" directory to begin serving your website.</p>
|
||||||
|
</center>
|
|
@ -1,67 +0,0 @@
|
||||||
#include "../lib/http/request.hpp"
|
|
||||||
#include <chrono>
|
|
||||||
#include <fstream>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#ifdef SPEEDTEST_COMPARE_BOOST
|
|
||||||
#include <boost/beast.hpp>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using std::chrono::duration;
|
|
||||||
using std::chrono::duration_cast;
|
|
||||||
using std::chrono::high_resolution_clock;
|
|
||||||
using std::chrono::milliseconds;
|
|
||||||
|
|
||||||
constexpr uint32_t num_requests = 10000000;
|
|
||||||
|
|
||||||
TEST(speed_tests, request_parse)
|
|
||||||
{
|
|
||||||
std::ifstream t("./test_files/test_request.http");
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << t.rdbuf();
|
|
||||||
std::string raw_req = buffer.str();
|
|
||||||
|
|
||||||
auto start = high_resolution_clock::now();
|
|
||||||
|
|
||||||
for (int i = 0; i < num_requests; ++i) {
|
|
||||||
volatile anthracite::http::request req(raw_req, "0.0.0.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end = high_resolution_clock::now();
|
|
||||||
|
|
||||||
auto ms_int = duration_cast<milliseconds>(end - start);
|
|
||||||
|
|
||||||
double m_rps = ((1000.0 / ms_int.count()) * num_requests) / 1000000;
|
|
||||||
|
|
||||||
std::cout << "Parsed " << (num_requests / 1000000) << " Million requests in " << ms_int << " ms";
|
|
||||||
std::cout << " at " << m_rps << " Million RPS " << std::endl;
|
|
||||||
|
|
||||||
ASSERT_LT(ms_int.count(), 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SPEEDTEST_COMPARE_BOOST
|
|
||||||
TEST(speed_tests, boost)
|
|
||||||
{
|
|
||||||
std::ifstream t("./test_files/test_request.http");
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << t.rdbuf();
|
|
||||||
std::string raw_req = buffer.str();
|
|
||||||
|
|
||||||
auto start = high_resolution_clock::now();
|
|
||||||
|
|
||||||
for (int i = 0; i < num_requests; ++i) {
|
|
||||||
boost::system::error_code ec;
|
|
||||||
boost::beast::http::request_parser<boost::beast::http::string_body> p;
|
|
||||||
p.put(boost::asio::buffer(raw_req), ec);
|
|
||||||
boost::beast::http::request<boost::beast::http::string_body> r = p.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end = high_resolution_clock::now();
|
|
||||||
auto ms_int = duration_cast<milliseconds>(end - start);
|
|
||||||
|
|
||||||
double m_rps = ((1000.0 / ms_int.count()) * num_requests) / 1000000;
|
|
||||||
|
|
||||||
std::cout << "Parsed " << (num_requests / 1000000) << " Million requests in " << ms_int << " ms";
|
|
||||||
std::cout << " at " << m_rps << " Million RPS " << std::endl;
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,15 +0,0 @@
|
||||||
GET /foo/bar?test=a&test2=b HTTP/1.1
|
|
||||||
Accept: */*
|
|
||||||
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
|
|
||||||
Accept-Encoding: gzip,deflate
|
|
||||||
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
|
|
||||||
Connection: keep-alive
|
|
||||||
Content-Type: application/x-www-form-urlencoded
|
|
||||||
Cookie: foo=bar; lorem=ipsum;
|
|
||||||
Host: example.org
|
|
||||||
Keep-Alive: 115
|
|
||||||
Referer: http://example.org/test
|
|
||||||
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
|
|
||||||
X-Requested-With: XMLHttpRequest
|
|
||||||
|
|
||||||
{ "test": "content" }
|
|
|
@ -1,18 +0,0 @@
|
||||||
#include "../lib/http/request.hpp"
|
|
||||||
#include <boost/beast.hpp>
|
|
||||||
#include <fstream>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
TEST(unit_tests, single_request_parse)
|
|
||||||
{
|
|
||||||
std::ifstream t("./test_files/test_request.http");
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << t.rdbuf();
|
|
||||||
|
|
||||||
std::string raw_req = buffer.str();
|
|
||||||
std::string expected = buffer.str();
|
|
||||||
|
|
||||||
anthracite::http::request req(raw_req, "0.0.0.0");
|
|
||||||
|
|
||||||
ASSERT_EQ(expected, req.to_string());
|
|
||||||
}
|
|