init commit

This commit is contained in:
Luxferre
2026-10-14 09:18:19 +03:00
commit b8b03f3b41
10 changed files with 6159 additions and 0 deletions
+624
View File
@@ -0,0 +1,624 @@
/*
Dynagate HTTP/1.1 server implementation (C++20 port)
Created by Luxferre in 2026, released into the public domain
*/
#ifndef _DARWIN_C_SOURCE
#define _DARWIN_C_SOURCE
#endif
#include "dynagate.hpp"
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <thread>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
namespace {
constexpr size_t kMaxHeaderBytes = 65536;
constexpr size_t kMaxBodyBytes = 64 * 1024 * 1024; // 64 MB
bool write_all(int fd, const char *data, size_t len) {
size_t written = 0;
while (written < len) {
ssize_t n = write(fd, data + written, len - written);
if (n > 0) {
written += static_cast<size_t>(n);
} else if (n < 0 && errno == EINTR) {
continue;
} else {
return false;
}
}
return true;
}
const char *status_reason_phrase(int code) {
switch (code) {
case 200:
return "OK";
case 201:
return "Created";
case 202:
return "Accepted";
case 204:
return "No Content";
case 400:
return "Bad Request";
case 401:
return "Unauthorized";
case 403:
return "Forbidden";
case 404:
return "Not Found";
case 405:
return "Method Not Allowed";
case 408:
return "Request Timeout";
case 413:
return "Payload Too Large";
case 429:
return "Too Many Requests";
case 499:
return "Client Closed Request";
case 500:
return "Internal Server Error";
case 502:
return "Bad Gateway";
case 503:
return "Service Unavailable";
case 504:
return "Gateway Timeout";
default:
return "Status";
}
}
class SocketResponseWriter : public ResponseWriter {
public:
explicit SocketResponseWriter(int fd) : fd_(fd) {}
HeaderList &header() override { return headers_; }
void write_header(int status_code) override {
if (!header_written_) {
status_code_ = status_code;
header_written_ = true;
}
}
bool write(std::string_view data) override {
if (!header_written_) {
write_header(200);
}
write_called_ = true;
if (headers_sent_to_socket_) {
if (data.empty()) {
return !write_error_;
}
if (!write_all(fd_, data.data(), data.size())) {
write_error_ = true;
return false;
}
return true;
}
pending_body_.append(data.data(), data.size());
return true;
}
void flush() override {
if (write_error_) {
return;
}
if (!header_written_) {
write_header(200);
}
if (!headers_sent_to_socket_) {
headers_sent_to_socket_ = true;
std::string raw;
raw.reserve(512 + pending_body_.size());
raw.append("HTTP/1.1 ");
raw.append(std::to_string(status_code_));
raw.push_back(' ');
raw.append(status_reason_phrase(status_code_));
raw.append("\r\n");
for (const auto &kv : headers_.items) {
raw.append(kv.first);
raw.append(": ");
raw.append(kv.second);
raw.append("\r\n");
}
if (write_called_) {
if (headers_.get("Content-Length").empty()) {
raw.append("Content-Length: ");
raw.append(std::to_string(pending_body_.size()));
raw.append("\r\n");
}
if (headers_.get("Connection").empty()) {
raw.append("Connection: close\r\n");
}
} else {
if (headers_.get("Connection").empty()) {
raw.append("Connection: close\r\n");
}
}
raw.append("\r\n");
if (!pending_body_.empty()) {
raw.append(pending_body_);
pending_body_.clear();
}
if (!write_all(fd_, raw.data(), raw.size())) {
write_error_ = true;
}
}
}
private:
int fd_;
int status_code_ = 200;
bool header_written_ = false;
bool write_called_ = false;
bool headers_sent_to_socket_ = false;
bool write_error_ = false;
HeaderList headers_;
std::string pending_body_;
};
bool is_socket_disconnected(int fd) {
if (fd < 0) {
return true;
}
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN | POLLHUP | POLLERR;
pfd.revents = 0;
int rc = poll(&pfd, 1, 0);
if (rc < 0) {
return errno != EINTR;
}
if (rc == 0) {
return false;
}
if (pfd.revents & (POLLHUP | POLLERR | POLLNVAL)) {
return true;
}
if (pfd.revents & POLLIN) {
char ch = 0;
ssize_t n = recv(fd, &ch, 1, MSG_PEEK);
if (n == 0) {
return true;
}
if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
return true;
}
}
return false;
}
bool read_chunked_body(int fd,
std::string &buffer,
size_t initial_offset,
std::string &out_body) {
size_t pos = initial_offset;
auto ensure_bytes = [&](size_t needed_end) -> bool {
char tmp[4096];
while (buffer.size() < needed_end) {
ssize_t n = read(fd, tmp, sizeof(tmp));
if (n > 0) {
buffer.append(tmp, static_cast<size_t>(n));
if (buffer.size() > kMaxBodyBytes + kMaxHeaderBytes) {
return false;
}
} else if (n < 0 && errno == EINTR) {
continue;
} else {
return false;
}
}
return true;
};
auto ensure_crlf = [&](size_t from, size_t &crlf_pos) -> bool {
char tmp[4096];
for (;;) {
crlf_pos = buffer.find("\r\n", from);
if (crlf_pos != std::string::npos) {
return true;
}
ssize_t n = read(fd, tmp, sizeof(tmp));
if (n > 0) {
buffer.append(tmp, static_cast<size_t>(n));
if (buffer.size() > kMaxBodyBytes + kMaxHeaderBytes) {
return false;
}
} else if (n < 0 && errno == EINTR) {
continue;
} else {
return false;
}
}
};
for (;;) {
size_t line_end = 0;
if (!ensure_crlf(pos, line_end)) {
return false;
}
std::string size_str = str_trim(
std::string_view(buffer).substr(pos, line_end - pos));
size_t semi = size_str.find(';');
if (semi != std::string::npos) {
size_str.resize(semi);
}
if (size_str.empty()) {
return false;
}
char *endptr = nullptr;
unsigned long chunk_len = std::strtoul(size_str.c_str(), &endptr, 16);
if (endptr == size_str.c_str() || chunk_len > kMaxBodyBytes) {
return false;
}
pos = line_end + 2;
if (chunk_len == 0) {
size_t trailer_end = 0;
if (!ensure_crlf(pos, trailer_end)) {
return false;
}
return true;
}
if (out_body.size() + chunk_len > kMaxBodyBytes) {
return false;
}
if (!ensure_bytes(pos + chunk_len + 2)) {
return false;
}
out_body.append(buffer.data() + pos, chunk_len);
pos += chunk_len;
if (buffer.substr(pos, 2) != "\r\n") {
return false;
}
pos += 2;
}
}
bool read_http_request(int fd, HttpRequest &req, int &err_status) {
err_status = 400;
std::string buffer;
buffer.reserve(4096);
char tmp[4096];
size_t header_end = std::string::npos;
for (;;) {
header_end = buffer.find("\r\n\r\n");
if (header_end != std::string::npos) {
break;
}
if (buffer.size() >= kMaxHeaderBytes) {
return false;
}
ssize_t n = read(fd, tmp, sizeof(tmp));
if (n > 0) {
buffer.append(tmp, static_cast<size_t>(n));
} else if (n < 0 && errno == EINTR) {
continue;
} else {
return false;
}
}
std::string_view header_block(buffer.data(), header_end);
size_t first_line_end = header_block.find("\r\n");
std::string_view request_line =
(first_line_end == std::string_view::npos)
? header_block
: header_block.substr(0, first_line_end);
size_t sp1 = request_line.find(' ');
if (sp1 == std::string_view::npos) {
return false;
}
req.method = std::string(request_line.substr(0, sp1));
std::string_view rest = request_line.substr(sp1 + 1);
size_t sp2 = rest.find(' ');
std::string_view raw_target =
(sp2 == std::string_view::npos) ? rest : rest.substr(0, sp2);
size_t qpos = raw_target.find('?');
req.path = std::string(
(qpos == std::string_view::npos) ? raw_target : raw_target.substr(0, qpos));
size_t pos = (first_line_end == std::string_view::npos)
? header_block.size()
: first_line_end + 2;
long content_length = 0;
bool is_chunked = false;
bool expect_continue = false;
while (pos < header_block.size()) {
size_t le = header_block.find("\r\n", pos);
std::string_view line =
(le == std::string_view::npos)
? header_block.substr(pos)
: header_block.substr(pos, le - pos);
pos = (le == std::string_view::npos) ? header_block.size() : le + 2;
if (line.empty()) {
continue;
}
size_t colon = line.find(':');
if (colon == std::string_view::npos) {
continue;
}
std::string key = str_trim(line.substr(0, colon));
std::string val = str_trim(line.substr(colon + 1));
if (key.empty()) {
continue;
}
if (str_iequals(key, "Content-Length")) {
content_length = std::atol(val.c_str());
} else if (str_iequals(key, "Transfer-Encoding")) {
if (str_to_lower(val).find("chunked") != std::string::npos) {
is_chunked = true;
}
} else if (str_iequals(key, "Expect")) {
if (str_iequals(val, "100-continue")) {
expect_continue = true;
}
}
req.headers.add(key, val);
}
if (expect_continue) {
const char *cont = "HTTP/1.1 100 Continue\r\n\r\n";
write_all(fd, cont, std::strlen(cont));
}
size_t body_offset = header_end + 4;
if (is_chunked) {
if (!read_chunked_body(fd, buffer, body_offset, req.body)) {
return false;
}
return true;
}
if (content_length < 0) {
return false;
}
if (static_cast<size_t>(content_length) > kMaxBodyBytes) {
err_status = 413;
return false;
}
size_t needed = static_cast<size_t>(content_length);
size_t avail = buffer.size() - body_offset;
req.body.reserve(needed);
size_t to_copy = std::min(avail, needed);
if (to_copy > 0) {
req.body.append(buffer.data() + body_offset, to_copy);
}
while (req.body.size() < needed) {
size_t rem = needed - req.body.size();
ssize_t n = read(fd, tmp, std::min(sizeof(tmp), rem));
if (n > 0) {
req.body.append(tmp, static_cast<size_t>(n));
} else if (n < 0 && errno == EINTR) {
continue;
} else {
return false;
}
}
return true;
}
} // namespace
GatewayServer::GatewayServer(int port,
ConfigManager &cm,
std::vector<std::string> auth_tokens,
std::chrono::milliseconds retry_delay)
: requested_port_(port),
cm_(cm),
auth_tokens_(std::move(auth_tokens)),
retry_delay_(retry_delay) {}
GatewayServer::~GatewayServer() {
stop();
}
bool GatewayServer::start(std::string *err_out) {
ensure_curl_initialized();
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
if (err_out) {
*err_out = "socket() failed: " + std::string(std::strerror(errno));
}
return false;
}
int one = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
#ifdef SO_NOSIGPIPE
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
#endif
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(static_cast<uint16_t>(requested_port_));
if (bind(fd, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) < 0) {
if (err_out) {
*err_out = "bind() failed: " + std::string(std::strerror(errno));
}
close(fd);
return false;
}
if (listen(fd, 128) < 0) {
if (err_out) {
*err_out = "listen() failed: " + std::string(std::strerror(errno));
}
close(fd);
return false;
}
struct sockaddr_in bound_addr;
socklen_t addr_len = sizeof(bound_addr);
std::memset(&bound_addr, 0, sizeof(bound_addr));
if (getsockname(fd, reinterpret_cast<struct sockaddr *>(&bound_addr),
&addr_len) == 0) {
bound_port_ = ntohs(bound_addr.sin_port);
} else {
bound_port_ = requested_port_;
}
listen_fd_ = fd;
stopping_.store(false);
return true;
}
void GatewayServer::serve() {
while (!stopping_.load()) {
int lfd = listen_fd_;
if (lfd < 0) {
break;
}
struct pollfd pfd;
pfd.fd = lfd;
pfd.events = POLLIN;
pfd.revents = 0;
int prc = poll(&pfd, 1, 100);
if (prc < 0) {
if (errno == EINTR) {
continue;
}
break;
}
if (prc == 0 || !(pfd.revents & POLLIN)) {
continue;
}
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int cfd = accept(lfd, reinterpret_cast<struct sockaddr *>(&client_addr),
&client_len);
if (cfd < 0) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
if (stopping_.load()) {
break;
}
continue;
}
#ifdef SO_NOSIGPIPE
int one = 1;
setsockopt(cfd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one));
#endif
{
std::unique_lock<std::mutex> lock(active_mu_);
active_cv_.wait(lock, [&]() {
return stopping_.load() ||
active_connections_ < kMaxConcurrentConnections;
});
if (stopping_.load()) {
close(cfd);
break;
}
++active_connections_;
}
std::thread([this, cfd]() {
handle_client(cfd);
close(cfd);
{
std::lock_guard<std::mutex> lock(active_mu_);
--active_connections_;
}
active_cv_.notify_all();
}).detach();
}
}
void GatewayServer::stop() {
bool expected = false;
if (!stopping_.compare_exchange_strong(expected, true)) {
return;
}
if (listen_fd_ >= 0) {
shutdown(listen_fd_, SHUT_RDWR);
close(listen_fd_);
listen_fd_ = -1;
}
active_cv_.notify_all();
std::unique_lock<std::mutex> lock(active_mu_);
active_cv_.wait_for(lock, std::chrono::seconds(10), [&]() {
return active_connections_ == 0;
});
}
void GatewayServer::handle_client(int client_fd) {
HttpRequest req;
req.client_fd = client_fd;
req.is_cancelled = [this, client_fd]() {
return stopping_.load() || is_socket_disconnected(client_fd);
};
SocketResponseWriter w(client_fd);
int err_status = 400;
if (!read_http_request(client_fd, req, err_status)) {
if (err_status == 413) {
w.header().set("Content-Type", "text/plain; charset=utf-8");
w.write_header(413);
w.write("Payload Too Large\n");
w.flush();
}
return;
}
if (req.path == "/v1/models" || req.path == "/models") {
handle_models(cm_, auth_tokens_, req, w);
} else if (req.path == "/v1/chat/completions" ||
req.path == "/chat/completions") {
handle_chat_completions(cm_, auth_tokens_, retry_delay_, req, w);
} else if (req.path == "/v1/images/generations" ||
req.path == "/images/generations") {
handle_image_generations(cm_, auth_tokens_, retry_delay_, req, w);
} else if (req.path == "/v1/messages" || req.path == "/messages") {
handle_messages(cm_, auth_tokens_, retry_delay_, req, w);
} else if (req.path == "/v1/responses" || req.path == "/responses") {
handle_responses(cm_, auth_tokens_, retry_delay_, req, w);
} else {
w.header().set("Content-Type", "text/plain; charset=utf-8");
w.write_header(404);
w.write("404 page not found\n");
w.flush();
}
}