MFC使用http post请求上传文件

文章目录

  • 前言
  • 代码

前言

笔者在Windows编程开发时候,有个上传文件的需求,服务端给的接口是http接口,和网页上面 表单上传文件一样(form-data方式)。当然我们拿到这需求,一想 用Windows原生的API去做 肯定麻烦一点 当然也能做,再一想 我们用支持http协议的框架不就行了嘛,MFC、libcurl、OpenSSL等等应该很多。笔者比较熟悉的2个库,MFC和libcurl,所以决定用这2个库来做文件上传。

和大家一样,刚开始觉的很简单,网上搜下然后改下就OK。但是笔者却花费了些时间才完成。一是太相信postman了被postman给误导了,二是把代码写的更通用更简洁了一点。

笔者刚开用postman模拟上传文件,能成功上传。然后使用postman生成的http报文数据去组装,按理来说肯定可以的,但是就是被误导。

MFC使用http post请求上传文件_第1张图片

代码

网上贴的代码,感觉的都不是很好,所以笔者稍微写整洁了一点,更通用了一点,下面是核心代码 贴出来。实际上可以根据文件后缀名获取到该文件的Content-Type的,可以做的更智能点 程序内部加一个数据字典就可以实现。笔者这里就偷懒了。

class PostUpFileValStruct;
typedef std::vector PostUpFileValVector;

class PostUpFileValStruct
{
public:
	CString name; // 普通字段、文件字段 有值
	CString filename; // 文件字段 有值
	CString ContentType; // 文件字段 有值
	CString content; // 普通字段 有值
}; 

