C++ 使用winhttp 上传pdf 文档 和一个字符串到 https 服务器
时间: 2025-07-27 12:25:45 浏览: 10
<think>我们使用WinHTTP库在C++中上传PDF文件和字符串到HTTPS服务器。我们将使用多部分表单数据(multipart/form-data)格式来同时发送文件和文本数据。步骤:1.初始化WinHTTP会话。2.连接到目标服务器。3.创建HTTPS请求(使用WINHTTP_FLAG_SECURE)。4.构建多部分表单数据体,包含文件部分和字符串部分。5.设置请求头,包括Content-Type为multipart/form-data并指定边界。6.发送请求并写入表单数据。7.接收响应。注意:由于PDF文件可能较大,我们采用流式方式读取文件并发送,避免一次性加载到内存。下面是一个完整的示例代码:</think># C++ 使用 WinHTTP 上传 PDF 文档和字符串到 HTTPS 服务器
下面我将提供一个完整的 C++ 解决方案,使用 WinHTTP API 将 PDF 文件和字符串 "aaaaa" 作为多部分表单数据上传到 HTTPS 服务器。
## 完整实现代码
```cpp
#include <windows.h>
#include <winhttp.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cctype>
#include <locale>
#include <codecvt>
#pragma comment(lib, "winhttp.lib")
// 将宽字符串转换为UTF-8
std::string WideToUTF8(const std::wstring& wstr) {
if (wstr.empty()) return "";
int size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
std::string result(size, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.size(), &result[0], size, nullptr, nullptr);
return result;
}
// 上传PDF和文本到HTTPS服务器
bool UploadPDFAndText(
const std::wstring& server,
const std::wstring& path,
const std::wstring& pdfPath,
const std::string& textData)
{
HINTERNET hSession = nullptr, hConnect = nullptr, hRequest = nullptr;
BOOL bResults = FALSE;
DWORD dwBytesWritten = 0;
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer = nullptr;
try {
// 1. 初始化WinHTTP会话
hSession = WinHttpOpen(
L"WinHTTP Upload Example/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
if (!hSession) {
throw std::runtime_error("WinHttpOpen failed");
}
// 2. 连接到服务器
hConnect = WinHttpConnect(
hSession,
server.c_str(),
INTERNET_DEFAULT_HTTPS_PORT,
0);
if (!hConnect) {
throw std::runtime_error("WinHttpConnect failed");
}
// 3. 创建HTTPS请求
hRequest = WinHttpOpenRequest(
hConnect,
L"POST",
path.c_str(),
nullptr,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE);
if (!hRequest) {
throw std::runtime_error("WinHttpOpenRequest failed");
}
// 4. 生成唯一边界
std::string boundary = "----WinHttpBoundary" + std::to_string(GetTickCount());
// 5. 准备文件信息
// 获取文件名
std::wstring filename = pdfPath.substr(pdfPath.find_last_of(L"\\/") + 1);
std::string utf8Filename = WideToUTF8(filename);
// 获取文件大小
std::ifstream file(pdfPath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
throw std::runtime_error("Failed to open PDF file");
}
DWORD fileSize = static_cast<DWORD>(file.tellg());
file.close();
// 6. 构建多部分表单数据
std::vector<std::string> parts;
// 文本部分
parts.push_back("--" + boundary + "\r\n");
parts.push_back("Content-Disposition: form-data; name=\"text_field\"\r\n\r\n");
parts.push_back(textData + "\r\n");
// 文件部分头
parts.push_back("--" + boundary + "\r\n");
parts.push_back("Content-Disposition: form-data; name=\"file_field\"; filename=\"" + utf8Filename + "\"\r\n");
parts.push_back("Content-Type: application/pdf\r\n\r\n");
// 计算总大小
DWORD totalSize = 0;
for (const auto& part : parts) {
totalSize += static_cast<DWORD>(part.size());
}
totalSize += fileSize; // 文件内容
totalSize += static_cast<DWORD>(("--" + boundary + "--\r\n").size()); // 结束边界
// 7. 设置请求头
std::string contentType = "Content-Type: multipart/form-data; boundary=" + boundary;
std::wstring wContentType = std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(contentType);
if (!WinHttpAddRequestHeaders(
hRequest,
wContentType.c_str(),
-1,
WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE))
{
throw std::runtime_error("WinHttpAddRequestHeaders failed");
}
// 8. 发送请求
if (!WinHttpSendRequest(
hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0,
WINHTTP_NO_REQUEST_DATA,
0,
totalSize,
0))
{
throw std::runtime_error("WinHttpSendRequest failed");
}
// 9. 发送表单文本部分
for (const auto& part : parts) {
if (!WinHttpWriteData(
hRequest,
part.c_str(),
static_cast<DWORD>(part.size()),
&dwBytesWritten))
{
throw std::runtime_error("WinHttpWriteData (text) failed");
}
}
// 10. 发送文件内容
std::ifstream pdfFile(pdfPath, std::ios::binary);
if (!pdfFile.is_open()) {
throw std::runtime_error("Failed to open PDF file for reading");
}
const DWORD bufferSize = 4096;
std::vector<char> buffer(bufferSize);
while (!pdfFile.eof()) {
pdfFile.read(buffer.data(), bufferSize);
DWORD bytesRead = static_cast<DWORD>(pdfFile.gcount());
if (bytesRead > 0) {
if (!WinHttpWriteData(
hRequest,
buffer.data(),
bytesRead,
&dwBytesWritten))
{
throw std::runtime_error("WinHttpWriteData (file) failed");
}
}
}
pdfFile.close();
// 11. 发送结束边界
std::string endBoundary = "--" + boundary + "--\r\n";
if (!WinHttpWriteData(
hRequest,
endBoundary.c_str(),
static_cast<DWORD>(endBoundary.size()),
&dwBytesWritten))
{
throw std::runtime_error("WinHttpWriteData (end boundary) failed");
}
// 12. 接收响应
if (!WinHttpReceiveResponse(hRequest, nullptr)) {
throw std::runtime_error("WinHttpReceiveResponse failed");
}
// 13. 读取响应
std::string response;
do {
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
throw std::runtime_error("WinHttpQueryDataAvailable failed");
}
if (dwSize == 0) break;
std::vector<char> responseBuffer(dwSize + 1);
if (!WinHttpReadData(
hRequest,
responseBuffer.data(),
dwSize,
&dwDownloaded))
{
throw std::runtime_error("WinHttpReadData failed");
}
if (dwDownloaded > 0) {
responseBuffer[dwDownloaded] = '\0';
response += responseBuffer.data();
}
} while (dwSize > 0);
std::cout << "服务器响应:\n" << response << std::endl;
bResults = TRUE;
}
catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << " (错误码: " << GetLastError() << ")" << std::endl;
bResults = FALSE;
}
// 清理资源
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
if (pszOutBuffer) free(pszOutBuffer);
return bResults != FALSE;
}
int main() {
// 配置服务器信息
std::wstring server = L"example.com"; // 替换为实际的服务器域名
std::wstring path = L"/upload"; // 替换为实际上传路径
// PDF文件路径
std::wstring pdfPath = L"C:\\path\\to\\your\\file.pdf"; // 替换为实际的PDF文件路径
// 文本数据
std::string textData = "aaaaa";
// 执行上传
if (UploadPDFAndText(server, path, pdfPath, textData)) {
std::cout << "上传成功!" << std::endl;
} else {
std::cout << "上传失败!" << std::endl;
}
return 0;
}
```
## 代码详细解释
### 1. 多部分表单结构
上传表单包含两个部分:
- 文本字段:名为 "text_field",值为 "aaaaa"
- 文件字段:名为 "file_field",包含 PDF 文件
表单结构如下:
```
--boundary
Content-Disposition: form-data; name="text_field"
aaaaa
--boundary
Content-Disposition: form-data; name="file_field"; filename="example.pdf"
Content-Type: application/pdf
[PDF文件内容]
--boundary--
```
### 2. WinHTTP 使用流程
1. **初始化会话** (`WinHttpOpen`)
2. **连接服务器** (`WinHttpConnect`)
3. **创建请求** (`WinHttpOpenRequest`)
4. **设置请求头** (`WinHttpAddRequestHeaders`)
5. **发送请求** (`WinHttpSendRequest`)
6. **写入表单数据** (`WinHttpWriteData`)
7. **接收响应** (`WinHttpReceiveResponse`)
8. **读取响应数据** (`WinHttpReadData`)
9. **清理资源** (`WinHttpCloseHandle`)
### 3. 关键实现细节
**边界生成**:
```cpp
std::string boundary = "----WinHttpBoundary" + std::to_string(GetTickCount());
```
使用时间戳生成唯一边界字符串,避免冲突。
**文件名处理**:
```cpp
// 获取文件名
std::wstring filename = pdfPath.substr(pdfPath.find_last_of(L"\\/") + 1);
std::string utf8Filename = WideToUTF8(filename);
```
正确处理文件名路径并转换为UTF-8编码,确保非ASCII字符正确传输。
**文件流式读取**:
```cpp
std::ifstream pdfFile(pdfPath, std::ios::binary);
const DWORD bufferSize = 4096;
std::vector<char> buffer(bufferSize);
while (!pdfFile.eof()) {
pdfFile.read(buffer.data(), bufferSize);
DWORD bytesRead = static_cast<DWORD>(pdfFile.gcount());
// 写入数据...
}
```
使用4KB缓冲区分块读取和发送文件内容,避免大文件占用过多内存。
**错误处理**:
```cpp
try {
// WinHTTP操作...
}
catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << " (错误码: " << GetLastError() << ")" << std::endl;
}
```
使用异常处理机制捕获所有可能的错误,并提供详细的错误信息。
### 4. Java 服务器端处理示例
Java 服务器可以使用 Spring Boot 处理上传请求:
```java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class UploadController {
@PostMapping("/upload")
public String handleFileUpload(
@RequestParam("text_field") String textData,
@RequestParam("file_field") MultipartFile file) {
System.out.println("Received text: " + textData);
System.out.println("Received file: " + file.getOriginalFilename());
try {
// 保存文件
file.transferTo(new File("/path/to/save/" + file.getOriginalFilename()));
return "Upload successful! Text: " + textData;
} catch (IOException e) {
return "Upload failed: " + e.getMessage();
}
}
}
```
## 高级功能扩展
### 1. 进度监控
添加上传进度监控功能:
```cpp
class UploadProgress {
public:
UploadProgress(DWORD totalSize) : totalSize(totalSize), uploaded(0) {}
void Update(DWORD bytesUploaded) {
uploaded += bytesUploaded;
float percent = (static_cast<float>(uploaded) / totalSize) * 100.0f;
std::cout << "上传进度: " << std::fixed << std::setprecision(1)
<< percent << "% (" << uploaded << "/" << totalSize << " 字节)\r";
}
private:
DWORD totalSize;
DWORD uploaded;
};
// 在写入数据时更新进度
UploadProgress progress(totalSize);
// ...
if (!WinHttpWriteData(hRequest, part.c_str(), part.size(), &dwBytesWritten)) {
// 错误处理
}
progress.Update(dwBytesWritten);
```
### 2. 超时设置
添加网络操作超时控制:
```cpp
// 设置超时参数
DWORD timeout = 30000; // 30秒
WinHttpSetOption(hSession, WINHTTP_OPTION_CONNECT_TIMEOUT, &timeout, sizeof(timeout));
WinHttpSetOption(hSession, WINHTTP_OPTION_RECEIVE_TIMEOUT, &timeout, sizeof(timeout));
WinHttpSetOption(hSession, WINHTTP_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
```
### 3. HTTPS 证书验证
处理自签名证书或自定义证书验证:
```cpp
// 证书验证回调
BOOL CALLBACK CertVerifyCallback(
LPVOID pvArg,
DWORD dwCertError,
DWORD dwFlags,
LPSTR pszChain,
DWORD dwChainLen) {
// 接受所有证书(仅用于测试)
if (dwCertError == CERT_E_UNTRUSTEDROOT) {
return TRUE; // 接受不受信任的根证书
}
return dwCertError == ERROR_SUCCESS;
}
// 设置证书验证选项
DWORD securityFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS,
&securityFlags, sizeof(securityFlags));
WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_CALLBACK,
&CertVerifyCallback, sizeof(CertVerifyCallback));
```
### 4. 多文件上传
扩展支持多个文件上传:
```cpp
struct FileUpload {
std::wstring path;
std::string fieldName;
std::string contentType;
};
bool UploadMultipleFiles(
const std::wstring& server,
const std::wstring& path,
const std::string& textData,
const std::vector<FileUpload>& files)
{
// 实现多个文件上传逻辑
// ...
}
```
## 编译与运行
### 编译命令
使用 Visual Studio 编译:
```bash
cl /EHsc /std:c++17 upload.cpp /link winhttp.lib
```
### 运行要求
1. Windows 7 或更高版本
2. 有效的 PDF 文件路径
3. 可访问的 HTTPS 服务器
4. 服务器防火墙允许入站 HTTPS 连接
## 常见问题解决
### 1. 证书验证失败
如果遇到证书错误,可以添加以下代码忽略证书错误(仅限测试环境):
```cpp
DWORD options = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS,
&options, sizeof(options));
```
### 2. 大文件上传
对于大文件上传,建议增加超时时间:
```cpp
DWORD timeout = 5 * 60 * 1000; // 5分钟
WinHttpSetOption(hSession, WINHTTP_OPTION_SEND_TIMEOUT, &timeout, sizeof(timeout));
```
### 3. 内存优化
对于非常大的文件,使用更大的缓冲区减少系统调用:
```cpp
const DWORD bufferSize = 64 * 1024; // 64KB
```
### 4. 代理支持
如果需要通过代理连接:
```cpp
hSession = WinHttpOpen(
L"WinHTTP Upload Example/1.0",
WINHTTP_ACCESS_TYPE_NAMED_PROXY,
L"proxy_server:port",
L"<local>",
0);
```
## 安全注意事项
1. **证书验证**:生产环境中应验证服务器证书
2. **输入验证**:验证文件路径和用户输入
3. **错误处理**:全面处理所有可能的错误情况
4. **超时设置**:添加合理的超时限制
5. **敏感数据**:避免在代码中硬编码敏感信息
阅读全文
相关推荐












