美文网首页
通过C++调用Windows api学习post请求的发送

通过C++调用Windows api学习post请求的发送

作者: AI_Finance | 来源:发表于2025-03-07 09:10 被阅读0次

下面的代码使用windows api的unicode函数实现了一个http post请求;本文为希望学习windows网络编程的人深度解读下面的代码

#include <windows.h>
#include <string>
#include <sstream>
#include <iostream>
#include <wininet.h>

// 自定义 UTF-8 转 UTF-16
std::wstring ConvertToWideString(const char* utf8String) {
    if (!utf8String) return L"";
    int wideLength = MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, NULL, 0);
    std::wstring wideString(wideLength, 0);
    MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, &wideString[0], wideLength);
    return wideString;
}

// 自定义 UTF-16 转 UTF-8
std::string ConvertToUTF8(const wchar_t* wideString) {
    if (!wideString) return "";
    int utf8Length = WideCharToMultiByte(CP_UTF8, 0, wideString, -1, NULL, 0, NULL, NULL);
    std::string utf8String(utf8Length, 0);
    WideCharToMultiByte(CP_UTF8, 0, wideString, -1, &utf8String[0], utf8Length, NULL, NULL);
    return utf8String;
}

// 将整数转换为宽字符串
std::wstring ToWString(size_t value) {
    std::wstringstream wss;
    wss << value;
    return wss.str();
}

// 使用 Windows API 写入日志
void WriteLog(const std::string& message) {
    const wchar_t* logFileName = L"chriszhao_Debug.log";

    HANDLE hFile = CreateFileW(
        logFileName,
        FILE_APPEND_DATA,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open log file: " << ConvertToUTF8(logFileName) << std::endl;
        return;
    }

    DWORD bytesWritten = 0;
    WriteFile(hFile, message.c_str(), message.size(), &bytesWritten, NULL);
    WriteFile(hFile, "\\n", 1, &bytesWritten, NULL);
    CloseHandle(hFile);
}

// 导出函数声明
extern "C" __declspec(dllexport) int HttpPost(const char* url, const char* headers, const char* body, char* response, int responseSize);

