#include "http_server.hpp" #include "encoding.hpp" #include #include #include #include #include #include #include #ifdef __APPLE__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" #endif #ifdef _WIN32 #define NOMINMAX #include #include using Socket = SOCKET; constexpr Socket kInvalidSocket = INVALID_SOCKET; #else #include #include #include #include #ifdef __APPLE__ #include #include #include #endif 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 } struct ConnectionStream { Socket socket = kInvalidSocket; #ifdef __APPLE__ SSLContextRef tls = nullptr; #endif }; #ifdef __APPLE__ OSStatus socket_read(SSLConnectionRef reference, void* data, std::size_t* length) { const Socket socket = *static_cast(reference); const long count = recv(socket, static_cast(data), static_cast(*length), 0); if (count > 0) { *length = static_cast(count); return noErr; } *length = 0; if (count == 0) return errSSLClosedGraceful; return errno == EAGAIN || errno == EWOULDBLOCK ? errSSLWouldBlock : errSSLInternal; } OSStatus socket_write(SSLConnectionRef reference, const void* data, std::size_t* length) { const Socket socket = *static_cast(reference); const long count = send(socket, static_cast(data), static_cast(*length), 0); if (count > 0) { *length = static_cast(count); return noErr; } *length = 0; return errno == EAGAIN || errno == EWOULDBLOCK ? errSSLWouldBlock : errSSLInternal; } void* load_identity(const std::filesystem::path& path, std::string& error) { std::ifstream input(path, std::ios::binary); if (!input) { error = "无法读取 HTTPS 身份证书"; return nullptr; } const std::string bytes((std::istreambuf_iterator(input)), std::istreambuf_iterator()); CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast(bytes.data()), static_cast(bytes.size())); const void* keys[] = {kSecImportExportPassphrase}; const void* values[] = {CFSTR("spine-connector-local")}; CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFArrayRef items = nullptr; const OSStatus status = SecPKCS12Import(data, options, &items); CFRelease(options); CFRelease(data); if (status != errSecSuccess || !items || CFArrayGetCount(items) == 0) { if (items) CFRelease(items); error = "无法读取 HTTPS 身份证书"; return nullptr; } CFDictionaryRef item = static_cast(CFArrayGetValueAtIndex(items, 0)); CFTypeRef identity = CFDictionaryGetValue(item, kSecImportItemIdentity); if (identity) CFRetain(identity); CFRelease(items); if (!identity) error = "HTTPS 身份证书缺少私钥"; return const_cast(identity); } #endif bool open_stream(ConnectionStream& stream, void* identity, std::string& error) { if (!identity) return true; #ifdef __APPLE__ SSLContextRef context = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType); if (!context) { error = "无法创建 HTTPS 服务"; return false; } SSLSetIOFuncs(context, socket_read, socket_write); SSLSetConnection(context, &stream.socket); SSLSetProtocolVersionMin(context, kTLSProtocol12); const void* certificates[] = {identity}; CFArrayRef certificate_array = CFArrayCreate(kCFAllocatorDefault, certificates, 1, &kCFTypeArrayCallBacks); const OSStatus certificate_status = SSLSetCertificate(context, certificate_array); CFRelease(certificate_array); if (certificate_status != noErr) { CFRelease(context); error = "无法配置 HTTPS 身份证书"; return false; } OSStatus status; do { status = SSLHandshake(context); } while (status == errSSLWouldBlock); if (status != noErr) { CFRelease(context); return false; } stream.tls = context; return true; #else (void)stream; error = "当前平台不支持 HTTPS 连接器"; return false; #endif } void close_stream(ConnectionStream& stream) { #ifdef __APPLE__ if (stream.tls) { SSLClose(stream.tls); CFRelease(stream.tls); stream.tls = nullptr; } #endif if (stream.socket != kInvalidSocket) { close_socket(stream.socket); stream.socket = kInvalidSocket; } } 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"; } } int read_stream(ConnectionStream& stream, char* buffer, int length) { #ifdef __APPLE__ if (stream.tls) { std::size_t received = 0; const OSStatus status = SSLRead(stream.tls, buffer, static_cast(length), &received); if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify) return static_cast(received); if (status != noErr && status != errSSLWouldBlock) return -1; return static_cast(received); } #endif return recv(stream.socket, buffer, length, 0); } void send_all(ConnectionStream& stream, const std::string& data) { std::size_t sent = 0; while (sent < data.size()) { int count; #ifdef __APPLE__ if (stream.tls) { std::size_t written = 0; const OSStatus status = SSLWrite(stream.tls, data.data() + sent, (std::min)(data.size() - sent, static_cast(1 << 20)), &written); if (status != noErr && status != errSSLWouldBlock) return; count = static_cast(written); } else { count = send(stream.socket, data.data() + sent, static_cast((std::min)(data.size() - sent, static_cast(1 << 20))), 0); } #else count = send(stream.socket, data.data() + sent, static_cast((std::min)(data.size() - sent, static_cast(1 << 20))), 0); #endif if (count <= 0) return; sent += static_cast(count); } } bool parse_request(ConnectionStream& stream, 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 = read_stream(stream, chunk, sizeof(chunk)); 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 = read_stream(stream, chunk, sizeof(chunk)); 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, void* identity, const RequestHandler& handler) { ConnectionStream stream{client}; std::string tls_error; if (!open_stream(stream, identity, tls_error)) { close_stream(stream); return; } HttpRequest request; int error_status = 400; HttpResponse response; if (parse_request(stream, request, error_status)) { #ifdef __APPLE__ request.secure = stream.tls != nullptr; #endif 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(stream, output.str()); send_all(stream, response.body); close_stream(stream); } } // namespace HttpServer::HttpServer() = default; HttpServer::~HttpServer() { stop(); } bool HttpServer::start(unsigned short port, RequestHandler handler, std::string& error, const std::filesystem::path& identity) { if (running_) return true; #ifdef __APPLE__ if (!identity.empty()) { void* loaded = load_identity(identity, error); if (loaded) tls_identity_ = std::shared_ptr(loaded, [](void* value) { CFRelease(value); }); if (!tls_identity_) return false; } #else if (!identity.empty()) { error = "当前平台不支持 HTTPS 连接器"; return false; } #endif #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([client, identity = tls_identity_, handler = handler_] { serve_client(client, identity.get(), 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 #ifdef __APPLE__ #pragma clang diagnostic pop #endif