首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

WinINet HttpSendRequest 與 HTTP 頭部構建深度探秘:從錯誤 122 到手動構建請求行

     編輯:關於C語言

Windows WinINet API 的 `HttpSendRequest` 行為極具反直覺性。本文記錄一次真實除錯過程:從 `HttpOpenRequest` 返回 `ERROR_INSUFFICIENT_BUFFER (122)` 起,經過系統性實驗,揭示 `HttpSendRequest` 第二參數 `lpszHeaders` 的嚴格格式要求——請求行不可含 `\r\n`,首部行可選擇性包含,結尾雙 `\r\n\r\n` 非強制,且 `Referer` 首部為成功關鍵。

一、現象重現:神秘的錯誤 122


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 文檔未明確說明此行為。

二、關鍵發現:`HttpSendRequest` 第二參數可手動構建完整 HTTP 請求頭

當 `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

鐵律總結

  1. 請求行 (A行) 絕對不能包含 `\r\n`,否則 `HttpSendRequest` 解析失敗 (12150)。
  2. Referer 首部 (B行) 必須存在,且建議以 `\r\n` 結尾。
  3. Host 首部 (C行) 與結尾 (D行) 格式寬鬆,單/雙 `\r\n` 均可接受。

四、替代方案:`HttpAddRequestHeaders` 正規軍

避免手動拼接字串,使用專用 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` 參數一一對應:

  • A行 `GET /path HTTP/1.1` → `lpszVerb`、`lpszObjectName`、`lpszVersion`
  • B行 `Referer: ...` → `lpszReferer`
  • C行 `Host: ...` → 自動由 WinINet 根據連接目標生成,手動指定可覆蓋

六、避坑指南與最佳實踐

  1. 優先使用 `HttpOpenRequest` 完整參數 + `HttpSendRequest(NULL, 0, NULL, 0)`,讓 WinINet 自動構建標準請求。
  2. 需自定義首部時,用 `HttpAddRequestHeaders`,勿手動拼接 `lpszHeaders`。
  3. 若必須手動構建 (如代理、特殊協議):**請求行無 `\r\n`、必帶 `Referer`、結尾至少一 `\r\n`**。
  4. `HttpOpenRequest` 返回 122 且句柄有效時,**可忽略**,不影響後續操作 (經驗值,非文檔保證)。
  5. 調試利器:Wireshark/Fiddler 抓包對比 WinINet 發送的原始報文。

WinINet 的 HTTP 協議封裝存在「半自動」特性:高層 API 便利但不透明,底層手動又極其挑剔。理解其邊界行為,是開發穩健 Windows 網絡應用的必修課。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved