当前位置: 首页 > article >正文

【C++接入大模型】WinHTTP类封装:实现对话式大模型接口访问

一、类设计概述

近期准备用C++做一些大预言模型方面的开发,先期计划实现C++调用公共的大模型Web接口,因为之前没做过C++的Web开发,经验少,所以对比了一些主流的框架,包括实际测试验证。以下是Windows平台下主流C++ HTTP库的对比分析:

库名称内置支持第三方依赖HTTPS支持API复杂度跨平台社区活跃度典型应用场景
WinHTTP✅ 是(Windows SDK)无(自动处理SSL/TLS)✅ 原生支持(无需OpenSSL)中等❌ 仅Windows中等系统级应用、快速集成、Windows专属开发
libcurl❌ 需手动集成需OpenSSL/zlib等✅ 依赖第三方库配置✅ 是跨平台项目、复杂协议需求
libhv❌ 需手动集成无(内置SSL支持)✅ 需编译时启用中等✅ 是中等高性能网络服务、异步IO场景
Boost.Beast❌ 需Boost库需OpenSSL✅ 需手动配置✅ 是现代C++项目、需要高度定制化
cpp-netlib❌ 需手动集成需Boost/Asio等✅ 需依赖项支持✅ 是传统企业级应用、遗留系统维护
Poco❌ 需手动集成需OpenSSL✅ 需手动配置中等✅ 是中等综合性框架需求、企业级开发
Crow❌ 需手动集成无(仅HTTP)❌ 无原生支持✅ 是轻量级Web服务、快速原型开发

转了一圈,最终选择了Windows原生WinHTTP API实现。

选型原因分析

  1. 内置支持与零配置优势

    • WinHTTP直接集成于Windows系统,无需额外安装或配置OpenSSL等依赖,避免了证书链、动态库路径等复杂问题。对于Windows专属应用,可直接调用winhttp.lib,实现"开箱即用",也支持流式接口。
  2. 简化HTTPS开发

    • WinHTTP自动处理TLS/SSL握手和证书验证(通过SECURITY_FLAG_IGNORE_*标志控制),开发者无需手动管理加密套件或协议版本,显著降低HTTPS开发门槛。
  3. 资源占用与性能平衡

    • 相比libhv或Boost.Beast等跨平台库,WinHTTP在Windows环境下表现出更优的资源利用率,尤其适合对内存占用敏感的系统工具或后台服务。
  4. API复杂度可控

    • 虽然WinHTTP API较为底层,但通过封装(如您提供的SimpleHttp类)可屏蔽复杂性,同时保留对请求头、超时等关键参数的控制能力。
  5. 企业级安全合规

    • WinHTTP通过微软签名验证,符合Windows安全合规要求,适合金融、政务等对供应链安全敏感的场景。

SimpleHttp核心特性

SimpleHttp 是基于 Windows WinHTTP API 封装的 C++ HTTP 客户端类,支持同步/异步 HTTP/HTTPS 请求,提供流式数据处理能力。特性包括:

  • 自适应HTTP/HTTPS协议
  • 支持流式传输(Streaming)
  • 线程安全的数据队列
  • 异常安全的资源管理
  • 符合C++20标准的现代语法

类接口说明

