zoukankan      html  css  js  c++  java
  • MFC中使用post提交form-data上传文件

    已经有将近6年时间没写过MFC了,想想以前我也是写VC++入门程序开发的,那时候写协议栈、搞语音编码、做视频压缩和实时数据传输,相比现在更多偏业务的开发,那时候搞得都是非常技术的东西。眨眼间,MFC已经退出舞台,就连微软也在主推C#.net,曾经风光无限的MFC开发现如今已经几乎消失;ActiveX有java的applet来替代,MFC桌面应用程序也由更简单友好的C#取代,而软件开发的趋势早已经从传统的C/S过渡到B/S,云等等。技术的日新月异逼迫IT人不得不马不停蹄的学习,否则,一不留神就会被这个时代所抛弃,被后来者所碾压。时刻保持一颗不断学习,谦逊,对技术敬畏的心,才能在这条路上才能走的更远走得稳。

    因为一些缘故,刚毕业时候带我的老领导找我紧急支援一点东西,具体需求就是用MFC写一个抓屏程序,把当前屏幕抓取下来,实时提交给服务器,然后再由服务中转推送给客户端去显示,可以简单理解为一个桌面监控程序。让我帮忙处理抓屏和POST提交文件的功能。我听了之后本来想拒绝,尽管老领导对我的印象还是VC++coder,但毕竟我已经好多年不写MFC,着实没有信心搞定。可是又不想辜负老领导对我期望,毕竟是他是我IT路上的引路人,所以最后还答应了。然后这个周末就一直在网上搜相关的解决方案,所幸,这些东西并不复杂,关于截屏的代码非常多,大多都能满足需求。但是最后关于POST提交文件这一块遇到了困难,网上能搜索到很多关于MFC使用post上传下载文件的代码,但是几乎没有哪一个能真正用的。

    我这里记录并分享一个我最后搜索并优化的方案,先看代码


    CString CScreenPushDlg::MakeRequestHeaders(CString& strBoundary)
    {
    CString strFormat;
    CString strData;
    strFormat = _T("Content-Type: multipart/form-data; boundary=%s ");
    strData.Format(strFormat, strBoundary);
    return strData;
    }

    CString CScreenPushDlg::MakePreFileData(CString& strBoundary, CString& strFileName, int iRecordID)
    {
    CString strFormat;
    CString strData;
    strFormat += _T("--%s");
    strFormat += _T(" ");
    strFormat += _T("Content-Disposition: form-data; name="job_number"");
    strFormat += _T(" ");
    strFormat += _T("%d");
    strFormat += _T(" ");
    strFormat += _T("--%s");
    strFormat += _T(" ");
    strFormat += _T("Content-Disposition: form-data; name="image"; filename="%s"");
    strFormat += _T(" ");
    //strFormat += _T("Content-Type: audio/wav");
    strFormat += _T("Content-Type: image/png");
    strFormat += _T(" ");
    strFormat += _T("Content-Transfer-Encoding: binary");
    strFormat += _T(" ");
    strData.Format(strFormat, strBoundary, iRecordID, strBoundary, strFileName);
    return strData;
    }

    CString CScreenPushDlg::MakePostFileData(CString& strBoundary)
    {
    CString strFormat;
    CString strData;
    strFormat = _T(" ");
    strFormat += _T("--%s");
    strFormat += _T(" ");
    strFormat += _T("Content-Disposition: form-data; name="act"");
    strFormat += _T(" ");
    strFormat += _T("upload_img");
    strFormat += _T(" ");
    strFormat += _T("--%s--");
    strFormat += _T(" ");
    strData.Format(strFormat, strBoundary, strBoundary);
    return strData;
    }
    //上传文件数据至HTTP服务器
    int CScreenPushDlg::UploadFile(const CString &szServerURL, const CString &szUploadFileName)
    {
    if (szServerURL.IsEmpty() || szUploadFileName.IsEmpty())
    {
    return -1;
    }
    BOOL bRet = FALSE;
    DWORD dwServiceType = 0;
    CString strServer = _T("");
    CString strObject = _T("");
    INTERNET_PORT nPort = 0;
    bRet = AfxParseURL(szServerURL, dwServiceType, strServer, strObject, nPort);
    if(!bRet)
    {
    return -2;
    }

    int nRet = 0;
    CInternetSession Session;
    CHttpConnection * pHttpConnection = NULL;
    CFile fTrack;
    CHttpFile* pHTTPFile = NULL;
    CString strHTTPBoundary = _T("");
    CString strPreFileData = _T("");
    CString strPostFileData = _T("");
    DWORD dwTotalRequestLength;
    DWORD dwChunkLength = 0;
    DWORD dwReadLength = 0;
    DWORD dwResponseLength = 0;
    TCHAR szError[MAX_PATH] = {0};
    void* pBuffer = NULL;
    LPSTR szResponse = NULL;
    CString strResponse = _T("");
    BOOL bSuccess = TRUE;
    CString strDebugMessage = _T("");

    if (FALSE == fTrack.Open(szUploadFileName, CFile::modeRead | CFile::shareDenyWrite))
    {
    //AfxMessageBox(_T("Unable to open the file."));
    return -3;
    }
    int nPos = szUploadFileName.ReverseFind('\');
    CString strFileName = szUploadFileName.Mid(nPos+1);
    int iRecordID = _ttoi(m_Num);
    strHTTPBoundary = _T("----istroop----");
    strPreFileData = MakePreFileData(strHTTPBoundary, strFileName, iRecordID);
    strPostFileData = MakePostFileData(strHTTPBoundary);
    dwTotalRequestLength = strPreFileData.GetLength() + strPostFileData.GetLength() + fTrack.GetLength();
    dwChunkLength = 64 * 1024;
    pBuffer = malloc(dwChunkLength);
    if (NULL == pBuffer)
    {
    fTrack.Close();
    return -4;
    }
    try
    {
    pHttpConnection = Session.GetHttpConnection(strServer,nPort);
    pHTTPFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject);
    pHTTPFile->AddRequestHeaders(MakeRequestHeaders(strHTTPBoundary));
    pHTTPFile->SendRequestEx(dwTotalRequestLength, HSR_SYNC | HSR_INITIATE);
    #ifdef _UNICODE
    USES_CONVERSION;
    pHTTPFile->Write(W2A(strPreFileData), strPreFileData.GetLength());
    #else
    pHTTPFile->Write((LPSTR)(LPCSTR)strPreFileData, strPreFileData.GetLength());
    #endif
    dwReadLength = -1;
    DWORD m_dwUploadSize = 0;
    while (0 != dwReadLength)
    {
    // strDebugMessage.Format(_T("%u / %un"), fTrack.GetPosition(), fTrack.GetLength());
    // TRACE(strDebugMessage);
    dwReadLength = fTrack.Read(pBuffer, dwChunkLength);
    if (0 != dwReadLength)
    {
    m_dwUploadSize += dwReadLength;
    pHTTPFile->Write(pBuffer, dwReadLength);
    }
    }
    #ifdef _UNICODE
    pHTTPFile->Write(W2A(strPostFileData), strPostFileData.GetLength());
    #else
    pHTTPFile->Write((LPSTR)(LPCSTR)strPostFileData, strPostFileData.GetLength());
    #endif
    pHTTPFile->EndRequest(HSR_SYNC);
    dwResponseLength = pHTTPFile->GetLength();
    while (0 != dwResponseLength)
    {
    szResponse = (LPSTR)malloc(dwResponseLength + 1);
    szResponse[dwResponseLength] = '';
    pHTTPFile->Read(szResponse, dwResponseLength);
    strResponse += szResponse;
    free(szResponse);
    szResponse = NULL;
    dwResponseLength = pHTTPFile->GetLength();
    }
    //strResponse;
    }
    catch (CException* e)
    {
    // e->GetErrorMessage(szError, MAX_PATH);
    // e->Delete();
    nRet = -5;
    }
    if (NULL != pHTTPFile)
    {
    pHTTPFile->Close();
    delete pHTTPFile;
    pHTTPFile = NULL;
    }

    fTrack.Close();
    if (NULL != pBuffer)
    {
    free(pBuffer);
    pBuffer = NULL;
    }

    return nRet;
    }
    这段代码相比网上能搜到的大多数方案,最大的区别在于写了一个MakeRequestHeaders和MakePreFileData方法。

    因为这些年主要写java和前端js开发,java中有httpclient可以直接发起post/get请求,非常方便,js中有ajax、axios等都非常方便的发起的请求,类似设置header、Content-type、入参data等都有很简单明了的方法,但是VC++中使用CHttpFile类进行post请求却相对复杂些,主要在于api封装的颗粒度。我们先来看以下下面这两张截图:

                                                                                    (截图一)

                                                                            (截图二)

    这是一个ajax发起的上传文件并携带参数的POST请求,我以前都是只关注截图一,从来没考虑也没看过截图二中的内容。其实对于MFC中的POST请求,就需要我们自己封装原始的数据,即自己封装好截图二中的数据格式,类似Content-Disposition、Content-Type、通过name指定参数名,通过filename指定具体文件,还要设置FormBoundar等。这些东西在java、js中时绝对不会接触到的。而MakePreFileData正是起到这个作用。

    大概记录到这里吧,开发语言这东西,真的是不写就会生疏,以前写VC++都能盲打api,现在看数据类型的声明类都觉得生疏,真的是感慨万千。毕业的这几年收获最大的大概是不像以前那么玻璃心,见不得别人和自己相左的意见;但是相对的,我觉得那时候的自己更自负,而现在我却有点过分自知,甚至不那么自信。

    认清自己不难,难的是知道自己的不足和缺点后怎么鞭策自己去改变。
    --------------------- 

  • 相关阅读:
    C# Devexpress GridView获得当前活动行(选中行)的索引
    C# Devexpress GridView获得当前活动行(选中行)的索引
    计算机网络学习总结(超赞!!!)
    计算机网络学习总结(超赞!!!)
    30张图带你彻底理解红黑树
    30张图带你彻底理解红黑树
    DevExpress GridView 添加和设置右键菜单
    DevExpress GridView 添加和设置右键菜单
    数据库分离和附加 (SQL Server)
    数据库分离和附加 (SQL Server)
  • 原文地址:https://www.cnblogs.com/hyhy904/p/10942239.html
Copyright © 2011-2022 走看看