Windows WinINet API 的 `HttpSendRequest` 行為極具反直覺性。本文記錄一次真實除錯過程:從 `HttpOpenRequest` 返回 `ERROR_INSUFFICIENT_BUFFER (122)` 起,經過系統性實驗,揭示 `HttpSendRequest` 第二參數 `lpszHeaders` 的嚴格格式要求——請求行不可含 `\r\n`,首部行可選擇性包含,結尾雙 `\r\n\r\n` 非強制,且 `Referer` 首部為成功關鍵。
HINTERNET hOpenRequest = HttpOpenRequest(hConnect, _T("GET"),
_T("img/baidu_sylogo1.gif"), _T("HTTP/1.1"),
_T("https://www.baidu.com/"), NULL, dwFlags, 0);
DWORD err = GetLastError(); // 122 = ERROR_INSUFFICIENT_BUFFER
// 但句柄有效,後續 HttpSendRequest 竟成功返回 200 OK
疑點:`HttpOpenRequest` 明明成功 (句柄非 NULL),為何 `GetLastError()` 返回 122?MSDN 文檔未明確說明此行為。
當 `HttpOpenRequest` 參數傳 `NULL` 時,必須在 `HttpSendRequest` 的 `lpszHeaders` 手動提供完整原始 HTTP 請求:
LPCTSTR lpHeader =
_T("GET /img/baidu_sylogo1.gif HTTP/1.1") // A行: 請求行,**不可** 含 \r\n
_T("Referer: https://www.baidu.com/\r\n") // B行: Referer,**必須** 存在且以 \r\n 結尾
_T("Host: www.baidu.com") // C行: Host,可選,無 \r\n 也可
_T("\r\n\r\n"); // D行: 雙 CRLF 結尾,單 \r\n 也可
HttpSendRequest(hRequest, lpHeader, _tcslen(lpHeader), NULL, 0); // 成功!
| 測試案例 | A行結尾 | B/C行結尾 | D行結尾 | 結果 | 錯誤碼 |
|---|---|---|---|---|---|
| Test 1 | \r\n | 有 | \r\n\r\n | 失敗 | 12150 (HEADER_NOT_FOUND) |
| Test 2 | 無 | 無 | \r\n\r\n | 成功 | 0 |
| Test 3 | 無 | 有/無 | \r\n (單) | 成功 | 0 |
| Test 4 | 無 | 有/無 | 無 | 成功 | 0 |
\r\n`,否則 `HttpSendRequest` 解析失敗 (12150)。\r\n` 結尾。\r\n` 均可接受。避免手動拼接字串,使用專用 API 添加首部:
HINTERNET hReq = HttpOpenRequest(hConnect, NULL, NULL, NULL, NULL, NULL, dwFlags, 0);
LPCTSTR hdrs = _T("GET /img/baidu_sylogo1.gif HTTP/1.1") _T("Referer: https://www.baidu.com/");
HttpAddRequestHeaders(hReq, hdrs, _tcslen(hdrs),
HTTP_ADDREQ_FLAG_REPLACE | HTTP_ADDREQ_FLAG_ADD);
HttpSendRequest(hReq, NULL, 0, NULL, 0); // 此時第二參數為 NULL
此方式語義清晰,符合 API 設計意圖,強烈推薦生產環境使用。
手動構建的 `lpHeader` 與 `HttpOpenRequest` 參數一一對應:
GET /path HTTP/1.1` → `lpszVerb`、`lpszObjectName`、`lpszVersion`Referer: ...` → `lpszReferer`Host: ...` → 自動由 WinINet 根據連接目標生成,手動指定可覆蓋\r\n`、必帶 `Referer`、結尾至少一 `\r\n`**。WinINet 的 HTTP 協議封裝存在「半自動」特性:高層 API 便利但不透明,底層手動又極其挑剔。理解其邊界行為,是開發穩健 Windows 網絡應用的必修課。