1. 程式人生 > >一個簡單的WinHttp上傳檔案的類

一個簡單的WinHttp上傳檔案的類

這幾天用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 &paramstr);
    std::string &BuildOtherParamString(std::string &key, std::string &value, std::string &paramstr);
    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;
}

程式碼是編譯沒有問題的。。但是沒有具體的測試了。