公共接口说明
1. 构造函数
SimpleHttp(const std::string& home, bool https = false);
  • 功能:初始化 WinHTTP 会话,设置基地址和协议类型
  • 参数
    • home:服务器基地址(如 "api.example.com"
    • https:是否使用 HTTPS(默认 false
  • 异常:会话初始化失败时抛出 std::runtime_error
2. 析构函数
~SimpleHttp();
  • 功能:释放所有资源,确保流式线程安全退出
  • 行为
    • 终止流式接收线程(若有)
    • 关闭所有 WinHTTP 句柄
3. GET 请求
int Get(const std::string& upath, std::string& resp, bool stream = false);
  • 功能:发起 GET 请求
  • 参数
    • upath:接口路径(自动拼接基地址)
    • resp:同步模式下存储完整响应
    • stream:是否启用流式处理(默认 false
  • 返回值
    • 0:成功(流式请求立即返回)
    • -1:失败(异常抛出前返回)
  • 异常:网络错误或 HTTP 状态码非 200 时抛出 std::runtime_error
4. POST 请求
int Post(const std::string& upath, const std::string& para, std::string& resp, bool stream = false);
  • 功能:发起 POST 请求
  • 参数
    • upath:接口路径
    • para:JSON 格式请求体
    • resp:同步模式下存储完整响应
    • stream:是否启用流式处理
  • 返回值:同 Get() 方法
  • 默认头:自动添加 Content-Type: application/json(可覆盖)
5. 流式响应获取
int TryFetchResp(std::string& resp);
  • 功能:轮询获取流式数据块
  • 参数resp 存储当前数据块
  • 返回值
    • >0:数据长度
    • 0:流结束
    • -1:暂无数据
  • 线程安全:通过互斥锁保护队列
6. 设置请求头
void SetHeaders(const std::unordered_map<std::string, std::string>& headers);
  • 功能:批量设置请求头
  • 参数:键值对映射(如 { {"Authorization", "Bearer token"} }
  • 合并策略:相同头字段自动合并(WINHTTP_ADDREQ_FLAG_COALESCE

异常处理

  • 抛出类型std::runtime_error
  • 典型场景
    • 网络连接失败(如 DNS 解析错误)
    • 请求创建失败
    • HTTP 状态码非 200
    • 流式线程异常终止

流式处理工作流程

  1. 调用 Get()/Post() 时设置 stream = true
  2. 轮询调用 TryFetchResp() 获取数据
  3. 当返回 0 时终止循环

示例代码

SimpleHttp client("https://api.example.com", true);
client.Get("/stream", response, true);

std::string chunk;
do {
    int size = client.TryFetchResp(chunk);
    if (size > 0) Process(chunk);
    else std::this_thread::sleep_for(std::chrono::milliseconds(100));
} while (size != 0);

实现细节

  • 线程管理:流式请求启动独立线程持续接收数据
  • 资源释放:析构函数确保线程和句柄正确关闭
  • 编码转换:内部使用 UTF-8 处理字符串(wstring2s/string2w

限制与注意事项

  1. 仅支持 Windows 平台(依赖 WinHTTP)
  2. 流式请求需手动管理生命周期
  3. HTTPS 默认忽略证书错误(生产环境需修改安全标志)

版本兼容性

  • Windows SDK:最低支持 Windows 7/Server 2008 R2
  • 编译器:需支持 C++17

二、核心函数深度分析

封装的类比较简单,支持GET和POST请求,支持流式接口,下面对封装的每个部分逐一展开说明。

1. 构造函数

SimpleHttp(const std::string& home, bool https = false)

实现要点:

  • 会话初始化:通过WinHttpOpen创建持久化会话,设置用户代理为"SimpleHttp/1.0"
  • 超时控制:统一设置5秒超时(连接/发送/接收/空闲)
  • 异常处理:会话创建失败时抛出runtime_error,包含系统错误码
  • 协议识别:通过_isHttps标记自动区分协议类型

设计考量:

  • 使用默认代理配置(WINHTTP_ACCESS_TYPE_DEFAULT_PROXY)提高兼容性
  • 零初始化参数(WINHTTP_NO_PROXY_NAME)避免冗余配置

2. 析构函数

~SimpleHttp()

资源管理:

  • 线程安全终止:通过原子变量_streamActive控制流式线程退出
  • 句柄清理
    • 顺序关闭请求→连接→会话句柄
    • 使用RAII模式确保资源释放

异常防御:

  • joinable()检查避免二次join导致崩溃
  • 线程终止前设置_streamActive=false防止死锁

3. Get请求

int Get(const std::string& upath, std::string& resp, bool stream = false)

工作流程:

  1. 调用SendRequest发起GET请求
  2. 根据stream参数选择同步/异步模式
  3. 同步模式直接返回完整响应
  4. 异步模式启动子线程持续接收数据

参数设计:

  • upath:URL路径自动拼接基地址
  • stream:控制响应处理模式(内存缓冲/流式队列)

4. Post请求

int Post(const std::string& upath, const std::string& para, std::string& resp, bool stream = false)

增强特性:

  • 自动设置Content-Type为application/json(未指定时)
  • 支持任意格式POST数据(通过para参数)
  • 数据长度自动计算(postData.size()

安全机制:

  • 使用WINHTTP_FLAG_REFRESH强制获取最新资源
  • HTTPS请求自动忽略证书错误(测试环境适用)

5. 流式响应获取

int TryFetchResp(std::string& resp)

队列管理:

  • 互斥锁保护的_datas队列
  • 三态返回值设计:
    • >0:有效数据长度
    • 0:流结束标记
    • -1:暂无数据

性能优化:

  • 队列首部数据优先出队(O(1)时间复杂度)
  • 空队列时立即返回避免阻塞

6. 请求头设置

void SetHeaders(const std::unordered_map<std::string, std::string>& headers)

头处理机制:

  • 使用WINHTTP_ADDREQ_FLAG_COALESCE自动合并重复头
  • 支持批量添加/覆盖现有头
  • 保留默认Content-Type设置

实现细节:

  • 字符串转换使用UTF-8编码
  • 头格式验证(自动添加冒号分隔符)

三、关键私有方法

1. 核心请求处理

int SendRequest(const std::wstring& method, ...)

多阶段处理:

  1. 连接建立WinHttpConnect创建目标服务器连接
  2. 请求创建WinHttpOpenRequest配置请求方法/路径
  3. 安全设置:HTTPS请求忽略证书错误
  4. 头注入:遍历_headers容器添加自定义头
  5. 数据发送WinHttpSendRequest传输请求体
  6. 响应处理:同步模式直接读取,异步模式启动接收线程

异常处理:

  • 每个WinAPI调用后立即检查返回值
  • 使用结构化异常处理(SEH)捕获系统级错误

2. 流式接收器

void StreamReceiver(HINTERNET hConnect, HINTERNET hRequest)

并发设计:

  • 使用原子变量_streamActive控制循环
  • 独立线程持续调用WinHttpReadData
  • 数据块即时推入线程安全队列

资源管理:

  • 函数退出时自动关闭连接/请求句柄
  • 异常传播前确保资源释放

3. 字符串转换

std::string wstring2s(const std::wstring& wstr)
std::wstring string2w(const std::string& str)

编码转换:

  • 基于Windows API的UTF-8转换
  • 预分配目标缓冲区优化性能
  • 空字符串快速路径

四、辅助函数解析

1. 全局替换函数

std::string ReplaceAll(std::string str, const std::string& from, const std::string& to)

实现特点:

  • 原地修改(接收值拷贝)
  • 处理重叠替换问题(pos递增策略)
  • 空模式保护避免死循环

2. JSON内容提取

std::string ExtractContent(std::string& resp)

解析逻辑:

  • 基于特征字符串定位(“content”:")
  • 支持转义字符处理(\n, \r, ", \)
  • 修改输入字符串实现流式解析

健壮性设计:

  • 非规范JSON容错处理
  • 内存安全(范围检查)

五、代码示例说明

1. 同步请求示例

SimpleHttp client("api.example.com", true);
std::string response;
client.Get("/data", response);
  • 即时获取完整响应
  • 适用于小型数据交互

2. 流式处理示例

client.Post("/stream", data, response, true);
while(int size = client.TryFetchResp(chunk)) {
    if(size > 0) process(chunk);
}
  • 实时处理大文件/持续数据流
  • 减少内存占用(无需完整缓存)

六、问题解决

Q:析构时触发断点

  • 原因:流式线程未正确终止
  • 解决方案:
    ~SimpleHttp() {
        CancelStream(); // 新增统一清理
        if(_hSession) WinHttpCloseHandle(_hSession);
    }
    
    void CancelStream() {
        if(_streamActive.exchange(false)) {
            WinHttpCloseHandle(_hRequest); // 强制终止
            if(_worker.joinable()) _worker.join();
        }
    }
    

Q:HTTPS证书错误

  • 解决方案:生产环境应移除证书忽略配置
    if(_isHttps) {
        DWORD dwFlags = SECURITY_FLAG_SECURE; // 严格验证
        WinHttpSetOption(...);
    }
    

七、总结

本实现通过封装WinHTTP API,在保持高性能的同时提供了简洁的接口。通过线程安全的流式处理、健壮的错误恢复和现代化的C++特性,适用于需要精细控制HTTP通信的Windows应用场景。未来可通过连接池、异步回调等机制进一步提升性能,满足更复杂的企业级需求。

八、完整代码

#include <windows.h>
#include <winhttp.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <tchar.h>
#include <mutex>
#include <atomic>
#include <thread>


#pragma comment(lib, "winhttp.lib")
#include <strsafe.h>


/// <summary>
/// 简单的HTTP类,自动支持http和https请求
/// 也支持流式处理,此时结果先缓存到队列,需主动调用TryFetchResp去轮询结果
/// </summary>
class SimpleHttp
{
public:
    SimpleHttp(const std::string& home, bool https = false) :_strBaseUrl(home), _isHttps(https)
    {
        // 初始化WinHTTP会话 [[1]][[2]]
        _hSession = WinHttpOpen(L"SimpleHttp/1.0",
            WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
            WINHTTP_NO_PROXY_NAME,
            WINHTTP_NO_PROXY_BYPASS,
            0);
        if (!_hSession) 
        {
            throw std::runtime_error("Failed to initialize WinHTTP session: " + GetLastError());
        }

        // 设置默认超时(5秒)
        DWORD timeout = 5000;
        WinHttpSetTimeouts(_hSession, timeout, timeout, timeout, timeout);
    }
    ~SimpleHttp()
    {
        if (_worker.joinable())
        {
            _streamActive = false;
            _worker.join();
        }
        if (_hSession) WinHttpCloseHandle(_hSession);
    }

    /// <summary>
    /// 发起GET请求
    /// </summary>
    /// <param name="upath">接口名</param>
    /// <param name="resp">非流式请求时接收应答数据</param>
    /// <param name="stream">是否为流式请求</param>
    /// <returns></returns>
    int Get(const std::string& upath, std::string& resp, bool stream = false)
    {
        return SendRequest(L"GET", upath, "", resp, stream);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="upath">接口名</param>
    /// <param name="para">JSON请求参数</param>
    /// <param name="resp">非流式请求时接收应答数据</param>
    /// <param name="stream">是否为流式请求</param>
    /// <returns></returns>
    int Post(const std::string& upath, const std::string& para, std::string& resp, bool stream = false)
    {
        return SendRequest(L"POST", upath, para, resp, stream);
    }

    /// <summary>
    /// 获取流式结果
    /// </summary>
    /// <param name="resp"></param>
    /// <returns>返回本次数据包大小,等于0表示数据包接收完成,-1表示没数据</returns>
    int TryFetchResp(std::string& resp)
    {
        std::lock_guard<std::mutex> guard(_mtxDatas);
        if (_datas.empty())
        {
            return (_streamActive ? -1 : 0);
        }
        resp = _datas[0];
        _datas.erase(_datas.begin());
        return static_cast<int>(resp.size());
    }

    /// <summary>
    /// 设置请求头
    /// </summary>
    /// <param name="headers"></param>
    void SetHeaders(const std::unordered_map<std::string, std::string>& headers)
    {
        for (auto& e : headers)
        {
            _headers[e.first] = e.second;
        }
    }

protected:
    /// <summary>
    /// 写入接收数据
    /// </summary>
    /// <param name="dat"></param>
    void Push(const std::string& dat)
    {
        std::lock_guard<std::mutex> guard(_mtxDatas);
        if(!dat.empty()) _datas.push_back(dat);
    }

private:
    int SendRequest(const std::wstring& method, const std::string& upath, const std::string& postData, std::string& resp, bool stream) 
    {
        DWORD dwSize = 0;
        DWORD dwDownloaded = 0;
        LPSTR pszOutBuffer;
        BOOL bResults = FALSE;
        HINTERNET hConnect = nullptr;
        HINTERNET hRequest = nullptr;

        try 
        {
            // 创建连接
            hConnect = WinHttpConnect(_hSession,
                string2w(_strBaseUrl).c_str(),
                INTERNET_DEFAULT_PORT,
                0);
            if (!hConnect) throw std::runtime_error("Connection failed");

            // 创建请求
            hRequest = WinHttpOpenRequest(hConnect,
                method.c_str(), 
                string2w(upath).c_str(),
                nullptr, 
                WINHTTP_NO_REFERER,
                WINHTTP_DEFAULT_ACCEPT_TYPES,
                (_isHttps ? (WINHTTP_FLAG_SECURE | WINHTTP_FLAG_REFRESH) : 0));
            if (!hRequest) throw std::runtime_error("Request creation failed");

            // 设置安全选项(忽略证书错误)
            if (_isHttps)
            {
                DWORD dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
                    SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
                WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));
            }

            // 设置请求头
            SetHeaders(hRequest);

            // 发送请求
            bResults = WinHttpSendRequest(hRequest,
                WINHTTP_NO_ADDITIONAL_HEADERS,
                0,
                (LPVOID)postData.c_str(),
                (DWORD)postData.size(),
                (DWORD)postData.size(),
                0);
            if (!bResults) throw std::runtime_error("Send request failed");

            // 接收响应
            if (!WinHttpReceiveResponse(hRequest, nullptr))
            {
                throw std::runtime_error("Receive response failed");
            }

            // 获取状态码 [[10]]
            DWORD dwStatusCode = 0;
            DWORD dwSize = sizeof(dwStatusCode);
            WinHttpQueryHeaders(hRequest,
                WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                WINHTTP_HEADER_NAME_BY_INDEX, 
                &dwStatusCode,
                &dwSize,
                WINHTTP_NO_HEADER_INDEX);

            if (dwStatusCode != 200) 
            {
                throw std::runtime_error("HTTP error: " + std::to_string(dwStatusCode));
            }

            // 流式子线程处理
            if (stream)
            {
                _streamActive = true;
                _worker = std::thread(&SimpleHttp::StreamReceiver, this, hConnect, hRequest);
                return 0; // 立即返回
            }

            // 处理响应数据
            std::string buffer;
            do 
            {
                dwSize = 0;
                if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize <= 0) break;

                size_t nRecv = dwSize;
                pszOutBuffer = new char[nRecv + 1];
                ZeroMemory(pszOutBuffer, nRecv + 1);

                if (WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded))
                {
                    buffer.append(pszOutBuffer, dwDownloaded);
                }
                delete[] pszOutBuffer;
            } while (dwSize > 0);

            resp = buffer;
            return 0;
        }
        catch (const std::exception& e) 
        {
            if (hRequest) WinHttpCloseHandle(hRequest);
            if (hConnect) WinHttpCloseHandle(hConnect);
            throw std::runtime_error("WinHTTP Error: " + std::string(e.what()) +
                " [ErrorCode: " + std::to_string(GetLastError()) + "]");
            return -1;
        }
    }

    /// <summary>
    /// 流式接收应答
    /// </summary>
    void StreamReceiver(HINTERNET hConnect, HINTERNET hRequest)
    {
        DWORD dwSize = 0;
        DWORD dwDownloaded = 0;
        char* pszOutBuffer = nullptr;
        std::string strErr;

        try 
        {
            while (_streamActive) 
            {
                dwSize = 0;
                if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break;

                const size_t nRecv = dwSize;
                pszOutBuffer = new char[nRecv + 1];
                ZeroMemory(pszOutBuffer, nRecv + 1);

                if (WinHttpReadData(hRequest, pszOutBuffer, dwSize, &dwDownloaded))
                {
                    Push(std::string(pszOutBuffer, dwDownloaded));
                }
                delete[] pszOutBuffer;
            }
        }
        catch (...) 
        {
            strErr = "stream recv data error";
        }

        // 发送结束标记
        _streamActive = false;
        WinHttpCloseHandle(hRequest);
        WinHttpCloseHandle(hConnect);
        if (!strErr.empty())
        {
            throw std::runtime_error(strErr.c_str());
        }
    }

    /// <summary>
    /// 设置请求头
    /// </summary>
    void SetHeaders(HINTERNET hRequest)
    {
        // 创建请求后立即设置请求头
        for (const auto& h : _headers) 
        {
            std::wstring header = string2w(h.first + ": " + h.second);
            if (!WinHttpAddRequestHeaders(hRequest,
                header.c_str(),
                static_cast<DWORD>(header.length()),
                WINHTTP_ADDREQ_FLAG_COALESCE))
            {
                //throw std::runtime_error("Failed to set header: " + h.first + " [Error: " + std::to_string(GetLastError()) + "]");
            }
        }
    }

    // std::wstring → std::string (UTF-8)
    std::string wstring2s(const std::wstring& wstr) {
        if (wstr.empty()) return {};
        int len = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
        std::string result(len, '\0');
        WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), (LPSTR)result.data(), len, nullptr, nullptr);
        return result;
    }

    // std::string (UTF-8) → std::wstring
    std::wstring string2w(const std::string& str) {
        if (str.empty()) return {};
        int len = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
        std::wstring result(len, L'\0');
        MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), (LPWSTR)result.data(), len);
        return result;
    }

private:
    // 是否https
    bool _isHttps{ false };
    // 基地址
    std::string _strBaseUrl;
    // 默认headers
    std::unordered_map<std::string, std::string> _headers;
    // 异步数据
    std::vector<std::string> _datas;
    std::mutex _mtxDatas;

    // WinHttp对象
    HINTERNET _hSession = nullptr;

    // 流式操作子线程
    std::atomic<bool> _streamActive{ false };
    std::thread _worker;
};


std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {
    if (from.empty()) return str; // 避免死循环[[2]]

    size_t pos = 0;
    while ((pos = str.find(from, pos)) != std::string::npos) 
    {
        str.replace(pos, from.length(), to);
        pos += to.length();
    }
    return str;
}

std::string ExtractContent(std::string& resp) {
    std::vector<std::string> results;
    std::string key = "\"content\":";
    size_t pos = 0;

    while ((pos = resp.find(key, pos)) != std::string::npos) 
    {
        pos += key.length();
        // 跳过空白和冒号
        while (pos < resp.size() && (resp[pos] == ' ' || resp[pos] == ':')) pos++;

        if (resp[pos] != '"') continue; // 非字符串值跳过

        size_t start = ++pos;
        std::string content;

        // 处理转义字符和双引号
        while (pos < resp.size()) 
        {
            char c = resp[pos++];
            if (c == '"') break;
            else if (c == '\\' && pos < resp.size())
            {
                switch (resp[pos++])
                {
                case 'n':
                    c = '\n';
                    break;
                case '\r':
                    c = '\r';
                    break;
                case '\\':
                    c = '\\';
                    break;
                case '"':
                    c = '\"';
                    break;
                default:
                    pos--;
                    break;
                }
            }
            content += c;
        }
        resp = resp.substr(pos);
        return content;
    }
    resp = "";
    return resp;
}

int main()
{
    int iii = 0;
    std::cin >> iii;
    // 请求参数
    char para[4096] = "{\"stream\" : true,\"model\":\"deepseek-r1-distill-qwen-32b\",\"messages\":[{\"role\":\"user\",\"content\":\"Please help me write a C++ class for parsing XML, with user-friendly interfaces that support XPath and iterative data access.\"}]}";

    // char para[4096] = "{\"stream\" : true,\"model\":\"deepseek-r1-distill-qwen-32b\",\"messages\":[{\"role\":\"user\",\"content\":\"The prime numbers within 10?\"}]}";

    auto url = "https://cloud.infini-ai.com/maas/v1/chat/completions";
    auto api_key = "Bearer sk-************";
    auto model_name = "deepseek-r1-distill-qwen-32b";

    SimpleHttp web("cloud.infini-ai.com", true);
    web.SetHeaders({ { "Content-Type", "application/json" }, { "Authorization", "Bearer sk-daxdj5ksqdc6iuvn" } });

    std::string resp;
    web.Post("/maas/v1/chat/completions", para, resp, true);
    int nRecv = 0;

    do
    {
        nRecv = web.TryFetchResp(resp);
        if (nRecv > 0)
        {
            while (!resp.empty())
            {
                std::cout << ExtractContent(resp);
            }
        }
        else
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

    } while (nRecv);
    return 0;
}

运行结果:
在这里插入图片描述


http://www.kler.cn/a/612072.html

相关文章:

  • 适合DBA的brew上手指南
  • (C语言)网络编程之TCP(含三次握手和四次挥手详解)
  • 适配器模式及其典型应用
  • Vue-create-vue-开发流程-项目结构-API风格-ElementPlus-入门准备工作
  • 【保姆级别教程】VMware虚拟机安装Mac OS15苹果系统附带【VMware TooLS安装】【解锁虚拟机】和【Mac OS与真机共享文件夹】手把手教程
  • 分布式共识算法解密:从Paxos到Raft的演进之路
  • 使用string和string_view(一)——C风格字符串、字符串字面量和std::string
  • 批量将 PDF 转换为 Word/PPT/Txt/Jpg图片等其它格式
  • 开发DOM更新算法
  • [python]基于yolov8实现热力图可视化支持图像视频和摄像头检测
  • CentOS 7安装 mysql
  • 老是忘记package.json,备忘一下 webpack 环境下 Vue Cli 和 Vite 命令行工具对比
  • 【Pandas】pandas Series to_xarray
  • SpringBoot集成腾讯云OCR实现身份证识别
  • 【牛客网】数据分析笔试刷题
  • Charles抓HTTPS包
  • 数据结构:汉诺塔问题的递归求解和分析
  • 部分 Bash 内置命令的详解
  • 企业网站源码HTML成品网站与网页代码模板指南
  • 学习记录-Ajax-自封装axios函数