解决使用 libcurl 与 Charles 抓包的问题

发布时间 2023-11-16 12:12:22作者: 小悠123

解决使用 libcurl 与 Charles 抓包的问题

在使用 C++ 发送网络请求时,利用 libcurl 是个不错的选择。然而,有时候我们需要使用 Charles 抓包工具来检查这些请求,但可能会遇到无法抓取请求包的情况,或者 libcurl 提示代理名称无法解析等问题。

设置 libcurl 使用代理

要抓取 libcurl 的请求,需要设置代理。下面的代码演示了如何通过 libcurl 设置代理:

curl_easy_setopt( m_CURL, CURLOPT_PROXY, "127.0.0.1:8888" );
//8888端口就是在Charles 代理设置里面的http代理的端口

获取系统代理配置

为了正确设置 libcurl 的代理,通常在 Windows 系统上,可以使用 WinHTTP 库来获取系统的代理配置信息。以下是网上找到的(注意:使用了wxWidgets库 里面的类)

struct PROXY_CONFIG
        {
            wxString host;
            wxString username;
            wxString password;
        };

bool GetSystemProxyConfig(const wxString& aURL, PROXY_CONFIG& aCfg)
{
    // 原始来源:来自微软示例(公共领域)
    // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/WinhttpProxy/cpp/GetProxy.cpp#L844

    bool                                 autoProxyDetect = false; // 标志:是否需要自动代理检测
    WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyConfig = { 0 }; // 存储当前用户的 IE 代理配置
    WINHTTP_AUTOPROXY_OPTIONS            autoProxyOptions = { 0 }; // 自动代理选项
    WINHTTP_PROXY_INFO                   autoProxyInfo = { 0 }; // 自动代理信息
    HINTERNET                            proxyResolveSession = NULL; // 代理解析会话
    bool                                 success = false; // 是否成功获取代理配置

    // 获取当前用户的 IE 代理配置信息
    if (WinHttpGetIEProxyConfigForCurrentUser(&ieProxyConfig))
    {
        // 如果存在自动代理检测或自动代理配置 URL,设置相应的标志和选项
        if (ieProxyConfig.fAutoDetect)
        {
            autoProxyDetect = true;
        }

        if (ieProxyConfig.lpszAutoConfigUrl != NULL)
        {
            autoProxyDetect = true;
            autoProxyOptions.lpszAutoConfigUrl = ieProxyConfig.lpszAutoConfigUrl;
        }
    }
    else if (GetLastError() == ERROR_FILE_NOT_FOUND)
    {
        // 如果出现特定的错误代码,继续尝试寻找代理
        autoProxyDetect = true;
    }

    // 如果需要自动代理检测
    if (autoProxyDetect)
    {
        proxyResolveSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
                                          WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);

        if (proxyResolveSession)
        {
            // 根据自动代理配置 URL 或自动检测模式设置相应标志和选项
            if (autoProxyOptions.lpszAutoConfigUrl != NULL)
            {
                autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
            }
            else
            {
                autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
                autoProxyOptions.dwAutoDetectFlags =
                    WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
            }

            // 最初不进行自动登录,让 Windows 使用缓存
            autoProxyOptions.fAutoLogonIfChallenged = FALSE;

            // 获取给定 URL 的代理信息
            autoProxyDetect = WinHttpGetProxyForUrl(proxyResolveSession, aURL.c_str(),
                                                    &autoProxyOptions, &autoProxyInfo);

            // 如果未成功并出现登录失败,尝试使用自动登录
            if (!autoProxyDetect && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
            {
                autoProxyOptions.fAutoLogonIfChallenged = TRUE;

                // 现在尝试自动登录
                autoProxyDetect = WinHttpGetProxyForUrl(proxyResolveSession, aURL.c_str(),
                                                        &autoProxyOptions, &autoProxyInfo);
            }

            WinHttpCloseHandle(proxyResolveSession);
        }
    }

    // 如果成功获取了代理信息
    if (autoProxyDetect)
    {
        if (autoProxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY)
        {
            // autoProxyInfo 将返回一个以分号分隔的代理列表
            // todo...可能需要更好的选择逻辑
            wxString          proxyList = autoProxyInfo.lpszProxy;
            wxStringTokenizer tokenizer(proxyList, wxT(";"));

            if (tokenizer.HasMoreTokens())
            {
                aCfg.host = tokenizer.GetNextToken();
            }

            success = true;
        }
    }
    else
    {
        // 如果没有使用自动代理,则直接从 IE 配置中获取代理信息
        if (ieProxyConfig.lpszProxy != NULL)
        {
            // IE 代理配置可能返回 : 或 :: 作为空代理
            aCfg.host = ieProxyConfig.lpszProxy;

            if (aCfg.host != ":" && aCfg.host != "::")
            {
                success = true;
            }
        }
    }

    // 清理 Win32 API 返回的字符串内存
    if (autoProxyInfo.lpszProxy)
    {
        GlobalFree(autoProxyInfo.lpszProxy);
        autoProxyInfo.lpszProxy = NULL;
    }

    if (autoProxyInfo.lpszProxyBypass)
    {
        GlobalFree(autoProxyInfo.lpszProxyBypass);
        autoProxyInfo.lpszProxyBypass = NULL;
    }

    if (ieProxyConfig.lpszAutoConfigUrl != NULL)
    {
        GlobalFree(ieProxyConfig.lpszAutoConfigUrl);
        ieProxyConfig.lpszAutoConfigUrl = NULL;
    }

    if (ieProxyConfig.lpszProxy != NULL)
    {
        GlobalFree(ieProxyConfig.lpszProxy);
        ieProxyConfig.lpszProxy = NULL;
    }

    if (ieProxyConfig.lpszProxyBypass != NULL)
    {
        GlobalFree(ieProxyConfig.lpszProxyBypass);
        ieProxyConfig.lpszProxyBypass = NULL;
    }

    return success; // 返回是否成功获取代理配置
}

