1. 程式人生 > >通過HTTP協議上傳檔案

通過HTTP協議上傳檔案

     HTTP是很常見的協議,雖然用得很多,但對細節的瞭解卻是很淺,這回通過向服務端上傳檔案資訊來理解細節。網路庫的選擇:1、WinHTTP是windows下常用的庫;2、CURL是廣受喜愛的開源庫。對於我來說,libcurl最大的優點是使用方便,可以把注意力更多的集中到業務層上,提高工作效率,避免重造輪子;缺點是略大(MD編譯有264KB,MT編譯有340KB),不像WinHTTP可以由windows作業系統整合。下邊展示如何使用這兩種網路庫實現表單POST檔案。

一、libcurl

     庫編譯:把include、lib的程式碼拿出來,使用靜態庫,設定預定義巨集BUILDING_LIBCURL。      測試工程:設定預定義巨集CURL_STATICLIB;新增依賴lib:Ws2_32.lib Wldap32.lib。      主要程式碼 複製程式碼
void curl_post_cswuyg()
{
    curl_global_init(CURL_GLOBAL_ALL);
    CURL* hCurl = curl_easy_init();
    if(hCurl != NULL)
    {
        //也許有Expect: 100-continue,去掉它
curl_slist* pOptionList = NULL; pOptionList = curl_slist_append(pOptionList, "Expect:"); curl_easy_setopt(hCurl, CURLOPT_HTTPHEADER, pOptionList); curl_httppost* pFormPost = NULL; curl_httppost* pLastElem = NULL; //上傳檔案,指定本地檔案完整路徑 curl_formadd(&pFormPost, &pLastElem, CURLFORM_COPYNAME, "
ufile01", CURLFORM_FILE, "C:\\temp\\upload_test_curl_cswuyg.txt", CURLFORM_CONTENTTYPE, "application/octet-stream", CURLFORM_END); //上傳自定義檔案內容的檔案,CURLFORM_BUFFER指定服務端檔名 //http://curl.haxx.se/libcurl/c/curl_formadd.html char* file_info = "file_info——cswuyg"; int record_length = std::string
(file_info).length(); curl_formadd(&pFormPost, &pLastElem, CURLFORM_COPYNAME, "ufile01", CURLFORM_BUFFER, "cswuyg_test.txt", CURLFORM_BUFFERPTR, file_info, CURLFORM_BUFFERLENGTH, record_length, CURLFORM_END); //不加一個結束的hfs服務端無法寫入檔案,一般不存在這種問題,這裡加入只是為了測試. //curl_formadd(&pFormPost, &pLastElem, CURLFORM_COPYNAME, "end", CURLFORM_COPYCONTENTS, "end", CURLFORM_END); curl_easy_setopt(hCurl, CURLOPT_HTTPPOST, pFormPost); curl_easy_setopt(hCurl, CURLOPT_URL, "http://127.0.0.1/hfs_up/"); CURLcode res = curl_easy_perform(hCurl); if(res != CURLE_OK) { std::wcout << "Error" << std::endl; } curl_formfree(pFormPost); curl_easy_cleanup(hCurl); } curl_global_cleanup(); }
複製程式碼      wireshark抓包,127.0.0.1地址無法抓到,網路上有解決方案。示意圖: 頭域設定分割符:  MIME部分:  

二、WinHTTP

     庫:winhttp.lib      主要程式碼複製程式碼
void cswuyg_winhttp_post()
{
    HINTERNET hSession = ::WinHttpOpen(L"cswuyg post test/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, NULL);
    HINTERNET hConnect = ::WinHttpConnect(hSession, L"127.0.0.1", INTERNET_DEFAULT_HTTP_PORT, 0);
    if (hConnect == NULL)
    {
        int i = ERROR_WINHTTP_INCORRECT_HANDLE_TYPE;
        return;
    }
    const wchar_t* lpszAcceptedType[] = {L"*/*", NULL};
    HINTERNET hRequest = ::WinHttpOpenRequest(hConnect, L"POST", L"hfs_up", L"HTTP/1.1", WINHTTP_NO_REFERER, lpszAcceptedType, 0);
    if (hRequest == NULL)
    {
        return;
    }
    DWORD dwTime = 5000;
    ::WinHttpSetOption(hRequest, WINHTTP_OPTION_CONNECT_TIMEOUT, &dwTime, sizeof(DWORD));
    std::wstring strHeader = L"Content-Type: multipart/form-data; boundary=--boundary_cswuygtest\r\n";
    ::WinHttpAddRequestHeaders(hRequest, strHeader.c_str(), strHeader.length(), WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);

    std::string strMIME = "----boundary_cswuygtest\r\n";
    strMIME += "Content-Disposition: form-data; name=\"ufile01\"; filename=\"upload_test_http_cswuyg.txt\"\r\n";
    strMIME += "Content-Type:application/octet-stream\r\n\r\n";
    strMIME += "1\r\n";
    /////////////////////////////
    //除了這個表單之外,後頭還必須要有另外的表單資訊,否則hfs服務端那邊無法寫入檔案,這裡僅為測試。
    //strMIME += "----boundary_cswuygtest\r\n" ;
    //strMIME += "Content-Disposition: form-data; name=\"name\"\r\n\r\n";
    //strMIME += "temp.txt\r\n";
    /////////////////////////
    strMIME += "----boundary_cswuygtest--\r\n";
    /////////////////////////
    //可以直接寫入
    //::WinHttpSendRequest(hRequest, strHeader.c_str(), strHeader.length(), (LPVOID)strMIME.c_str(), strMIME.length(), strMIME.length(), 0);

    //也可以後面分步寫入
    ::WinHttpSendRequest(hRequest, NULL, 0, NULL, 0, strMIME.length(), 0);
    DWORD dwWritten = 0;
    while(!strMIME.empty())
    {
        ::WinHttpWriteData(hRequest, strMIME.c_str(), strMIME.length(), &dwWritten);
        if (strMIME.length() > dwWritten)
        {
            strMIME.substr(dwWritten);
        }
        else
        {
            strMIME = "";
        }
        
    }
    ::WinHttpReceiveResponse(hRequest, NULL);

    char buf[1024] = { 0 };
    DWORD dwToRead = 1024;
    DWORD dwHaveRead = 0;
    ::WinHttpReadData(hRequest, buf, dwToRead, &dwHaveRead);
    std::cout << buf << std::endl;
    ::WinHttpCloseHandle(hRequest);
    ::WinHttpCloseHandle(hConnect);
    ::WinHttpCloseHandle(hSession);
}
複製程式碼      wireshark抓包: 頭域設定:   表單設定:  

三、總結

     這兩個例子用hfs做伺服器測試可以發現在上傳資料夾中上傳了檔案,程式碼都很簡單。測試過程中的最重要的是能儘快的反饋結果,所以能自己搭建一個http伺服器很重要,hfs工具,非常簡單便捷。   POST表單最重要的是瞭解表單的格式。首先要在頭域增加Content-Type說明,如:“content-type:multipart/from-data; boundary=--cswuyg”。然後http body填充的資料要以MIME格式上傳,每部分資料的開頭都是由“--”+boundary開始的,結尾以"--"+boundary+"--"結束。   如果要上傳檔案,需要跟伺服器約定好格式,譬如上邊例子約定在content-Disposition中有filename鍵的則是上傳檔案資訊,該片的content為檔案資料,這應該是比較通用的約定。 附上部分資料

MIME:http://baike.baidu.com/view/160611.htm
MIME來源 : Multipurpose Internet Mail Extensions 多用途網際網路郵件擴充套件。伺服器會將它們傳送的多媒體資料的型別告訴瀏覽器,而通知手段就是說明該多媒體資料的MIME型別。
GZIP檔案:application/x-gzip
任意的二進位制資料:application/octet-stream.
普通文字:text/plain

wireshark: MIME Multipart Media Encapsulation 代表使用表單上傳的所有資料 Encapsulated multipart part 代表表單中不同項的資料 Boundary 用來隔開表單中不同部分的資料 每部分資料的開頭都是由“--”+boundary開始的。 如果使用wireshark抓包,而http使用的不是預設的80埠,那麼需要抓TCP包,然後再強制Decode為http。 HTTP協議的文件: chroimumde breakpad_sender程式碼中也有post表單上傳檔案部分,只不過它是用WinInet寫的。 2015 補充上傳檔案時的抓包資訊: