Files
SpineParticlesWeb/connector/native/http_server.cpp
T
2026-09-05 23:35:45 +08:00

209 lines
6.8 KiB
C++

#include "http_server.hpp"
#include "encoding.hpp"
#include <algorithm>
#include <cctype>
#include <cstring>
#include <sstream>
#include <thread>
#ifdef _WIN32
#define NOMINMAX
#include <winsock2.h>
#include <ws2tcpip.h>
using Socket = SOCKET;
constexpr Socket kInvalidSocket = INVALID_SOCKET;
#else
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
using Socket = int;
constexpr Socket kInvalidSocket = -1;
#endif
namespace connector {
namespace {
constexpr std::size_t kMaxBody = 200 * 1024 * 1024;
constexpr std::size_t kMaxHeaders = 64 * 1024;
void close_socket(Socket socket) {
#ifdef _WIN32
closesocket(socket);
#else
close(socket);
#endif
}
std::string lowercase(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return value;
}
std::string status_text(int status) {
switch (status) {
case 200: return "OK";
case 204: return "No Content";
case 400: return "Bad Request";
case 403: return "Forbidden";
case 404: return "Not Found";
case 413: return "Payload Too Large";
case 500: return "Internal Server Error";
case 503: return "Service Unavailable";
default: return "Response";
}
}
void send_all(Socket socket, const std::string& data) {
std::size_t sent = 0;
while (sent < data.size()) {
const int count = send(socket, data.data() + sent, static_cast<int>((std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20))), 0);
if (count <= 0) return;
sent += static_cast<std::size_t>(count);
}
}
bool parse_request(Socket socket, HttpRequest& request, int& error_status) {
std::string buffer;
char chunk[8192];
std::size_t header_end = std::string::npos;
while ((header_end = buffer.find("\r\n\r\n")) == std::string::npos) {
const int count = recv(socket, chunk, sizeof(chunk), 0);
if (count <= 0) return false;
buffer.append(chunk, count);
if (buffer.size() > kMaxHeaders) { error_status = 413; return false; }
}
std::istringstream headers(buffer.substr(0, header_end));
std::string line;
if (!std::getline(headers, line)) return false;
if (!line.empty() && line.back() == '\r') line.pop_back();
std::istringstream first(line);
std::string version;
if (!(first >> request.method >> request.target >> version)) return false;
while (std::getline(headers, line)) {
if (!line.empty() && line.back() == '\r') line.pop_back();
const auto separator = line.find(':');
if (separator != std::string::npos) request.headers[lowercase(trim(line.substr(0, separator)))] = trim(line.substr(separator + 1));
}
const auto query_position = request.target.find('?');
request.path = request.target.substr(0, query_position);
if (query_position != std::string::npos) {
const std::string query = request.target.substr(query_position + 1);
std::size_t start = 0;
while (start <= query.size()) {
const auto end = query.find('&', start);
const std::string part = query.substr(start, end - start);
const auto equal = part.find('=');
request.query[url_decode(part.substr(0, equal))] = url_decode(equal == std::string::npos ? "" : part.substr(equal + 1));
if (end == std::string::npos) break;
start = end + 1;
}
}
std::size_t content_length = 0;
if (const auto found = request.headers.find("content-length"); found != request.headers.end()) {
try { content_length = static_cast<std::size_t>(std::stoull(found->second)); }
catch (...) { error_status = 400; return false; }
}
if (content_length > kMaxBody) { error_status = 413; return false; }
request.body = buffer.substr(header_end + 4);
while (request.body.size() < content_length) {
const int count = recv(socket, chunk, sizeof(chunk), 0);
if (count <= 0) return false;
request.body.append(chunk, count);
}
if (request.body.size() > content_length) request.body.resize(content_length);
return true;
}
void serve_client(Socket client, const RequestHandler& handler) {
HttpRequest request;
int error_status = 400;
HttpResponse response;
if (parse_request(client, request, error_status)) {
try { response = handler(request); }
catch (const std::exception& error) {
response.status = 500;
response.headers["Content-Type"] = "application/json; charset=utf-8";
response.body = std::string("{\"code\":\"INTERNAL_ERROR\",\"message\":\"") + error.what() + "\"}";
}
} else {
response.status = error_status;
response.body = "Bad request";
}
response.headers["Content-Length"] = std::to_string(response.body.size());
response.headers["Connection"] = "close";
std::ostringstream output;
output << "HTTP/1.1 " << response.status << ' ' << status_text(response.status) << "\r\n";
for (const auto& [name, value] : response.headers) output << name << ": " << value << "\r\n";
output << "\r\n";
send_all(client, output.str());
send_all(client, response.body);
close_socket(client);
}
} // namespace
HttpServer::HttpServer() = default;
HttpServer::~HttpServer() { stop(); }
bool HttpServer::start(unsigned short port, RequestHandler handler, std::string& error) {
if (running_) return true;
#ifdef _WIN32
WSADATA data{};
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) { error = "无法初始化网络服务"; return false; }
#endif
Socket server = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server == kInvalidSocket) { error = "无法创建本地服务"; return false; }
int reuse = 1;
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&reuse), sizeof(reuse));
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_port = htons(port);
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(server, reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0 || listen(server, 16) != 0) {
close_socket(server);
error = "端口 27843 已被占用,可能已有连接器正在运行";
return false;
}
socket_ = static_cast<std::intptr_t>(server);
handler_ = std::move(handler);
running_ = true;
thread_ = new std::thread([this] { accept_loop(); });
return true;
}
void HttpServer::accept_loop() {
const Socket server = static_cast<Socket>(socket_);
while (running_) {
Socket client = accept(server, nullptr, nullptr);
if (client == kInvalidSocket) {
if (running_) continue;
break;
}
std::thread(serve_client, client, handler_).detach();
}
}
void HttpServer::stop() {
if (!running_.exchange(false)) return;
#ifdef _WIN32
shutdown(static_cast<Socket>(socket_), SD_BOTH);
#else
shutdown(static_cast<Socket>(socket_), SHUT_RDWR);
#endif
close_socket(static_cast<Socket>(socket_));
if (thread_) {
auto* thread = static_cast<std::thread*>(thread_);
if (thread->joinable()) thread->join();
delete thread;
thread_ = nullptr;
}
#ifdef _WIN32
WSACleanup();
#endif
}
} // namespace connector