Skip to content

Commit 5fdb6de

Browse files
authored
Merge pull request #5 from Shfdis/rfc-compliance
Rfc compliance
2 parents 484eb45 + 43e03f9 commit 5fdb6de

20 files changed

Lines changed: 2271 additions & 400 deletions

.github/workflows/ci.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
- master
8+
push:
9+
branches:
10+
- main
11+
- master
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
build-and-test:
18+
name: build-and-test
19+
runs-on: ubuntu-latest
20+
container:
21+
image: archlinux:latest
22+
options: --security-opt seccomp=unconfined --ulimit memlock=-1:-1
23+
24+
steps:
25+
- name: Install dependencies
26+
run: |
27+
pacman -Syu --noconfirm
28+
pacman -S --noconfirm base-devel cmake gcc liburing pkgconf git nodejs
29+
30+
- name: Check out repository
31+
uses: actions/checkout@v4
32+
33+
- name: Configure server tests
34+
run: cmake -S server -B build/server -DCMAKE_BUILD_TYPE=Release
35+
36+
- name: Build server tests
37+
run: cmake --build build/server -j
38+
39+
- name: Run tests
40+
run: ctest --test-dir build/server --output-on-failure
41+
42+
- name: Configure example
43+
run: cmake -S example -B build/example -DCMAKE_BUILD_TYPE=Release -DENABLE_ASAN=OFF
44+
45+
- name: Build example
46+
run: cmake --build build/example -j