// HTTP POST 函数实现
extern "C" __declspec(dllexport) int HttpPost(const char* url, const char* headers, const char* body, char* response, int responseSize) {
    WriteLog("HttpPost called with parameters:");
    WriteLog("URL: " + std::string(url));
    WriteLog("Headers: " + std::string(headers));
    WriteLog("Body: " + std::string(body));

    if (!url || strlen(url) == 0) {
        WriteLog("Error: URL is empty or null!");
        return -1;
    }

    // 转换 URL 和 Headers 为宽字符
    std::wstring wideUrl = ConvertToWideString(url);
    std::wstring wideHeaders = ConvertToWideString(headers);
    std::string utf8Body = body; // Body 应该是 UTF-8 格式,不需要转换

    if (wideUrl.find(L"http://") != 0 && wideUrl.find(L"https://") != 0) {
        WriteLog("Error: URL must start with http:// or https://");
        return -1;
    }

    // 打开 Internet 会话
    HINTERNET hInternet = InternetOpenW(L"MT4_HTTP_Client", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
    if (!hInternet) {
        WriteLog("Error: InternetOpenW failed! Error code: " + std::to_string(GetLastError()));
        return -1;
    }

    // 解析 URL
    URL_COMPONENTSW urlComponents = {0};
    wchar_t scheme[16] = {0};
    wchar_t hostName[256] = {0};
    wchar_t urlPath[1024] = {0};
    wchar_t extraInfo[256] = {0};

    urlComponents.dwStructSize = sizeof(urlComponents);
    urlComponents.lpszScheme = scheme;
    urlComponents.dwSchemeLength = sizeof(scheme) / sizeof(wchar_t);
    urlComponents.lpszHostName = hostName;
    urlComponents.dwHostNameLength = sizeof(hostName) / sizeof(wchar_t);
    urlComponents.lpszUrlPath = urlPath;
    urlComponents.dwUrlPathLength = sizeof(urlPath) / sizeof(wchar_t);
    urlComponents.lpszExtraInfo = extraInfo;
    urlComponents.dwExtraInfoLength = sizeof(extraInfo) / sizeof(wchar_t);

    if (!InternetCrackUrlW(wideUrl.c_str(), 0, 0, &urlComponents)) {
        WriteLog("Error: InternetCrackUrlW failed! URL: " + ConvertToUTF8(wideUrl.c_str()) + ", Error code: " + std::to_string(GetLastError()));
        InternetCloseHandle(hInternet);
        return -2;
    }

    WriteLog("InternetCrackUrlW succeeded!");
    WriteLog("Scheme: " + ConvertToUTF8(urlComponents.lpszScheme ? urlComponents.lpszScheme : L"NULL"));
    WriteLog("HostName: " + ConvertToUTF8(urlComponents.lpszHostName ? urlComponents.lpszHostName : L"NULL"));
    WriteLog("UrlPath: " + ConvertToUTF8(urlComponents.lpszUrlPath ? urlComponents.lpszUrlPath : L"NULL"));
    WriteLog("Port: " + std::to_string(urlComponents.nPort));

    // 创建连接
    HINTERNET hConnect = InternetConnectW(hInternet, urlComponents.lpszHostName, urlComponents.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
    if (!hConnect) {
        WriteLog("Error: InternetConnectW failed! Error code: " + std::to_string(GetLastError()));
        InternetCloseHandle(hInternet);
        return -3;
    }

    // 打开 POST 请求
    HINTERNET hRequest = HttpOpenRequestW(hConnect, L"POST", urlComponents.lpszUrlPath, NULL, NULL, NULL, INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0);
    if (!hRequest) {
        WriteLog("Error: HttpOpenRequestW failed! Error code: " + std::to_string(GetLastError()));
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return -4;
    }

    // 构造请求头
    std::wstring fixedHeaders = L"Content-Type: application/json\\r\\n";
    fixedHeaders += L"Host: 127.0.0.1\\r\\n";
    fixedHeaders += L"Accept: application/json\\r\\n";
    fixedHeaders += L"User-Agent: MT4_HTTP_Client\\r\\n";
    fixedHeaders += L"Content-Length: " + ToWString(utf8Body.size()) + L"\\r\\n\\r\\n"; // 额外的空行表示请求头结束

    WriteLog("Final Headers (Wide): " + ConvertToUTF8(fixedHeaders.c_str()));
    WriteLog("Request Body: " + utf8Body);

    // 发送请求
    BOOL result = HttpSendRequestW(hRequest, fixedHeaders.c_str(), fixedHeaders.size(), (LPVOID)utf8Body.c_str(), utf8Body.size());
    if (!result) {
        DWORD errorCode = GetLastError();
        WriteLog("Error: HttpSendRequestW failed! Error code: " + std::to_string(errorCode));

        InternetCloseHandle(hRequest);
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return -5;
    }

    // 读取响应
    char buffer[8192] = {0};
    DWORD bytesRead = 0;
    result = InternetReadFile(hRequest, buffer, sizeof(buffer) - 1, &bytesRead);
    if (!result) {
        WriteLog("Error: InternetReadFile failed! Error code: " + std::to_string(GetLastError()));
        InternetCloseHandle(hRequest);
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return -6;
    }

    buffer[bytesRead] = '\\0';

    // 将响应写入输出缓冲区
    strncpy(response, buffer, responseSize - 1);
    response[responseSize - 1] = '\\0';

    WriteLog("Response: " + std::string(buffer));

    InternetCloseHandle(hRequest);
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hInternet);

    return 0;
}

这个代码实现了一个使用 Windows API 的 HTTP POST 请求函数,并包含了日志记录和字符串编码转换功能。以下是对代码中所有知识点的详细解读:


1. Windows API 基础知识

Windows API 是微软提供的一组函数,用于开发 Windows 应用程序。本代码中使用了以下 Windows API:

  • CreateFileW:用于创建或打开文件。
  • WriteFile:用于向文件写入数据。
  • InternetOpenW:用于初始化一个 Internet 会话。
  • InternetCrackUrlW:用于解析 URL。
  • InternetConnectW:用于连接到指定的服务器。
  • HttpOpenRequestW:用于创建 HTTP 请求句柄。
  • HttpSendRequestW:用于发送 HTTP 请求。
  • InternetReadFile:用于读取 HTTP 响应数据。
  • CloseHandleInternetCloseHandle:用于关闭文件或 Internet 句柄。

这些函数的名称以 W 结尾,表示它们使用宽字符(UTF-16编码)。


2. 字符编码转换

本代码实现了从 UTF-8 到 UTF-16 和从 UTF-16 到 UTF-8 的转换功能。Windows API 中通常使用宽字符(UTF-16),而 HTTP 请求中的数据通常是 UTF-8,因此需要进行编码转换。

UTF-8 转 UTF-16

std::wstring ConvertToWideString(const char* utf8String) {
    int wideLength = MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, NULL, 0);
    std::wstring wideString(wideLength, 0);
    MultiByteToWideChar(CP_UTF8, 0, utf8String, -1, &wideString[0], wideLength);
    return wideString;
}
  • MultiByteToWideChar:将多字节字符串(如 UTF-8)转换为宽字符字符串(UTF-16)。
  • 参数:
    • CP_UTF8:指定源字符串的编码为 UTF-8。
    • utf8String:源字符串。
    • wideLength:宽字符字符串的长度。
  • 返回值:宽字符字符串。

UTF-16 转 UTF-8

std::string ConvertToUTF8(const wchar_t* wideString) {
    int utf8Length = WideCharToMultiByte(CP_UTF8, 0, wideString, -1, NULL, 0, NULL, NULL);
    std::string utf8String(utf8Length, 0);
    WideCharToMultiByte(CP_UTF8, 0, wideString, -1, &utf8String[0], utf8Length, NULL, NULL);
    return utf8String;
}
  • WideCharToMultiByte:将宽字符字符串(UTF-16)转换为多字节字符串(UTF-8)。
  • 参数:
    • CP_UTF8:指定目标字符串的编码为 UTF-8。
    • wideString:宽字符字符串。
    • utf8Length:多字节字符串的长度。
  • 返回值:UTF-8 字符串。