/*
	http post请求上传文件函数
*/
bool CUtility::HttpPostUploadFile(const PostUpFileValVector &PostParamVec/*in 请求参数列表*/, const CString &strURL/*in 上传文件服务器接口url*/,
                                  std::map &requestHeaders/*in 请求头数据*/,CString &strResponse/*out 响应数据*/,
                                  CString &strErrMsg/* out 上传文件出错信息*/)
{
	bool bResult = false;
	CString strPostUrl = strURL;

	DWORD dw(0);
	CString strServer;
	CString strObject;
	INTERNET_PORT nPort;

	AfxParseURL(strPostUrl, dw, strServer, strObject, nPort );
	CInternetSession session;
	if(strURL.IsEmpty())
	{
		strErrMsg = _T("strURL为空!!!");
		return bResult;
	}

	const int nTimeOut = 5000;//3000
	session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, nTimeOut); //重试之间的等待延时
	session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 3);   //重试次数
	session.SetOption(INTERNET_OPTION_SEND_TIMEOUT, nTimeOut );
	session.SetOption(INTERNET_OPTION_RECEIVE_TIMEOUT, nTimeOut );
	CHttpConnection *pHttpConnection(NULL);
	CHttpFile *pHttpFile(NULL);
	try
	{
		pHttpConnection = session.GetHttpConnection( strServer, INTERNET_FLAG_DONT_CACHE , nPort );
		CString str = L"https:";
		CString strTemp = strURL.Left(str.GetLength());
		BOOL bHttpsFlag(FALSE);
		if (0 == strTemp.CompareNoCase(str))
		{
			bHttpsFlag = TRUE;
		}

		if (bHttpsFlag)
		{
			pHttpFile = pHttpConnection->OpenRequest( CHttpConnection::HTTP_VERB_POST, strObject, NULL, 0, 0, _T("HTTP/1.1"), INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD|INTERNET_FLAG_SECURE);
		}
		else
		{
			pHttpFile = pHttpConnection->OpenRequest( CHttpConnection::HTTP_VERB_POST, strObject, NULL, 0, 0, _T("HTTP/1.1"), INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_RELOAD);
		}
		//CString cstrBoundary = _T("----WebKitFormBoundaryoDL1nQAJxdvsAlcu");
		CString cstrBoundary = _T("----") + CUtility::GetGUID(); // 这个字符串,随意生成即可,主要是界定字段数据的。
		CString boundaryHead;
		boundaryHead.Format(_T("Content-Type:multipart/form-data; boundary=%s"),cstrBoundary);


		pHttpFile->AddRequestHeaders(_T("Accept: application/json, text/plain, */*" ) );
		pHttpFile->AddRequestHeaders(_T("Accept-Encoding: gzip, deflate, br") );
		pHttpFile->AddRequestHeaders(_T("Accept-Language: zh-cn"));
		//pHttpFile->AddRequestHeaders(_T("Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoDL1nQAJxdvsAlcu"));
		pHttpFile->AddRequestHeaders(_T("Cache-Control:	no-cache"));
		std::map::iterator it = requestHeaders.begin();
		CString strHeadTemp;
		for(; it != requestHeaders.end(); it++)
		{
			strHeadTemp.Format(_T("%s: %s"),it->first,it->second);
			pHttpFile->AddRequestHeaders(strHeadTemp);
		}
		pHttpFile->AddRequestHeaders(boundaryHead);

		if (bHttpsFlag)
		{
			DWORD dwFlags(0);
			pHttpFile->QueryOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
			dwFlags |= (SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
			            SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
			            SECURITY_FLAG_IGNORE_WRONG_USAGE |
			            SECURITY_FLAG_IGNORE_REVOCATION |
			            SECURITY_FLAG_IGNORE_UNKNOWN_CA);

			pHttpFile->SetOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
		}

		if( pHttpFile != NULL )
		{
			// MFC在传输数据前要先发送数据长度,所以先计算上传报文数据的长度

			////////--- 通过post请求上传文件报文格式如下 ---/////////
			//一个普通的字段
			//\r\n--{strBoundary}\r\nContent-Disposition: form-data; name="tranCode"\r\n\r\n2007
			//一个文件的字段
			//\r\n--{strBoundary}\r\nContent-Disposition: form-data; name="file"; filename="1590721092834.jpg"\r\nContent-Type: image/jpeg\r\n\r\n{二进制数据}
			//报文结束标志
			//\r\n--{strBoundary}--\r\n

			// 结束部分报文
			std::string strBoundary = CUtility::W2Astring(cstrBoundary);
			std::string dataEnd = "\r\n--"+strBoundary+"--\r\n";

			// 传输数据时是以字节为单位进行传输的,所以这里都用std::string或者char*进行处理
			// 1.计算上传报文数据长度
			DWORD totalLen = 0;
			bool bFile = false;
			for(int i = 0; i < PostParamVec.size(); i++)
			{
				bFile =  PostParamVec[i].filename.IsEmpty() ? false:true;
				CString temp;
				if(bFile)
				{
					temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n"),
					            cstrBoundary,PostParamVec[i].name,PostParamVec[i].filename,PostParamVec[i].ContentType);
				}
				else
				{
					temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s"),
					            cstrBoundary,PostParamVec[i].name,PostParamVec[i].content);
				}
				// post请求时,有的参数值可能带中文,需要unicode->utf8,写数据的时候同理
				char* pData = NULL;
				CUtility::ConvertUnicodeToUTF8(temp,pData);
				if(pData)
				{
					totalLen += ::strlen(pData);
					delete[] pData;
					pData = NULL;
				}

				if(bFile)
				{
					CFile myFile;
					CFileException fileException;
					if ( !myFile.Open( PostParamVec[i].filename, CFile::modeRead, &fileException ) )
					{
						CString errMsg;
						errMsg.Format(_T("Can't open file %s, error = %u\n"),PostParamVec[i].filename, fileException.m_cause);
						TRACE(errMsg);
#ifdef TEXT_LOG
						CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),errMsg);
#endif
					}
					totalLen += myFile.GetLength();
					myFile.Close();
				}
			}
			totalLen+= dataEnd.length();
			pHttpFile->SendRequestEx( (DWORD)totalLen );

			// 2.往服务器写数据
			bFile = false;
			for(int i = 0; i < PostParamVec.size(); i++)
			{
				bFile =  PostParamVec[i].filename.IsEmpty() ? false:true;
				CString temp;
				if(bFile)
				{
					temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n"),
					            cstrBoundary,PostParamVec[i].name,PostParamVec[i].filename,PostParamVec[i].ContentType);
				}
				else
				{
					temp.Format(_T("\r\n--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s"),
					            cstrBoundary,PostParamVec[i].name,PostParamVec[i].content);
				}
				// post请求时,有的参数值可能带中文,需要unicode->utf8,写数据的时候同理
				char* pData = NULL;
				CUtility::ConvertUnicodeToUTF8(temp,pData);
				if(pData)
				{
					int pDataLen = ::strlen(pData);
					pHttpFile->Write(pData, pDataLen);
					delete[] pData;
					pData = NULL;
				}

				if(bFile)
				{
					CFile myFile;
					CFileException fileException;
					if ( !myFile.Open( PostParamVec[i].filename, CFile::modeRead, &fileException ) )
					{
						TRACE( _T("Can't open file %s, error = %u\n"),
						       PostParamVec[i].filename, fileException.m_cause );
					}

					int bufflength = 4 * 1024;
					byte* buffer = new byte[bufflength];
					memset(buffer,0,bufflength);
					int byteRead = 0;
					while ((byteRead = myFile.Read(buffer, bufflength)) != 0)
					{
						pHttpFile->Write(buffer, byteRead);
						memset(buffer,0,bufflength);
					}
					myFile.Close();
				}
			}

			pHttpFile->Write(dataEnd.c_str(),dataEnd.length());
			pHttpFile->EndRequest( );