为了成功获取网络请求的代理配置并设置libcurl以实现正确的代理,我们需要解析获取到的代理信息。有时,代理配置可能以形如"http=127.0.0.1:8888;https=127.0.0.1:8888"的格式出现,这种情况下直接传入libcurl可能会导致问题。为了确保正确设置HTTP和HTTPS代理,我需要按照代理信息中的不同键值对进行解析,并分配给相应的变量。

以下是我用于解析代理配置并设置HTTP和HTTPS代理的代码段:

 const char* proxyConfig = cfg.host; // 代理配置示例 http=127.0.0.1:8888;https=127.0.0.1:8888
const char* httpProxy = NULL;
const char* httpsProxy = NULL;

// 检查代理配置是否包含 "http=" 或 "https="
if(strstr(proxyConfig, "http=") != NULL || strstr(proxyConfig, "https=") != NULL) {
    // 解析代理配置,按分号分割
    char* token;
    char* context = NULL;
    token = strtok_s((char*)proxyConfig, ";", &context);

    while(token != NULL) {
        // 根据 "http=" 或 "https=" 设置对应的代理变量
        if(strstr(token, "http=") != NULL) {
            httpProxy = token + strlen("http=");
        } else if(strstr(token, "https=") != NULL) {
            httpsProxy = token + strlen("https=");
        }
        token = strtok_s(NULL, ";", &context);
    }
} else {
    // 代理配置为单一 IP + 端口形式,直接设置为 HTTP 和 HTTPS 代理
    httpProxy = proxyConfig;
    httpsProxy = proxyConfig;
}

这段代码会检查代理配置是否包含了 "http=" 或 "https=" 的信息,若包含则进行分割和解析,将对应的代理地址分配给 httpProxyhttpsProxy 变量;如果代理配置只是一个单一的 IP + 端口形式,则将其同时设置为 HTTP 和 HTTPS 代理。

Charles 代理设置

Charles 代理设置