3. 文件操作

日志写入

void WriteLog(const std::string& message) {
    const wchar_t* logFileName = L"chriszhao_Debug.log";

    HANDLE hFile = CreateFileW(
        logFileName,
        FILE_APPEND_DATA,
        FILE_SHARE_READ,
        NULL,
        OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open log file: " << ConvertToUTF8(logFileName) << std::endl;
        return;
    }

    DWORD bytesWritten = 0;
    WriteFile(hFile, message.c_str(), message.size(), &bytesWritten, NULL);
    WriteFile(hFile, "\\n", 1, &bytesWritten, NULL);
    CloseHandle(hFile);
}
  • CreateFileW:打开或创建一个文件。
    • 参数:
      • FILE_APPEND_DATA:允许追加数据。
      • OPEN_ALWAYS:如果文件不存在则创建文件。
  • WriteFile:向文件写入数据。
  • CloseHandle:关闭文件句柄,释放资源。

日志记录功能将调试信息写入到 chriszhao_Debug.log 文件,方便调试和问题排查。


4. HTTP 请求处理

该代码实现了一个 HTTP POST 请求,涉及以下步骤:

4.1 初始化 Internet 会话

HINTERNET hInternet = InternetOpenW(L"MT4_HTTP_Client", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
  • InternetOpenW:初始化 Internet 会话,返回一个句柄。
  • 参数:
    • L"MT4_HTTP_Client":用户代理字符串。
    • INTERNET_OPEN_TYPE_DIRECT:直接连接到 Internet。

4.2 解析 URL

URL_COMPONENTSW urlComponents = {0};
if (!InternetCrackUrlW(wideUrl.c_str(), 0, 0, &urlComponents)) {
    WriteLog("Error: InternetCrackUrlW failed! Error code: " + std::to_string(GetLastError()));
    InternetCloseHandle(hInternet);
    return -2;
}
  • InternetCrackUrlW:解析 URL,提取协议、主机名、路径等信息。
  • 参数:
    • wideUrl:要解析的 URL。
    • urlComponents:存储解析结果的结构体。

4.3 创建连接

HINTERNET hConnect = InternetConnectW(hInternet, urlComponents.lpszHostName, urlComponents.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
  • InternetConnectW:连接到指定的服务器。
  • 参数:
    • urlComponents.lpszHostName:服务器主机名。
    • urlComponents.nPort:端口号。
    • INTERNET_SERVICE_HTTP:服务类型为 HTTP。

4.4 创建 HTTP 请求

HINTERNET hRequest = HttpOpenRequestW(hConnect, L"POST", urlComponents.lpszUrlPath, NULL, NULL, NULL, INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0);
  • HttpOpenRequestW:创建 HTTP 请求句柄。
  • 参数:
    • L"POST":请求方法。
    • urlComponents.lpszUrlPath:请求路径。

4.5 发送 HTTP 请求

BOOL result = HttpSendRequestW(hRequest, fixedHeaders.c_str(), fixedHeaders.size(), (LPVOID)utf8Body.c_str(), utf8Body.size());
  • HttpSendRequestW:发送 HTTP 请求。
  • 参数:
    • fixedHeaders:请求头。
    • utf8Body:请求体。

4.6 读取响应

result = InternetReadFile(hRequest, buffer, sizeof(buffer) - 1, &bytesRead);
  • InternetReadFile:读取 HTTP 响应数据。
  • 参数:
    • buffer:存储响应数据的缓冲区。
    • bytesRead:实际读取的字节数。

5. 错误处理

代码中大量使用了 GetLastError 来获取错误码,并记录到日志中。例如:

WriteLog("Error: InternetOpenW failed! Error code: " + std::to_string(GetLastError()));
  • GetLastError:获取最近一次系统调用的错误码。

6. 导出函数

代码使用了 __declspec(dllexport) 关键字导出函数,使得它可以被其他程序调用:

extern "C" __declspec(dllexport) int HttpPost(const char* url, const char* headers, const char* body, char* response, int responseSize);
  • extern "C":避免 C++ 名字修饰,使得函数可以用 C 的方式调用。

总结

这段代码展示了如何使用 Windows API 实现文件操作、字符串编码转换、HTTP 请求处理等功能。主要知识点包括:

  1. Windows API 的使用。
  2. 字符编码转换(UTF-8 和 UTF-16)。
  3. 文件操作和日志记录。
  4. HTTP 请求的创建、发送和响应处理。
  5. 错误处理和调试。
  6. 导出函数供外部使用。

它是一个完整的 HTTP 客户端实现,适合学习 Windows API 和网络编程的开发者。

相关文章

网友评论

      本文标题:通过C++调用Windows api学习post请求的发送

      本文链接:https://www.haomeiwen.com/subject/fdirmjtx.html