#ifdef TEXT_LOG
			CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),_T(" EndRequest ok"));
#endif

			DWORD dwStateCode = 0;
			pHttpFile->QueryInfoStatusCode(dwStateCode);
			if (dwStateCode != HTTP_STATUS_OK )
			{
				CString str;
				str.Format(_T("服务器返回状态码:%d !!!\n"), dwStateCode);
#ifdef TEXT_LOG
				CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),str);
#endif
				TRACE(str);
				// 认为出错
				strErrMsg = str;

			}
			else if(dwStateCode == HTTP_STATUS_OK )
			{
				// 3.处理响应数据
				CString contentType ;
				pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_TYPE, contentType );
				if(contentType.Find(_T("application/json")) >= 0)
				{
					bResult = true;
					DWORD contentLength  = 0;
					BOOL b = pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH,contentLength);
					if(contentLength == 0)
					{
						contentLength = 4096;
					}
					char *pReadBuf = new char[contentLength +1];
					memset(pReadBuf, 0, contentLength +1);

					DWORD nRead = pHttpFile->Read(pReadBuf, contentLength);

					// 响应数据可能带中文,需要utf8->unicode
					CUtility::ConvertUTF8ToUnicode(pReadBuf, strResponse,nRead);

					delete []pReadBuf;
					pReadBuf = NULL;
				}
				else
				{
					// 认为出错
					strErrMsg = _T("Content-type错误!!!");
#ifdef TEXT_LOG
					CUtility::TextLog(_T("CUtility::HttpPostUploadFile"),strErrMsg);
#endif
					TRACE(strErrMsg);

				}

#ifdef TEXT_LOG
				CUtility::TextLog(L"CUtility::HttpPostUploadFile",L"QueryInfoStatusCode ok" );
#endif

			}
		}
	}
	catch (CInternetException* e)
	{

		TCHAR szError[1024] = {0};
		e->GetErrorMessage(szError,1024);
		DWORD errorCode = e->m_dwError;
		CString str;
		str.Format(_T("errorMsg:%s,m_dwError=%d"), szError,errorCode);
#ifdef TEXT_LOG
		CUtility::TextLog(_T("CUtility::HttpPostUploadFile,HttpPostUploadFile exception"),str);
#endif

		e->Delete();
		TRACE( _T("\r----------------internet error:%s\r"),str );
		strErrMsg = str;
	}

	if(pHttpConnection != NULL)
	{
		pHttpConnection->Close();
		delete pHttpConnection;
		pHttpConnection = NULL;
	}
	session.Close();

	if(pHttpFile != NULL)
	{
		pHttpFile->Close();
		delete pHttpFile;
		pHttpFile = NULL;
	}

	return bResult;
}

接口调用地方代码

void CClienTestDlg::OnBnClickedButton3()
{
	// 使用MFC 方式上传文件
	PostUpFileValVector vec;
	PostUpFileValStruct param;
	param.name = _T("tranCode");
	param.content = _T("2007");
	vec.push_back(param);
	param.name = _T("busiNo");
	param.content = _T("20200527000000209096");
	vec.push_back(param);
	param.name = _T("sysId");
	param.content = _T("ACP");
	vec.push_back(param);
	param.name = _T("operaId");
	param.content = _T("00300");
	vec.push_back(param);
	param.name = _T("branchNo");
	param.content = _T("00300");
	vec.push_back(param);
	param.name = _T("billId");
	param.content = _T("ACP04");
	vec.push_back(param);
	param.name = _T("file");
	param.content = _T("");
	param.filename = _T("D:\\20200520120631.jpg");
	param.ContentType = _T("image/jpeg");
	vec.push_back(param);

	CString upUrl = _T("http://172.xx.xxx.xxx:xxxxx/file/xxxxx");
	CString cstrResponse;
	CString cstrErrMsg;
	std::map requestHeads;
	bool bResult = CUtility::HttpPostUploadFile(vec,upUrl,requestHeads,
		cstrResponse,cstrErrMsg);

	CString strMsg;
	if(bResult)
	{
		strMsg.Format(_T("文件上传成功,响应信息:%s"),cstrResponse);
		MessageBox(strMsg,_T("文件上传"));
	}
	else
	{
		strMsg.Format(_T("文件上传失败!!!错误提示信息:%s"),cstrErrMsg);
		MessageBox(strMsg,_T("文件上传"));
	}

	return;
}

你可能感兴趣的:(【Windows编程】,【Language_C++】,MFC上传文件,http,post请求上传文件,form-data,CHttpFile,Windows编程)