Files
SpineParticlesWeb/connector/native/http_server.cpp
T
2026-11-09 10:08:29 +08:00

390 lines
13 KiB
C++

#include "http_server.hpp"
#include "encoding.hpp"
#include <algorithm>
#include <cerrno>
#include <cctype>
#include <cstring>
#include <fstream>
#include <sstream>
#include <thread>
#ifdef __APPLE__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
#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>
#ifdef __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecImportExport.h>
#include <Security/SecureTransport.h>
#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<const Socket*>(reference);
const long count = recv(socket, static_cast<char*>(data), static_cast<int>(*length), 0);
if (count > 0) {
*length = static_cast<std::size_t>(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<const Socket*>(reference);
const long count = send(socket, static_cast<const char*>(data), static_cast<int>(*length), 0);
if (count > 0) {
*length = static_cast<std::size_t>(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<char>(input)), std::istreambuf_iterator<char>());
CFDataRef data = CFDataCreate(kCFAllocatorDefault, reinterpret_cast<const UInt8*>(bytes.data()),
static_cast<CFIndex>(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<CFDictionaryRef>(CFArrayGetValueAtIndex(items, 0));
CFTypeRef identity = CFDictionaryGetValue(item, kSecImportItemIdentity);
if (identity) CFRetain(identity);
CFRelease(items);
if (!identity) error = "HTTPS 身份证书缺少私钥";
return const_cast<void*>(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<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";
}
}
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<std::size_t>(length), &received);
if (status == errSSLClosedGraceful || status == errSSLClosedNoNotify) return static_cast<int>(received);
if (status != noErr && status != errSSLWouldBlock) return -1;
return static_cast<int>(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<std::size_t>(1 << 20)), &written);
if (status != noErr && status != errSSLWouldBlock) return;
count = static_cast<int>(written);
} else {
count = send(stream.socket, data.data() + sent,
static_cast<int>((std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20))), 0);
}
#else
count = send(stream.socket, data.data() + sent,
static_cast<int>((std::min)(data.size() - sent, static_cast<std::size_t>(1 << 20))), 0);
#endif
if (count <= 0) return;
sent += static_cast<std::size_t>(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::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 = 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<void>(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<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([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>(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
#ifdef __APPLE__
#pragma clang diagnostic pop
#endif