#include "http_server.hpp" #include "encoding.hpp" #include #include #include #include #include #ifdef _WIN32 #define NOMINMAX #include #include using Socket = SOCKET; constexpr Socket kInvalidSocket = INVALID_SOCKET; #else #include #include #include #include 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(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((std::min)(data.size() - sent, static_cast(1 << 20))), 0); if (count <= 0) return; sent += static_cast(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::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(&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(&address), sizeof(address)) != 0 || listen(server, 16) != 0) { close_socket(server); error = "端口 27843 已被占用,可能已有连接器正在运行"; return false; } socket_ = static_cast(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_); 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_), SD_BOTH); #else shutdown(static_cast(socket_), SHUT_RDWR); #endif close_socket(static_cast(socket_)); if (thread_) { auto* thread = static_cast(thread_); if (thread->joinable()) thread->join(); delete thread; thread_ = nullptr; } #ifdef _WIN32 WSACleanup(); #endif } } // namespace connector