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:
@@ -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);
|
||||
}
|
||||
|
||||
1
dstalk-core/src/boost_json.cpp
Normal file
1
dstalk-core/src/boost_json.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include <boost/json/src.hpp>
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -15,8 +15,7 @@ struct HttpResponse {
|
||||
|
||||
/*
|
||||
* HTTPS 客户端统一接口
|
||||
* Windows: WinHTTP 实现 (零依赖)
|
||||
* 其他平台: Boost.Beast + OpenSSL 实现
|
||||
* 所有平台统一使用 Boost.Beast + OpenSSL 实现
|
||||
*/
|
||||
class HttpClient {
|
||||
public:
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user