这几天用WinHttp做一个文件上传的东东,中间遇到大坑,虽然时候发现其实是小问题,但是既然耽搁了这么久的时间,还是决定写下来。
之前也在网上看到过很多网友的文章,说在使用WinHttp上传文件的时候总是出现各种问题,其实,只要按照格式来,就不会有这么难。这里首先要晓得在使用WinHttp POST上传文件的时候要注意哪些:
1)POST不像GET那样方便,可以将参数直接带上。
2)POST需要按照multipart 这种格式来组装数据。具体的格式请参见RFC1341;https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
我自己本人在做写这个代码的时候,实际上是因为粗心。因为没有仔细看协议,所以最终栽在了boundary这里。从RFC1341这里,可以看到boundary应该是这样的:
header里面:Content-Type:xxxx; boundary=yyyyyyyyyyyyyy
POST内容里面,分割字符串开始的时候,应在boundary前面多加两个短横杠,就像这样:--yyyyyyyyyyyyyy,我当时就是没有认真区分这里,所以弄得焦头烂额,搞了两个周。。
另外,建议用WinHttpAddRequestHeaders来填充请求头。话不多说,上代码,希望对大家有帮助
//头文件
#pragma once
/*
A simple demo that use WinHttp APIs post file to server
References:
https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
*/
#include <Windows.h>
#include <winhttp.h>
#include <iostream>
#include <string>
#pragma comment(lib,"winhttp.lib")
#ifdef _UNICODE
typedef std::wstring tstring;
#else
typedef std::string tstring;
#endif
#define MIN_SIZE 0x20
#define MAX_SIZE 0x200
#define WRITE_PER_TIME 0x400
class CWinHttpPost
{
public:
CWinHttpPost();
CWinHttpPost(wchar_t *ip, unsigned short port, wchar_t *app);
~CWinHttpPost();
bool DoPost(tstring &file, std::string &response);
private:
tstring &BuildBoundary(tstring &bdrout);
std::string &BuildFileParamString(std::string &key, std::string &value, std::string ¶mstr);
std::string &BuildOtherParamString(std::string &key, std::string &value, std::string ¶mstr);
std::string &BuildEndBoundaryString(std::string &endflag);
std::wstring &BuildHeaderContentType(std::wstring &strout);
std::wstring &string2wstring(std::string &astr, std::wstring &wstr);
std::string &wstring2string(std::wstring &wstr, std::string &astr);
unsigned long ReadPostFile(tstring &file, char **outbuf);
std::string &GetPathFileName(std::string &path, std::string &name);
bool SendRequest(void *data, unsigned long length, char *response, size_t len=512);
std::wstring &BuildContentLength(unsigned long length, std::wstring &lengthstr);
unsigned long BuildPostEntity(char *filebuf, unsigned long filesize, const char *filename, char **entity);
private:
tstring m_boundary;
wchar_t *m_ip;
wchar_t *m_app;
unsigned short m_port;
const wchar_t *m_agent = L"WinHttpPost";
};
//源文件
#include "WinHttpPost.h"
CWinHttpPost::CWinHttpPost()
{
}
CWinHttpPost::CWinHttpPost(wchar_t * ip, unsigned short port, wchar_t * app)
{
m_ip = ip;
m_port = port;
m_app = app;
}
CWinHttpPost::~CWinHttpPost()
{
}
/*
The DoPost function will return the status of POST operation ,and the response data will be carried out by response
*/
bool CWinHttpPost::DoPost(tstring & file, std::string &response)
{
std::string filename;
char *pfilebuffer = 0, *postentity = 0;
unsigned long filesize = 0,entitysize=0;
char res[MAX_SIZE];
#ifdef _UNICODE
std::string tmpfile;
wstring2string(file, tmpfile);
GetPathFileName(tmpfile, filename);
#else
GetPathFileName(file, filename);
#endif
filesize = ReadPostFile(file, &pfilebuffer);
entitysize = BuildPostEntity(pfilebuffer, filesize, filename.c_str(), &postentity);
bool ok = SendRequest(postentity, entitysize, res);
if (ok)
{
response = res;
}
return false;
}
tstring & CWinHttpPost::BuildBoundary(tstring & bdrout)
{
char tickCount[32] = { 0x0 };
DWORD tc = GetTickCount();
_ltoa_s(tc, tickCount, 16);
std::string astr("--------");
astr.append(tickCount);
#ifdef _UNICODE
string2wstring(astr, bdrout);
#else
bdrout = tickCount;
#endif // _UNICODE
return bdrout;
}
/*
Build the pararmeter string that matchs the post type 'File'(i take this type from fiddler)
At here,we assume that we will post the binary file,so the default Content-Disposition is octet-stream
and all of the data will be set to ASCII
Format:
--boundary
Content-Type: form-data; name="key"; filename="filename"
Content-Dispostion: octet-stream
data
--boundary
Note that the boundary string has two extern '-' before it,see the detail in WinHttpPost.h References section
*/
std::string & CWinHttpPost::BuildFileParamString(std::string & key, std::string & value, std::string & paramstr)
{
std::string temp;
#ifdef _UNICODE
wstring2string(m_boundary, temp);
paramstr.append("--").append(temp).append("\r\n");
#else
paramstr.append("--").append(m_boundary).append("\r\n");
#endif
paramstr.append("Content-Type: form-data; name=\"")
.append(key).append("\"")
.append("; filename=\"").append(value).append("\"").append("\r\n")
.append("Content-Disposition: octet-stream").append("\r\n\r\n");
return paramstr;
}
std::string & CWinHttpPost::BuildOtherParamString(std::string & key, std::string & value, std::string & paramstr)
{
std::string temp;
#ifdef _UNICODE
wstring2string(m_boundary, temp);
paramstr.append("--").append(temp).append("\r\n");
#else
paramstr.append("--").append(m_boundary).append("\r\n");
#endif
paramstr.append("Content-Disposition: form-data; name=\"")
.append(key).append("\"").append("\r\n\r\n").append(value).append("\r\n");
return paramstr;
}
std::string & CWinHttpPost::BuildEndBoundaryString(std::string &endflag)
{
std::string temp;
#ifdef _UNICODE
wstring2string(m_boundary, temp);
endflag.append("--").append(temp).append("--");
#else
endflag.append("--").append(m_boundary).append("--");
#endif
return endflag;
}
std::wstring & CWinHttpPost::BuildHeaderContentType(std::wstring & strout)
{
if (m_boundary.empty()) {
return strout;
}
#ifdef _UNICODE
strout.append(L"Content-Type: multipart/form-data; boundary=").append(m_boundary);
#else
std::wstring tmp;
string2wstring(m_boundary, tmp);
strout.append(L"Content-Type: multipart/form-data; boundary=").append(tmp);
#endif
return strout;
}
std::wstring & CWinHttpPost::string2wstring(std::string & astr, std::wstring & wstr)
{
if (astr.empty()) {
return wstr;
}
DWORD wchSize = MultiByteToWideChar(CP_ACP, 0, astr.c_str(), -1, NULL, 0);
wchar_t *pwchar = new wchar_t[wchSize];
ZeroMemory(pwchar, wchSize * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, astr.c_str(), astr.length(), pwchar, wchSize);
wstr = pwchar;
delete pwchar;
pwchar = NULL;
return wstr;
}
std::string & CWinHttpPost::wstring2string(std::wstring & wstr, std::string & astr)
{
if (wstr.empty()) {
return astr;
}
BOOL usedefault = TRUE;
DWORD achSize = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, "", &usedefault);
char *pachar = new char[achSize];
ZeroMemory(pachar, achSize * sizeof(char));
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), astr.length(), pachar, achSize, "", &usedefault);
astr = pachar;
delete pachar;
pachar = NULL;
return astr;
}
unsigned long CWinHttpPost::ReadPostFile(tstring & file, char ** outbuf)
{
if (file.empty()) {
return 0;
}
HANDLE hFile = CreateFile(
file.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return 0;
}
DWORD filesize = GetFileSize(hFile, 0);
if (filesize == 0) {
CloseHandle(hFile);
return 0;
}
*outbuf = new char[filesize];
if (*outbuf == 0) {
CloseHandle(hFile);
return 0;
}
DWORD bytesRead = 0;
if (!ReadFile(hFile, *outbuf, filesize, &bytesRead, 0)) {
delete *outbuf;
CloseHandle(hFile);
return 0;
}
CloseHandle(hFile);
return filesize;
}
std::string & CWinHttpPost::GetPathFileName(std::string & path, std::string & name)
{
if (path.empty()) {
return name;
}
std::string::size_type pos = path.rfind('\\');
if (pos == std::string::npos) {
return path;
}
name = path.substr(pos + 1);
return name;
}
/*
Not close handle here
*/
bool CWinHttpPost::SendRequest(void * data, unsigned long length, char *response, size_t len)
{
HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;
BOOL opSuccess = FALSE;
DWORD btsWritten = 0, size_ = 0, btsRead = 0;
bool sendok = true;
hSession = WinHttpOpen(m_agent, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) {
sendok = false;
return sendok;
}
hConnect = WinHttpConnect(hSession, m_ip, m_port, 0);
if (!hConnect) {
sendok = false;
return sendok;
}
hRequest = WinHttpOpenRequest(hConnect, L"POST", m_app, WINHTTP_NO_REFERER, NULL, NULL, 0);
if (!hRequest) {
sendok = false;
return sendok;
}
opSuccess = WinHttpAddRequestHeaders(hRequest, L"Expect:100-continue", -1L, 0);
if (!hRequest) {
sendok = false;
return sendok;
}
std::wstring ct;
BuildHeaderContentType(ct);
opSuccess = WinHttpAddRequestHeaders(hRequest, ct.c_str(), -1L, 0);
if (!opSuccess) {
sendok = false;
return sendok;
}
std::wstring cl;
BuildContentLength(length, cl);
opSuccess = WinHttpAddRequestHeaders(hRequest, cl.c_str(), -1L, 0);
if (!opSuccess) {
sendok = false;
return sendok;
}
opSuccess = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, NULL, 0, length, 0);
if (!opSuccess) {
sendok = false;
return sendok;
}
//you can also write data multiple times
opSuccess = WinHttpWriteData(hRequest, data, length, &btsWritten);
if (!opSuccess) {
sendok = false;
return sendok;
}
opSuccess = WinHttpReceiveResponse(hRequest, 0);
if (!opSuccess) {
sendok = false;
return sendok;
}
opSuccess = WinHttpQueryDataAvailable(hRequest, &size_);
if (!opSuccess) {
sendok = false;
return sendok;
}
if (size_ > len)
{
opSuccess = WinHttpReadData(hRequest, response, len, &btsRead);
}
else
{
opSuccess = WinHttpReadData(hRequest, response, size_, &btsRead);
}
if (!opSuccess) {
sendok = false;
return sendok;
}
if (hSession) WinHttpCloseHandle(hSession);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hRequest) WinHttpCloseHandle(hRequest);
return sendok;
}
std::wstring & CWinHttpPost::BuildContentLength(unsigned long length, std::wstring &lengthstr)
{
lengthstr.append(L"Content-Length:").append(std::to_wstring(length));
return lengthstr;
}
unsigned long CWinHttpPost::BuildPostEntity(char * filebuf, unsigned long filesize, const char * filename, char ** entity)
{
if (filebuf == 0 || filesize == 0 || filename == 0 || strlen(filename) == 0) {
return 0;
}
std::string file_param;
std::string file_param_key("file");
std::string file_param_value(filename);
BuildFileParamString(file_param_key, file_param_value, file_param);
std::string filename_param;
std::string filename_param_key("filename");
std::string filename_param_value(filename);
BuildOtherParamString(filename_param_key, filename_param_value, filename_param);
std::string endmime;
BuildEndBoundaryString(endmime);
unsigned long totallen = file_param.length() + filesize + filename_param.length() + endmime.length();
*entity = new char[totallen];
ZeroMemory(*entity, totallen);
unsigned long offset=0;
memcpy_s(*entity, totallen, file_param.c_str(), file_param.length());
offset += file_param.length();
memcpy_s(*entity + offset, totallen, filebuf, filesize);
offset += filesize;
memcpy_s(*entity + offset, totallen, filename_param.c_str(), filename_param.length());
offset += filename_param.length();
memcpy_s(*entity + offset, totallen, endmime.c_str(), endmime.length());
return totallen;
}
代码是编译没有问题的。。但是没有具体的测试了。