README.md

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,29 @@ cd ../benchmarks
7171
DURATION=30s THREADS=4 CONNECTIONS=100 ./benchmark.sh
7272
```
7373

74-
Output (2026-04-02 16:42:45 MSK, non-ASAN Release build):
75-
76-
- **GET /echo**
77-
- **C++**: 427149 req/s
78-
- **Tokio**: 500231 req/s
79-
- **POST /echo**
80-
- **C++**: 387572 req/s
81-
- **Tokio**: 452589 req/s
74+
Output (non-ASAN Release build):
75+
76+
#### GET /echo
77+
78+
| Server | Latency avg | Latency stdev | Latency max | +/- stdev | Requests/sec | Transfer/sec |
79+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
80+
| C++ coroutine server | 132.16us | 66.78us | 4.97ms | 80.82% | 439792.17 | 29.78MB |
81+
| Rust Tokio server | 150.12us | 119.34us | 5.53ms | 94.01% | 506996.06 | 40.61MB |
82+
83+
#### POST /echo
84+
85+
| Server | Latency avg | Latency stdev | Latency max | +/- stdev | Requests/sec | Transfer/sec |
86+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
87+
| C++ coroutine server | 149.98us | 77.14us | 5.27ms | 81.01% | 402624.34 | 29.57MB |
88+
| Rust Tokio server | 168.98us | 138.11us | 5.80ms | 91.79% | 465844.80 | 39.98MB |
89+
90+
## Pull Request Checks
91+
92+
Pull requests targeting `main` or `master` run the `CI / build-and-test`
93+
GitHub Actions check. Configure both branches in GitHub branch protection or a
94+
repository ruleset with:
95+
96+
- Require a pull request before merging
97+
- Require status checks to pass before merging
98+
- Required status check: `build-and-test` (shown in Actions as `CI / build-and-test`)
99+
- Restrict direct pushes to matching branches

server/CMakeLists.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pkg_check_modules(LIBURING REQUIRED liburing)
1919
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
2020
add_library(coro_http_server
2121
src/io_uring.cpp
22+
src/http_parser.cpp
2223
src/read_iterator.cpp
2324
src/http_error.cpp
2425
src/trie.cpp
@@ -42,3 +43,31 @@ include(GNUInstallDirs)
4243
install(TARGETS coro_http_server EXPORT MyServerConfig DESTINATION ${CMAKE_INSTALL_LIBDIR})
4344
install(EXPORT MyServerConfig FILE coro_http_serverConfig.cmake NAMESPACE coro_http_server:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/coro_http_server)
4445
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
46+
47+
if (CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
48+
set(CORO_HTTP_SERVER_BUILD_TESTS_DEFAULT ON)
49+
else()
50+
set(CORO_HTTP_SERVER_BUILD_TESTS_DEFAULT OFF)
51+
endif()
52+
option(CORO_HTTP_SERVER_BUILD_TESTS "Build coro_http_server tests" ${CORO_HTTP_SERVER_BUILD_TESTS_DEFAULT})
53+
54+
if (CORO_HTTP_SERVER_BUILD_TESTS)
55+
include(CTest)
56+
if (BUILD_TESTING)
57+
find_package(Threads REQUIRED)
58+
59+
add_executable(http_parser_test tests/http_parser_test.cpp)
60+
target_link_libraries(http_parser_test PRIVATE coro_http_server::coro_http_server)
61+
set_target_properties(http_parser_test PROPERTIES
62+
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests
63+
)
64+
add_test(NAME http_parser_test COMMAND http_parser_test)
65+
66+
add_executable(http_integration_test tests/http_integration_test.cpp)
67+
target_link_libraries(http_integration_test PRIVATE coro_http_server::coro_http_server Threads::Threads)
68+
set_target_properties(http_integration_test PROPERTIES
69+
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests
70+
)
71+
add_test(NAME http_integration_test COMMAND http_integration_test)
72+
endif()
73+
endif()

server/include/http_error.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#pragma once
22
#include <exception>
3-
#include <string_view>
3+
#include <string>
44
namespace HTTP {
55
class HTTPError : public std::exception {
66
public:
7-
std::string_view message;
7+
std::string message;
88
const int status;
9-
HTTPError(int status, std::string_view message);
9+
HTTPError(int status, std::string message);
1010
};
1111
} // namespace HTTP

server/include/http_parser.h

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#pragma once
2+
#include "co_future.h"
3+
#include "read_iterator.h"
4+
#include "request_data.h"
5+
#include <optional>
6+
#include <string>
7+
#include <string_view>
8+
9+
namespace HTTP {
10+
11+
enum class HttpParseStatus { NeedMoreData, NeedContinue, Complete, Error };
12+
enum class RequestReadStatus { Complete, NeedContinue, Closed };
13+
14+
struct HttpParseResult {
15+
HttpParseStatus status{HttpParseStatus::NeedMoreData};
16+
int errorStatus{0};
17+
std::string errorMessage;
18+
};
19+
20+
class HttpParserState {
21+
enum class State {
22+
StartLine,
23+
HeaderLine,
24+
FixedBody,
25+
ChunkSize,
26+
ChunkData,
27+
ChunkDataCrlf,
28+
TrailerLine,
29+
Complete,
30+
Error
31+
};
32+
33+
State state_{State::StartLine};
34+
RequestData current_;
35+
std::string line_;
36+
std::string pending_;
37+
bool sawCr_{false};
38+
bool continueSent_{false};
39+
bool waitingForContinue_{false};
40+
bool continueCandidate_{false};
41+
bool chunkNeedsLf_{false};
42+
bool started_{false};
43+
bool chunked_{false};
44+
bool expectsContinue_{false};
45+
bool hostEmpty_{false};
46+
bool contentLengthInvalid_{false};
47+
bool contentLengthConflict_{false};
48+
bool unsupportedTransferEncoding_{false};
49+
bool invalidExpect_{false};
50+
bool hasContentLength_{false};
51+
size_t hostCount_{0};
52+
size_t headerBytes_{0};
53+
size_t fixedRemaining_{0};
54+
size_t chunkRemaining_{0};
55+
size_t contentLength_{0};
56+
int errorStatus_{0};
57+
std::string errorMessage_;
58+
59+
void ResetForNext();
60+
size_t ProcessBytes(std::string_view data);
61+
void SetError(int status, std::string message);
62+
void CompleteCurrent();
63+
void ProcessLine();
64+
void AppendLineData(std::string_view data);
65+
void ProcessLineByte(char ch);
66+
void FinishHeaders();
67+
void TrackHeader(std::string_view name, std::string_view value);
68+
69+
public:
70+
HttpParserState();
71+
void Append(std::string_view data);
72+
size_t Consume(std::string_view data);
73+
bool Empty() const;
74+
void MarkContinueSent();
75+
HttpParseResult ParseNext(RequestData &request);
76+
};
77+
78+
class HttpRequestParser {
79+
ReadIterator iterator_;
80+
HttpParserState state_;
81+
82+
public:
83+
HttpRequestParser(IOUring &ring, int fd);
84+
CoFuture<RequestReadStatus> ReadRequest(RequestData &request);
85+
void MarkContinueSent();
86+
};
87+
88+
} // namespace HTTP

server/include/io_uring.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "co_future.h"
33
#include <array>
44
#include <atomic>
5+
#include <cstddef>
56
#include <deque>
67
#include <functional>
78
#include <liburing.h>
@@ -12,6 +13,8 @@
1213
#include <string_view>
1314
#define QUEUE_DEPTH 1024
1415
namespace HTTP {
16+
inline constexpr size_t kReadBufferSize = 256;
17+
1518
class IOUring;
1619

1720
class IOUring {
@@ -44,8 +47,8 @@ class IOUring {
4447
~IOUring();
4548
IOUring();
4649
IOUring &operator=(IOUring &&rhs);
47-
void Read(int fileDescriptor, std::array<char, 256> &buffer, std::function<void(int)> complete);
48-
CoFuture<int> ReadAsync(int fileDescriptor, std::array<char, 256> &buffer);
50+
void Read(int fileDescriptor, std::array<char, kReadBufferSize> &buffer, std::function<void(int)> complete);
51+
CoFuture<int> ReadAsync(int fileDescriptor, std::array<char, kReadBufferSize> &buffer);
4952
void Write(int fileDescriptor, std::string_view data, size_t offset, size_t len,
5053
std::function<void(int)> complete);
5154
CoFuture<int> WriteAsync(int fileDescriptor, std::string_view data, size_t offset,

server/include/read_iterator.h

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
#pragma once
22
#include "co_future.h"
33
#include "io_uring.h"
4-
#include "request_data.h"
54
namespace HTTP {
5+
66
class ReadIterator {
77
IOUring &ring_;
8-
std::array<char, 256> buffer_;
8+
std::array<char, kReadBufferSize> buffer_;
99
size_t length_{0};
1010
size_t position_{0};
1111
int fd_;
12+
bool eof_{false};
1213

1314
public:
1415
ReadIterator(IOUring &ring, int fd_);
15-
CoFuture<void> Ensure();
16+
CoFuture<void> operator++();
17+
char operator*() const;
18+
explicit operator bool() const;
19+
bool Eof() const;
1620
size_t Available() const;
1721
const char *CurrentPtr() const;
1822
void Advance(size_t n);
19-
CoFuture<void> operator++();
20-
char operator*();
21-
operator bool();
22-
CoFuture<void> ParseVariables(RequestData &data);
23-
CoFuture<void> ParseHeaders(RequestData &data);
24-
CoFuture<void> ParseMethod(RequestData &data);
25-
CoFuture<void> ParseBody(RequestData &data);
26-
};
2723
};
24+
} // namespace HTTP

server/include/request_data.h

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
#pragma once
2+
#include <cstddef>
3+
#include <optional>
24
#include <string>
5+
#include <string_view>
36
#include <unordered_map>
7+
#include <utility>
48
#include <vector>
59
namespace HTTP {
6-
enum Method { GET, PUT, POST, PATCH, DELETE };
10+
enum Method { GET, PUT, POST, PATCH, DELETE, HEAD, OPTIONS, UNKNOWN };
11+
12+
constexpr size_t kRoutableMethodCount = 7;
13+
714
struct RequestData {
815
std::unordered_map<std::string, std::string> headers;
916
std::unordered_map<std::string, std::string> params;
17+
std::unordered_map<std::string, std::string> trailers;
18+
std::vector<std::pair<std::string, std::string>> rawHeaders;
1019
std::vector<std::string> urlVariables;
11-
Method method;
20+
Method method{UNKNOWN};
21+
std::string methodToken;
22+
std::string target;
23+
std::string path;
24+
std::string query;
25+
std::string version;
1226
std::string body;
27+
bool connectionClose{false};
28+
bool connectionKeepAlive{false};
29+
30+
std::optional<std::string> Header(std::string_view name) const;
1331
};
1432
struct ResponseData {
1533
std::unordered_map<std::string, std::string> headers;
1634
std::string body;
17-
unsigned short status;
35+
unsigned short status{200};
1836
};
1937
} // namespace HTTP

server/include/server.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#pragma once
22
#include "co_future.h"
3+
#include "http_parser.h"
34
#include "io_uring.h"
4-
#include "read_iterator.h"
55
#include "request_data.h"
66
#include "trie.h"
77
#include <atomic>
88
#include <memory>
9+
#include <string>
10+
#include <string_view>
911
#include <vector>
1012
namespace HTTP {
1113
class Server {
@@ -20,9 +22,11 @@ class Server {
2022

2123
void WorkerLoop(IOUring &ring);
2224
CoFuture<void> AcceptAndProcess(IOUring &ring);
23-
CoFuture<void> GetHandler(RequestData &data, ReadIterator &iter, RespondType &handler);
24-
CoFuture<void> WriteResponse(IOUring &ring, int connectionFD, const ResponseData &data,
25-
bool keepAlive);
25+
CoFuture<void> WriteRaw(IOUring &ring, int connectionFD, std::string_view data);
26+
CoFuture<void> WriteResponse(IOUring &ring, int connectionFD,
27+
const ResponseData &data,
28+
const RequestData &request, bool keepAlive,
29+
std::string &buffer);
2630
CoFuture<void> Process(IOUring &ring, int connectionFD);
2731
friend class ServerBuilder;
2832

server/include/trie.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,28 @@ class Trie {
2323
StringEqual>
2424
children;
2525
std::unique_ptr<Node> wildcard;
26-
std::optional<RespondType> handlers[5];
26+
std::optional<RespondType> handlers[kRoutableMethodCount];
2727
Node() = default;
2828
Node &Move(std::string_view segment);
2929
};
3030
std::unique_ptr<Node> root_ = std::make_unique<Node>();
3131

3232
public:
33+
struct RouteResult {
34+
bool pathFound{false};
35+
bool methodAllowed{false};
36+
bool automaticOptions{false};
37+
RespondType handler;
38+
std::string allow;
39+
};
40+
3341
Trie() = default;
3442
Trie(Trie &&rhs);
3543
Trie &operator=(Trie &&rhs);
3644
void AddRequest(Method type, RespondType function, std::string_view path);
45+
std::string AllAllowedMethods() const;
46+
RouteResult Resolve(Method method, std::string_view path,
47+
std::vector<std::string> &urlVariables) const;
3748
RespondType Match(Method method, std::string_view path,
3849
std::vector<std::string> &urlVariables) const;
3950
};

0 commit comments

Comments
 (0)