547 lines
19 KiB
C++
547 lines
19 KiB
C++
#pragma once
|
||
#include "squirrel.h"
|
||
#include "sqstdaux.h"
|
||
#include "sqstdblob.h"
|
||
#include "sqstdio.h"
|
||
#include "sqstdmath.h"
|
||
#include "sqstdstring.h"
|
||
#include "sqstdsystem.h"
|
||
#include <asio.hpp>
|
||
#include <asio/ssl.hpp>
|
||
#include <asio/basic_socket_streambuf.hpp>
|
||
#include <iostream>
|
||
#include <thread>
|
||
#include <atomic>
|
||
#include <unordered_map>
|
||
#include <mutex>
|
||
#include <memory> // 新增:用于 std::unique_ptr
|
||
#include <string>
|
||
#include <sstream>
|
||
#include <algorithm>
|
||
#include <cctype>
|
||
#include <stdint.h>
|
||
|
||
// 全局变量声明(需在cpp文件中定义)
|
||
extern HSQUIRRELVM v;
|
||
extern std::recursive_mutex SqMtx;
|
||
|
||
using tcp = asio::ip::tcp;
|
||
|
||
// ====================== 工具函数 ======================
|
||
// 字符串转小写
|
||
std::string to_lower(std::string s) {
|
||
std::transform(s.begin(), s.end(), s.begin(),
|
||
[](unsigned char c) { return std::tolower(c); });
|
||
return s;
|
||
}
|
||
|
||
// 修剪字符串两端空格
|
||
std::string trim(std::string s) {
|
||
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
|
||
[](unsigned char c) { return !std::isspace(c); }));
|
||
s.erase(std::find_if(s.rbegin(), s.rend(),
|
||
[](unsigned char c) { return !std::isspace(c); }).base(), s.end());
|
||
return s;
|
||
}
|
||
|
||
// 解析 URL,分离路径和查询参数
|
||
void parse_url(const std::string& url, std::string& path, std::string& query) {
|
||
size_t query_pos = url.find('?');
|
||
if (query_pos == std::string::npos) {
|
||
path = url;
|
||
query = "";
|
||
}
|
||
else {
|
||
path = url.substr(0, query_pos);
|
||
query = url.substr(query_pos + 1);
|
||
}
|
||
}
|
||
|
||
// ====================== HTTP 头解析结构体 ======================
|
||
struct HttpRequestHeader {
|
||
std::string method; // GET/POST/PUT/DELETE 等
|
||
std::string path; // 请求路径(如 /api/test)
|
||
std::string version; // HTTP/1.1 / HTTP/1.0
|
||
std::string query_string; // URL 查询参数(如 ?id=1&name=test)
|
||
// 头部键值对(统一转小写,避免大小写问题)
|
||
std::unordered_map<std::string, std::string> headers;
|
||
|
||
// 便捷获取头部值(兼容大小写)
|
||
std::string get_header(const std::string& key) const {
|
||
std::string lower_key = to_lower(key);
|
||
auto it = headers.find(lower_key);
|
||
return it != headers.end() ? it->second : "";
|
||
}
|
||
};
|
||
|
||
// ====================== HTTP 头解析函数 ======================
|
||
HttpRequestHeader parse_http_header(const std::string& raw_header) {
|
||
HttpRequestHeader result;
|
||
std::istringstream iss(raw_header);
|
||
std::string line;
|
||
bool first_line_parsed = false;
|
||
|
||
// 第一步:解析请求首行(仅解析第一行,避免重复)
|
||
while (std::getline(iss, line)) {
|
||
// 移除行尾的 \r
|
||
if (!line.empty() && line.back() == '\r') {
|
||
line.pop_back();
|
||
}
|
||
line = trim(line);
|
||
|
||
// 跳过空行
|
||
if (line.empty()) {
|
||
continue;
|
||
}
|
||
|
||
// 仅解析第一个非空行作为请求首行
|
||
if (!first_line_parsed) {
|
||
std::string url;
|
||
std::istringstream line_iss(line);
|
||
line_iss >> result.method >> url >> result.version;
|
||
parse_url(url, result.path, result.query_string);
|
||
std::transform(result.method.begin(), result.method.end(), result.method.begin(),
|
||
[](unsigned char c) { return std::toupper(c); });
|
||
first_line_parsed = true;
|
||
continue;
|
||
}
|
||
|
||
// 解析头部键值对
|
||
size_t colon_pos = line.find(':');
|
||
if (colon_pos == std::string::npos) {
|
||
continue;
|
||
}
|
||
|
||
std::string key = trim(line.substr(0, colon_pos));
|
||
std::string value = trim(line.substr(colon_pos + 1));
|
||
result.headers[to_lower(key)] = value;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// ====================== 读取完整原始请求 ======================
|
||
std::string read_full_raw_request(tcp::socket& socket, HttpRequestHeader& header_out) {
|
||
asio::streambuf buf;
|
||
std::string full_request;
|
||
asio::error_code ec;
|
||
|
||
try {
|
||
// 第一步:读取头部(直到 \r\n\r\n)
|
||
size_t header_bytes = asio::read_until(socket, buf, "\r\n\r\n", ec);
|
||
if (ec) {
|
||
throw asio::system_error(ec);
|
||
}
|
||
|
||
// 提取头部内容
|
||
std::string header_str(asio::buffer_cast<const char*>(buf.data()), header_bytes);
|
||
full_request = header_str;
|
||
|
||
// 安全清理头部末尾的 \r\n\r\n(核心修复:先判断是否找到)
|
||
size_t end_header_pos = header_str.find("\r\n\r\n"); // 用 find 而非 find_last_of,精准匹配结束符
|
||
if (end_header_pos != std::string::npos) {
|
||
// 只保留头部内容,截断后面的空行
|
||
header_str = header_str.substr(0, end_header_pos);
|
||
}
|
||
|
||
// 解析头部
|
||
header_out = parse_http_header(header_str);
|
||
|
||
// 第二步:读取请求体(严格按 Content-Length 读取)
|
||
size_t content_len = 0;
|
||
std::string cl_str = header_out.get_header("Content-Length");
|
||
if (!cl_str.empty()) {
|
||
try {
|
||
content_len = std::stoul(cl_str);
|
||
}
|
||
catch (...) {
|
||
content_len = 0;
|
||
}
|
||
}
|
||
|
||
// 清空缓冲区中已读取的头部数据
|
||
buf.consume(header_bytes);
|
||
|
||
// 读取请求体(防越界)
|
||
if (content_len > 0) {
|
||
// 先检查剩余缓冲区是否足够,避免读取超限
|
||
size_t buf_available = buf.size();
|
||
if (buf_available < content_len) {
|
||
// 读取剩余需要的字节
|
||
asio::read(socket, buf, asio::transfer_exactly(content_len - buf_available), ec);
|
||
if (ec && ec != asio::error::eof) {
|
||
throw asio::system_error(ec);
|
||
}
|
||
}
|
||
|
||
// 提取请求体(防缓冲区数据不足)
|
||
size_t actual_read = std::min(buf.size(), content_len);
|
||
std::string body_str(asio::buffer_cast<const char*>(buf.data()), actual_read);
|
||
full_request += body_str;
|
||
|
||
// 清理缓冲区
|
||
buf.consume(actual_read);
|
||
}
|
||
}
|
||
catch (...) {
|
||
throw;
|
||
}
|
||
|
||
return full_request;
|
||
}
|
||
|
||
// ====================== 将 HttpRequestHeader 转为 Squirrel Table ======================
|
||
void push_http_header_to_squirrel(HSQUIRRELVM v, const HttpRequestHeader& header) {
|
||
// 1. 创建空 Table
|
||
sq_newtable(v);
|
||
|
||
// 2. 设置基础字段(method/path/version/query_string)
|
||
// method
|
||
sq_pushstring(v, _SC("method"), -1);
|
||
sq_pushstring(v, header.method.c_str(), -1);
|
||
sq_rawset(v, -3);
|
||
|
||
// path
|
||
sq_pushstring(v, _SC("path"), -1);
|
||
sq_pushstring(v, header.path.c_str(), -1);
|
||
sq_rawset(v, -3);
|
||
|
||
// version
|
||
sq_pushstring(v, _SC("version"), -1);
|
||
sq_pushstring(v, header.version.c_str(), -1);
|
||
sq_rawset(v, -3);
|
||
|
||
// query_string
|
||
sq_pushstring(v, _SC("query_string"), -1);
|
||
sq_pushstring(v, header.query_string.c_str(), -1);
|
||
sq_rawset(v, -3);
|
||
|
||
// 3. 创建 headers 子 Table
|
||
sq_pushstring(v, _SC("headers"), -1);
|
||
sq_newtable(v);
|
||
// 遍历头部键值对,写入子 Table
|
||
for (const auto& pair : header.headers) {
|
||
sq_pushstring(v, pair.first.c_str(), -1); // 键(小写)
|
||
sq_pushstring(v, pair.second.c_str(), -1); // 值
|
||
sq_rawset(v, -3);
|
||
}
|
||
// 将 headers 子 Table 写入主 Table
|
||
sq_rawset(v, -3);
|
||
}
|
||
|
||
// ====================== 处理客户端请求 ======================
|
||
void handle_client(tcp::socket* socket, HSQOBJECT HttpServerObject) {
|
||
if (!socket || !socket->is_open()) {
|
||
std::cerr << "无效的 socket 连接" << std::endl;
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 读取完整请求并解析头部
|
||
HttpRequestHeader header;
|
||
std::string full_request = read_full_raw_request(*socket, header);
|
||
|
||
// 加锁操作 Squirrel VM(lock_guard 自动管理锁生命周期)
|
||
std::lock_guard<std::recursive_mutex> lock(SqMtx);
|
||
|
||
// 执行 Squirrel 的 Event 函数
|
||
SQInteger top = sq_gettop(v); // 保存栈状态
|
||
sq_pushobject(v, HttpServerObject);
|
||
sq_pushstring(v, _SC("Event"), -1);
|
||
|
||
if (SQ_SUCCEEDED(sq_get(v, -2))) {
|
||
// 压入函数参数:
|
||
// 参数1:HttpServerObject(this)
|
||
sq_pushobject(v, HttpServerObject);
|
||
// 参数2:socket 指针(userpointer)
|
||
sq_pushuserpointer(v, socket);
|
||
// 参数3:解析后的 header table
|
||
push_http_header_to_squirrel(v, header);
|
||
// 参数4:完整原始请求字符串
|
||
size_t body_start_pos = full_request.find("\r\n\r\n");
|
||
std::string request_body = (body_start_pos != std::string::npos) ? full_request.substr(body_start_pos + 4) : "";
|
||
sq_pushstring(v, request_body.c_str(), request_body.size());
|
||
|
||
// 调用函数:参数数量=4,无返回值,开启错误捕获
|
||
sq_call(v, 4, SQFalse, SQTrue);
|
||
}
|
||
|
||
sq_settop(v, top); // 恢复栈状态
|
||
|
||
}
|
||
catch (const asio::system_error& e) {
|
||
std::cerr << "网络错误: " << e.what() << " (错误码: " << e.code() << ")" << std::endl;
|
||
}
|
||
catch (const std::exception& e) {
|
||
std::cerr << "处理请求错误: " << e.what() << std::endl;
|
||
}
|
||
catch (...) {
|
||
std::cerr << "未知错误" << std::endl;
|
||
}
|
||
}
|
||
|
||
// ====================== 服务器上下文(支持多实例) ======================
|
||
struct HttpServerCtx {
|
||
uint64_t server_id; // 唯一服务器ID(多实例核心)
|
||
std::atomic<bool> running{ true }; // 退出标志(atomic不可拷贝)
|
||
HSQUIRRELVM vm; // Squirrel VM
|
||
HSQOBJECT http_server_obj; // 保存的对象
|
||
asio::io_context* io_context = nullptr; // IO上下文(用于停止)
|
||
std::string host; // 记录主机(调试用)
|
||
std::string port; // 记录端口(调试用)
|
||
|
||
// 禁用拷贝构造和赋值(显式声明,避免误拷贝)
|
||
HttpServerCtx(const HttpServerCtx&) = delete;
|
||
HttpServerCtx& operator=(const HttpServerCtx&) = delete;
|
||
|
||
// 允许移动构造(用于unique_ptr)
|
||
HttpServerCtx(HttpServerCtx&&) = default;
|
||
HttpServerCtx& operator=(HttpServerCtx&&) = default;
|
||
|
||
// 构造函数
|
||
HttpServerCtx() = default;
|
||
HttpServerCtx(uint64_t id, HSQUIRRELVM vm_ptr, HSQOBJECT obj, const std::string& h, const std::string& p)
|
||
: server_id(id), vm(vm_ptr), http_server_obj(obj), host(h), port(p) {
|
||
}
|
||
};
|
||
|
||
// ====================== 全局服务器管理(核心修复:用unique_ptr存储,避免拷贝) ======================
|
||
static std::unordered_map<uint64_t, std::unique_ptr<HttpServerCtx>> g_server_ctxs;
|
||
static std::atomic<uint64_t> g_next_server_id{ 1 }; // 自增唯一ID(atomic不可拷贝,直接用)
|
||
static std::mutex g_server_mutex; // 保护全局容器
|
||
|
||
// ====================== 服务器主函数 ======================
|
||
void start_server(uint64_t server_id, const std::string& host, const std::string& port) {
|
||
// 查找上下文(加锁)
|
||
std::unique_lock<std::mutex> lock(g_server_mutex);
|
||
auto it = g_server_ctxs.find(server_id);
|
||
if (it == g_server_ctxs.end()) {
|
||
std::cerr << "服务器上下文不存在 (ID: " << server_id << ")" << std::endl;
|
||
return;
|
||
}
|
||
HttpServerCtx* ctx = it->second.get(); // 获取指针,不拷贝
|
||
lock.unlock(); // 手动解锁
|
||
|
||
asio::io_context io_context;
|
||
ctx->io_context = &io_context; // 关联IO上下文
|
||
|
||
try {
|
||
tcp::acceptor acceptor(io_context, tcp::endpoint(asio::ip::make_address(host), std::stoi(port)));
|
||
std::cout << "Server [" << server_id << "] listening on " << host << ":" << port << std::endl;
|
||
|
||
// 循环监听(支持退出)
|
||
while (ctx->running.load(std::memory_order_relaxed)) { // 原子操作读取
|
||
tcp::socket* socket = new tcp::socket(io_context);
|
||
try {
|
||
acceptor.accept(*socket);
|
||
// 启动客户端处理线程
|
||
std::thread(handle_client, std::move(socket), ctx->http_server_obj).detach();
|
||
}
|
||
catch (const asio::system_error& e) {
|
||
delete socket; // 中断时释放Socket
|
||
if (e.code() != asio::error::operation_aborted && ctx->running.load(std::memory_order_relaxed)) {
|
||
std::cerr << "Server [" << server_id << "] Accept error: " << e.what() << std::endl;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (const std::exception& e) {
|
||
std::cerr << "Server [" << server_id << "] error: " << e.what() << std::endl;
|
||
}
|
||
|
||
// 停止IO上下文
|
||
io_context.stop();
|
||
std::cout << "Server [" << server_id << "] stopped" << std::endl;
|
||
}
|
||
|
||
// ====================== Squirrel 绑定函数 - 创建HTTP客户端 ======================
|
||
static SQInteger CreateHttp(HSQUIRRELVM v) {
|
||
const SQChar* Host;
|
||
sq_getstring(v, 2, &Host);
|
||
const SQChar* Service;
|
||
sq_getstring(v, 3, &Service);
|
||
const SQChar* Content;
|
||
sq_getstring(v, 4, &Content);
|
||
|
||
try {
|
||
asio::io_context ioContext;
|
||
asio::ip::tcp::resolver resolver(ioContext);
|
||
auto endpoints = resolver.resolve(Host, Service);
|
||
asio::ip::tcp::socket socket(ioContext);
|
||
asio::connect(socket, endpoints);
|
||
|
||
// 发送HTTP请求
|
||
std::string request = Content;
|
||
asio::write(socket, asio::buffer(request));
|
||
|
||
// 读取响应
|
||
asio::streambuf response;
|
||
asio::error_code error;
|
||
while (asio::read(socket, response, asio::transfer_at_least(1), error)) {}
|
||
|
||
if (error && error != asio::error::eof) {
|
||
throw asio::system_error(error);
|
||
}
|
||
|
||
// 将响应内容返回
|
||
std::istream responseStream(&response);
|
||
std::ostringstream oss;
|
||
oss << responseStream.rdbuf();
|
||
sq_pushstring(v, oss.str().c_str(), -1);
|
||
return 1;
|
||
}
|
||
catch (const std::exception& e) {
|
||
return sq_throwerror(v, e.what());
|
||
}
|
||
catch (...) {
|
||
return sq_throwerror(v, _SC("Unknown error occurred"));
|
||
}
|
||
}
|
||
|
||
// ====================== Squirrel 绑定函数 - 创建HTTP服务器(核心修复:避免拷贝atomic) ======================
|
||
static SQInteger CreateHttpServer(HSQUIRRELVM v) {
|
||
const SQChar* host = nullptr;
|
||
const SQChar* port = nullptr;
|
||
HSQOBJECT HttpServerObject;
|
||
|
||
// 1. 参数校验
|
||
if (SQ_FAILED(sq_getstring(v, 2, &host)) ||
|
||
SQ_FAILED(sq_getstring(v, 3, &port)) ||
|
||
SQ_FAILED(sq_getstackobj(v, 4, &HttpServerObject))) {
|
||
sq_pushstring(v, _SC("param error: need (host:str, port:str, obj:HSQOBJECT)"), -1);
|
||
return SQ_ERROR;
|
||
}
|
||
|
||
// 2. 生成唯一服务器ID
|
||
uint64_t server_id = g_next_server_id.fetch_add(1, std::memory_order_relaxed);
|
||
|
||
try {
|
||
// 3. 动态创建上下文(避免拷贝)
|
||
auto ctx_ptr = std::unique_ptr<HttpServerCtx>(new HttpServerCtx(server_id, v, HttpServerObject, std::string(host), std::string(port)));
|
||
sq_addref(v, &ctx_ptr->http_server_obj); // 增加引用
|
||
|
||
// 4. 加锁存入全局(移动语义,不拷贝)
|
||
std::lock_guard<std::mutex> lock(g_server_mutex);
|
||
g_server_ctxs[server_id] = std::move(ctx_ptr); // 移动unique_ptr,不拷贝atomic
|
||
|
||
// 5. 启动服务器线程
|
||
std::thread server_thread(start_server, server_id, std::string(host), std::string(port));
|
||
server_thread.detach();
|
||
|
||
// 6. 返回唯一ID给Squirrel
|
||
sq_pushinteger(v, static_cast<SQInteger>(server_id));
|
||
return 1;
|
||
}
|
||
catch (...) {
|
||
// 异常清理
|
||
sq_release(v, &HttpServerObject); // 直接释放传入的对象
|
||
std::lock_guard<std::mutex> lock(g_server_mutex);
|
||
g_server_ctxs.erase(server_id);
|
||
sq_pushbool(v, false);
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
// ====================== Squirrel 绑定函数 - 停止HTTP服务器 ======================
|
||
static SQInteger StopHttpServer(HSQUIRRELVM v) {
|
||
SQInteger server_id_int;
|
||
if (SQ_FAILED(sq_getinteger(v, 2, &server_id_int)) || server_id_int <= 0) {
|
||
sq_pushstring(v, _SC("invalid server ID"), -1);
|
||
return SQ_ERROR;
|
||
}
|
||
uint64_t server_id = static_cast<uint64_t>(server_id_int);
|
||
|
||
std::lock_guard<std::mutex> lock(g_server_mutex);
|
||
auto it = g_server_ctxs.find(server_id);
|
||
if (it == g_server_ctxs.end()) {
|
||
// 修正:拼接错误信息中的 server ID
|
||
std::string err_msg = "server instance not found (ID: " + std::to_string(server_id) + ")";
|
||
sq_pushstring(v, err_msg.c_str(), -1);
|
||
return SQ_ERROR;
|
||
}
|
||
HttpServerCtx* ctx = it->second.get();
|
||
|
||
// 1. 标记停止(原子操作)
|
||
ctx->running.store(false, std::memory_order_relaxed);
|
||
|
||
// 2. 强制停止 IO 上下文(中断所有异步操作)
|
||
if (ctx->io_context) {
|
||
ctx->io_context->stop();
|
||
}
|
||
|
||
// 3. 释放 Squirrel 引用
|
||
sq_release(ctx->vm, &ctx->http_server_obj);
|
||
|
||
// 4. 移除上下文(unique_ptr 自动析构)
|
||
g_server_ctxs.erase(it);
|
||
|
||
sq_pushbool(v, true);
|
||
return 1;
|
||
}
|
||
|
||
// ====================== Squirrel 绑定函数 - 发送HTTP响应 ======================
|
||
static SQInteger HttpServerResponse_Write(HSQUIRRELVM v) {
|
||
SQUserPointer P;
|
||
if (SQ_FAILED(sq_getuserpointer(v, 2, &P))) {
|
||
return sq_throwerror(v, _SC("invalid socket pointer"));
|
||
}
|
||
tcp::socket* socket = static_cast<tcp::socket*>(P);
|
||
|
||
const SQChar* Content;
|
||
if (SQ_FAILED(sq_getstring(v, 3, &Content))) {
|
||
return sq_throwerror(v, _SC("invalid response content"));
|
||
}
|
||
|
||
try {
|
||
if (socket && socket->is_open()) {
|
||
std::string response = Content;
|
||
asio::write(*socket, asio::buffer(response));
|
||
}
|
||
}
|
||
catch (const std::exception& e) {
|
||
// 清理资源
|
||
if (socket) {
|
||
asio::error_code ec;
|
||
socket->close(ec);
|
||
delete socket;
|
||
}
|
||
return sq_throwerror(v, e.what());
|
||
}
|
||
catch (...) {
|
||
if (socket) {
|
||
asio::error_code ec;
|
||
socket->close(ec);
|
||
delete socket;
|
||
}
|
||
return sq_throwerror(v, _SC("Unknown error occurred"));
|
||
}
|
||
|
||
// 最后清理socket
|
||
if (socket) {
|
||
asio::error_code ec;
|
||
socket->close(ec);
|
||
delete socket;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// ====================== 辅助函数 - 注册Squirrel函数 ======================
|
||
static SQInteger register_Dio_func(HSQUIRRELVM v, SQFUNCTION f, const char* fname) {
|
||
sq_pushroottable(v);
|
||
sq_pushstring(v, fname, -1);
|
||
sq_newclosure(v, f, 0);
|
||
sq_newslot(v, -3, SQFalse);
|
||
sq_pop(v, 1); // 弹出根表
|
||
return 0;
|
||
}
|
||
|
||
// ====================== 注册所有Dio函数 ======================
|
||
static void RegisterDio(HSQUIRRELVM v) {
|
||
// 创建HTTP客户端
|
||
register_Dio_func(v, CreateHttp, _SC("Sq_CreateHttp"));
|
||
// HTTP服务器相关
|
||
register_Dio_func(v, CreateHttpServer, _SC("Sq_CreateHttpServer"));
|
||
register_Dio_func(v, StopHttpServer, _SC("Sq_StopHttpServer"));
|
||
// 发送HTTP响应
|
||
register_Dio_func(v, HttpServerResponse_Write, _SC("Sq_HttpServerResponse_Write"));
|
||
} |