Stabilize Conan build and remove stale TLS code

Simplifies the active Windows build path around Boost.Beast/OpenSSL, fixes VS2017/clang-cl compatibility, and removes unused BearSSL/WinHTTP remnants so the project builds and tests cleanly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 16:10:20 +08:00
parent d22a0102e2
commit 3e9ba04df5
316 changed files with 207 additions and 77902 deletions

View File

@@ -3,14 +3,17 @@
#include "file/file_io.hpp"
#include "net/http_client.hpp"
#include <cstdio>
#include <boost/json.hpp>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <string>
#include <utility>
#include <vector>
// ---- 内部状态 ----
namespace json = boost::json;
namespace {
bool g_initialized = false;
@@ -92,6 +95,14 @@ void parse_config_file(const char* path)
}
}
char* copy_to_c_string(const std::string& value)
{
char* output = static_cast<char*>(std::malloc(value.size() + 1));
if (!output) return nullptr;
std::memcpy(output, value.c_str(), value.size() + 1);
return output;
}
} // anonymous namespace
// ---- 初始化 / 销毁 ----
@@ -153,25 +164,20 @@ DSTALK_API void dstalk_set_model(const char* model)
DSTALK_API int dstalk_chat(const char* input, char** output)
{
if (!g_initialized || !input || !output) return -1;
*output = nullptr;
auto result = g_ai.chat(g_history, input);
if (!result.ok) {
// 返回错误信息
*output = static_cast<char*>(std::malloc(result.error.size() + 1));
if (*output) {
std::memcpy(*output, result.error.c_str(), result.error.size() + 1);
}
*output = copy_to_c_string(result.error);
return -1;
}
// 更新历史
char* reply = copy_to_c_string(result.content);
if (!reply) return -1;
g_history.push_back({"user", input});
g_history.push_back({"assistant", result.content});
*output = static_cast<char*>(std::malloc(result.content.size() + 1));
if (*output) {
std::memcpy(*output, result.content.c_str(), result.content.size() + 1);
}
*output = reply;
return 0;
}
@@ -228,37 +234,14 @@ DSTALK_API void dstalk_session_clear(void)
DSTALK_API int dstalk_session_save(const char* path)
{
if (!g_initialized || !path) return -1;
// 简单格式: 每行 JSON {"role":"...","content":"..."}
std::string data;
for (const auto& m : g_history) {
// 转义 JSON 特殊字符和控制字符
auto escape = [](const std::string& s) -> std::string {
std::string out;
for (char c : s) {
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
case '\b': out += "\\b"; break;
case '\f': out += "\\f"; break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
char buf[8];
std::snprintf(buf, sizeof(buf), "\\u%04x",
static_cast<unsigned char>(c));
out += buf;
} else {
out += c;
}
break;
}
}
return out;
};
data += "{\"role\":\"" + escape(m.role) + "\",\"content\":\""
+ escape(m.content) + "\"}\n";
json::object entry;
entry["role"] = m.role;
entry["content"] = m.content;
data += json::serialize(entry);
data += '\n';
}
return file_write_all(path, data.c_str());
}
@@ -275,7 +258,6 @@ DSTALK_API int dstalk_session_load(const char* path)
std::vector<dstalk::ai::Message> parsed;
// 逐行解析简化的 JSON
size_t pos = 0;
while (pos < data.size()) {
size_t nl = data.find('\n', pos);
@@ -284,24 +266,16 @@ DSTALK_API int dstalk_session_load(const char* path)
pos = (nl != std::string::npos) ? nl + 1 : data.size();
if (line.empty()) continue;
// 简陋 JSON 解析: 找 "role":"..." 和 "content":"..."
auto extract = [&](const std::string& key) -> std::string {
std::string search = "\"" + key + "\":\"";
size_t start = line.find(search);
if (start == std::string::npos) return "";
start += search.size();
size_t end = start;
while (end < line.size()) {
if (line[end] == '"' && (end == 0 || line[end-1] != '\\')) break;
end++;
try {
auto obj = json::parse(line).as_object();
auto* role = obj.if_contains("role");
auto* content_val = obj.if_contains("content");
if (role && content_val && role->is_string() && content_val->is_string()) {
parsed.push_back({json::value_to<std::string>(*role),
json::value_to<std::string>(*content_val)});
}
return line.substr(start, end - start);
};
std::string role = extract("role");
std::string content_val = extract("content");
if (!role.empty() && !content_val.empty()) {
parsed.push_back({role, content_val});
} catch (const std::exception&) {
return -1;
}
}
@@ -315,14 +289,18 @@ DSTALK_API int dstalk_session_load(const char* path)
DSTALK_API int dstalk_file_read(const char* path, char** content)
{
if (!g_initialized || !path || !content) return -1;
*content = nullptr;
size_t len = 0;
char* buf = file_read_all(path, &len);
if (!buf) return -1;
*content = buf;
return 0;
}
DSTALK_API int dstalk_file_write(const char* path, const char* content)
{
if (!g_initialized || !path || !content) return -1;
return file_write_all(path, content);
}

View File

@@ -0,0 +1 @@
#include <boost/json/src.hpp>

View File

@@ -1,146 +0,0 @@
#pragma once
// BearSSL TLS stream adapter — Beast SyncStream compatible
// Replaces boost::asio::ssl::stream<boost::asio::ip::tcp::socket>
#include <boost/asio/ip/tcp.hpp>
#include <bearssl.h>
#include <vector>
#include <string>
#include <memory>
namespace dstalk {
namespace net {
// Platform-specific system trust anchor loader
std::vector<br_x509_trust_anchor> load_system_trust_anchors();
// Beast-compatible TLS stream backed by BearSSL (MIT license)
class BearSSLStream {
public:
using next_layer_type = boost::asio::ip::tcp::socket;
using executor_type = next_layer_type::executor_type;
using lowest_layer_type = next_layer_type;
explicit BearSSLStream(boost::asio::io_context& ioc);
~BearSSLStream();
// Non-copyable
BearSSLStream(const BearSSLStream&) = delete;
BearSSLStream& operator=(const BearSSLStream&) = delete;
// Perform TLS handshake with SNI hostname
void handshake(const std::string& host);
// Beast SyncStream requirements
template<typename MutableBufferSequence>
size_t read_some(const MutableBufferSequence& buffers);
template<typename MutableBufferSequence>
size_t read_some(const MutableBufferSequence& buffers,
boost::system::error_code& ec);
template<typename ConstBufferSequence>
size_t write_some(const ConstBufferSequence& buffers);
template<typename ConstBufferSequence>
size_t write_some(const ConstBufferSequence& buffers,
boost::system::error_code& ec);
next_layer_type& next_layer() { return socket_; }
lowest_layer_type& lowest_layer() { return socket_; }
executor_type get_executor() { return socket_.get_executor(); }
private:
// BearSSL I/O callbacks (static, receive 'this' as ctx)
static int s_read(void* ctx, unsigned char* buf, size_t len);
static int s_write(void* ctx, const unsigned char* buf, size_t len);
// Low-level socket I/O used by BearSSL callbacks
int low_read(unsigned char* buf, size_t len);
int low_write(const unsigned char* buf, size_t len);
// Reset engine for re-handshake
void reset_engine(const std::string& host);
boost::asio::ip::tcp::socket socket_;
bool handshake_done_ = false;
// BearSSL client state
br_ssl_client_context sc_;
br_x509_minimal_context xc_;
std::vector<unsigned char> iobuf_;
br_sslio_context sslioc_;
std::vector<br_x509_trust_anchor> anchors_;
};
// ====== template implementations ======
template<typename MutableBufferSequence>
size_t BearSSLStream::read_some(const MutableBufferSequence& buffers)
{
boost::system::error_code ec;
size_t n = read_some(buffers, ec);
if (ec) throw boost::system::system_error(ec);
return n;
}
template<typename MutableBufferSequence>
size_t BearSSLStream::read_some(const MutableBufferSequence& buffers,
boost::system::error_code& ec)
{
namespace asio = boost::asio;
// Gather buffer into contiguous memory for BearSSL
size_t total = asio::buffer_size(buffers);
if (total == 0) return 0;
std::vector<unsigned char> tmp(total);
int ret = br_sslio_read(&sslioc_, tmp.data(), (int)total);
if (ret < 0) {
ec = boost::system::error_code(ret, boost::system::system_category());
return 0;
}
if (ret == 0) {
ec = boost::asio::error::eof;
return 0;
}
// Copy to output buffers
asio::buffer_copy(buffers, asio::buffer(tmp.data(), (size_t)ret));
ec.assign(0, ec.category());
return (size_t)ret;
}
template<typename ConstBufferSequence>
size_t BearSSLStream::write_some(const ConstBufferSequence& buffers)
{
boost::system::error_code ec;
size_t n = write_some(buffers, ec);
if (ec) throw boost::system::system_error(ec);
return n;
}
template<typename ConstBufferSequence>
size_t BearSSLStream::write_some(const ConstBufferSequence& buffers,
boost::system::error_code& ec)
{
namespace asio = boost::asio;
size_t total = asio::buffer_size(buffers);
if (total == 0) return 0;
// Gather into contiguous buffer
std::vector<unsigned char> tmp(total);
asio::buffer_copy(asio::buffer(tmp), buffers);
int ret = br_sslio_write_all(&sslioc_, tmp.data(), (int)total);
if (ret < 0) {
ec = boost::system::error_code(ret, boost::system::system_category());
return 0;
}
ec.assign(0, ec.category());
return total;
}
} // namespace net
} // namespace dstalk

View File

@@ -11,6 +11,8 @@
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>
#include <string_view>
namespace beast = boost::beast;
namespace http = beast::http;
namespace asio = boost::asio;
@@ -97,49 +99,41 @@ HttpResponse HttpClient::post_stream(
http::read_header(stream, buffer, parser);
result.status_code = parser.get().result_int();
result.body = parser.get().body();
beast::error_code ec;
if (on_line) {
std::string fragment = result.body;
size_t pos = 0;
while (pos < fragment.size()) {
size_t nl = fragment.find('\n', pos);
if (nl == std::string::npos) break;
std::string line = fragment.substr(pos, nl - pos);
if (!line.empty() && line.back() == '\r')
line.pop_back();
if (!on_line(line)) goto done;
pos = nl + 1;
}
if (pos > 0)
fragment = fragment.substr(pos);
std::string fragment = parser.get().body();
auto emit_lines = [&]() -> bool {
size_t pos = 0;
while (pos < fragment.size()) {
size_t nl = fragment.find('\n', pos);
if (nl == std::string::npos) break;
std::string line = fragment.substr(pos, nl - pos);
if (!line.empty() && line.back() == '\r')
line.pop_back();
if (!on_line(line)) return false;
pos = nl + 1;
}
if (pos > 0)
fragment = fragment.substr(pos);
return true;
};
if (!emit_lines()) goto done;
size_t processed = result.body.size();
size_t processed = parser.get().body().size();
while (!parser.is_done()) {
http::read_some(stream, buffer, parser, ec);
if (ec) break;
const std::string& full_body = parser.get().body();
if (full_body.size() > processed) {
std::string new_data = full_body.substr(processed);
result.body += new_data;
std::string_view new_data(full_body.data() + processed,
full_body.size() - processed);
processed = full_body.size();
fragment += new_data;
pos = 0;
while (pos < fragment.size()) {
size_t nl = fragment.find('\n', pos);
if (nl == std::string::npos) break;
std::string line = fragment.substr(pos, nl - pos);
if (!line.empty() && line.back() == '\r')
line.pop_back();
if (!on_line(line)) goto done;
pos = nl + 1;
}
if (pos > 0)
fragment = fragment.substr(pos);
fragment.append(new_data.data(), new_data.size());
if (!emit_lines()) goto done;
}
}
if (!fragment.empty()) {
@@ -152,10 +146,10 @@ HttpResponse HttpClient::post_stream(
while (!parser.is_done()) {
http::read_some(stream, buffer, parser, ec);
if (ec) break;
result.body = parser.get().body();
}
}
done:
result.body = parser.get().body();
beast::get_lowest_layer(stream).cancel();
stream.shutdown(ec);
} catch (std::exception& e) {

View File

@@ -15,8 +15,7 @@ struct HttpResponse {
/*
* HTTPS 客户端统一接口
* Windows: WinHTTP 实现 (零依赖)
* 其他平台: Boost.Beast + OpenSSL 实现
* 所有平台统一使用 Boost.Beast + OpenSSL 实现
*/
class HttpClient {
public:

View File

@@ -1,52 +0,0 @@
#pragma once
#include <string>
#include <unordered_map>
/*
* 纯 WinHTTP 实现 (Windows 内置, 零第三方依赖)
* 与 net/http_client.hpp 接口兼容
*/
namespace dstalk {
namespace net {
struct HttpResponse {
int status_code = 0;
std::string body;
std::unordered_map<std::string, std::string> headers;
};
class HttpClient {
public:
HttpClient();
~HttpClient();
void set_timeout(int connect_sec, int request_sec);
HttpResponse post_json(
const std::string& host,
const std::string& port,
const std::string& target,
const std::string& json_body,
const std::unordered_map<std::string, std::string>& extra_headers
);
// 流式 POST (SSE 回调)
HttpResponse post_stream(
const std::string& host,
const std::string& port,
const std::string& target,
const std::string& json_body,
const std::unordered_map<std::string, std::string>& extra_headers,
bool (*on_line)(const std::string& line, void* userdata),
void* userdata = nullptr
);
private:
struct Impl;
Impl* impl_;
};
} // namespace net
} // namespace dstalk