zoukankan      html  css  js  c++  java
  • 通过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
    ";
        ::WinHttpAddRequestHeaders(hRequest, strHeader.c_str(), strHeader.length(), WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE);
    
        std::string strMIME = "----boundary_cswuygtest
    ";
        strMIME += "Content-Disposition: form-data; name="ufile01"; filename="upload_test_http_cswuyg.txt"
    ";
        strMIME += "Content-Type:application/octet-stream
    
    ";
        strMIME += "1
    ";
        /////////////////////////////
        //除了这个表单之外,后头还必须要有另外的表单信息,否则hfs服务端那边无法写入文件,这里仅为测试。
        //strMIME += "----boundary_cswuygtest
    " ;
        //strMIME += "Content-Disposition: form-data; name="name"
    
    ";
        //strMIME += "temp.txt
    ";
        /////////////////////////
        strMIME += "----boundary_cswuygtest--
    ";
        /////////////////////////
        //可以直接写入
        //::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 补充上传文件时的抓包信息:
  • 相关阅读:
    C# Invoke 和 BeginInvoke 的的区别
    ArcGIS API For JS 中设置图层显示的方法(ArcGISDynamicMapServiceLayer)setVisibleLayers(ids, doNotRefresh?)介绍
    OpenLayer学习之矢量地图
    Python爬去百思不得其解的图片(VS2017)
    .NET面试试题
    arcgis for javascript 鼠标移到对象上面则置亮并弹出气泡
    ASP.NET MVC 中IBaseDal接口的封装
    ASP.NET中MemcacheHelper封装
    ASP.NET验证码的封装和使用
    Self-Paced Training (2)
  • 原文地址:https://www.cnblogs.com/cswuyg/p/3185164.html
Copyright © 2011-2022 走看看