------------------------------------------------------------------
大家好,我是許飛,微軟拼音的開發(fā)實習(xí)生。在網(wǎng)絡(luò)編程中,經(jīng)常用到從服務(wù)器上“下載”一些數(shù)據(jù),有時卻要向服務(wù)器“上傳”數(shù)據(jù)。曾在一個原型中使用了“multipart/form-data”格式向Web服務(wù)器上傳文件,這里和大家分享。
------------------------------------------------------------------
有時,在網(wǎng)絡(luò)編程過程中需要向服務(wù)器上傳文件。Multipart/form-data是上傳文件的一種方式。
Multipart/form-data其實就是瀏覽器用表單上傳文件的方式。最常見的情境是:在寫郵件時,向郵件后添加附件,附件通常使用表單添加,也就是用multipart/form-data格式上傳到服務(wù)器。
表單形式上傳附件
具體的步驟是怎樣的呢?
首先,客戶端和服務(wù)器建立連接(TCP協(xié)議)。
第二,客戶端可以向服務(wù)器端發(fā)送數(shù)據(jù)。因為上傳文件實質(zhì)上也是向服務(wù)器端發(fā)送請求。
第三,客戶端按照符合“multipart/form-data”的格式向服務(wù)器端發(fā)送數(shù)據(jù)。
Multipart/form-data的格式是怎樣的呢?
既然Multipart/form-data格式就是瀏覽器用表單提交數(shù)據(jù)的格式,我們就來看看文件經(jīng)過瀏覽器編碼后是什么樣子。
HTML表單
瀏覽器打開的表單
點擊“Browse…”分別選擇“unknow.gif”和“unknow1.gif”文件,點擊“submit”按紐后,文件將被上傳到服務(wù)器。
下面是服務(wù)器收到的數(shù)據(jù):
服務(wù)器收到的數(shù)據(jù)
這是一個POST請求。所以數(shù)據(jù)是放在請求體內(nèi),而不是請求頭內(nèi)。
這行指出這個請求是“multipart/form-data”格式的,且“boundary”是 “---------------------------7db15a14291cce”這個字符串。
不難想象,“boundary”是用來隔開表單中不同部分?jǐn)?shù)據(jù)的。例子中的表單就有 2 部分?jǐn)?shù)據(jù),用“boundary”隔開。“boundary”一般由系統(tǒng)隨機產(chǎn)生,但也可以簡單的用“-------------”來代替。
實際上,每部分?jǐn)?shù)據(jù)的開頭都是由"--" + boundary開始,而不是由 boundary 開始。仔細(xì)看才能發(fā)現(xiàn)下面的開頭這段字符串實際上要比 boundary 多了個 “--”
緊接著 boundary 的是該部分?jǐn)?shù)據(jù)的描述。
接下來才是數(shù)據(jù)。
“GIF”gif格式圖片的文件頭,可見,unknow1.gif確實是gif格式圖片。
在請求的最后,則是 "--" + boundary + "--" 表明表單的結(jié)束。
需要注意的是,在html協(xié)議中,用 “\r\n” 換行,而不是 “\n”。
下面的代碼片斷演示如何構(gòu)造multipart/form-data格式數(shù)據(jù),并上傳圖片到服務(wù)器。
view plaincopy to clipboardprint?//---------------------------------------
// this is the demo code of using multipart/form-data to upload text and photos.
// -use WinInet APIs.
//
//
// connection handlers.
//
HRESULT hr;
HINTERNET m_hOpen;
HINTERNET m_hConnect;
HINTERNET m_hRequest;
//
// make connection.
//
...
//
// form the content.
//
std::wstring strBoundary = std::wstring(L"------------------");
std::wstring wstrHeader(L"Content-Type: multipart/form-data, boundary=");
wstrHeader += strBoundary;
HttpAddRequestHeaders(m_hRequest, wstrHeader.c_str(), DWORD(wstrHeader.size()), HTTP_ADDREQ_FLAG_ADD);
//
// "std::wstring strPhotoPath" is the name of photo to upload.
//
//
// uploaded photo form-part begin.
//
std::wstring strMultipartFirst(L"--");
strMultipartFirst += strBoundary;
strMultipartFirst += L"\r\nContent-Disposition: form-data; name=\"pic\"; filename=";
strMultipartFirst += L"\"" + strPhotoPath + L"\"";
strMultipartFirst += L"\r\nContent-Type: image/jpeg\r\n\r\n";
//
// "std::wstring strTextContent" is the text to uploaded.
//
//
// uploaded text form-part begin.
//
std::wstring strMultipartInter(L"\r\n--");
strMultipartInter += strBoundary;
strMultipartInter += L"\r\nContent-Disposition: form-data; name=\"status\"\r\n\r\n";
std::wstring wstrPostDataUrlEncode(CEncodeTool::Encode_Url(strTextContent));
// add text content to send.
strMultipartInter += wstrPostDataUrlEncode;
std::wstring strMultipartEnd(L"\r\n--");
strMultipartEnd += strBoundary;
strMultipartEnd += L"--\r\n";
//
// open photo file.
//
// ws2s(std::wstring)
// -transform "strPhotopath" from unicode to ansi.
std::ifstream *pstdofsPicInput = new std::ifstream;
pstdofsPicInput->open((ws2s(strPhotoPath)).c_str(), std::ios::binary|std::ios::in);
pstdofsPicInput->seekg(0, std::ios::end);
int nFileSize = pstdofsPicInput->tellg();
if(nPicFileLen == 0)
{
return E_ACCESSDENIED;
}
char *pchPicFileBuf = NULL;
try
{
pchPicFileBuf = new char[nPicFileLen];
}
catch(std::bad_alloc)
{
hr = E_FAIL;
}
if(FAILED(hr))
{
return hr;
}
pstdofsPicInput->seekg(0, std::ios::beg);
pstdofsPicInput->read(pchPicFileBuf, nPicFileLen);
if(pstdofsPicInput->bad())
{
pstdofsPicInput->close();
hr = E_FAIL;
}
delete pstdofsPicInput;
if(FAILED(hr))
{
return hr;
}
// Calculate the length of data to send.
std::string straMultipartFirst = CEncodeTool::ws2s(strMultipartFirst);
std::string straMultipartInter = CEncodeTool::ws2s(strMultipartInter);
std::string straMultipartEnd = CEncodeTool::ws2s(strMultipartEnd);
int cSendBufLen = straMultipartFirst.size() + nPicFileLen + straMultipartInter.size() + straMultipartEnd.size();
// Allocate the buffer to temporary store the data to send.
PCHAR pchSendBuf = new CHAR[cSendBufLen];
memcpy(pchSendBuf, straMultipartFirst.c_str(), straMultipartFirst.size());
memcpy(pchSendBuf + straMultipartFirst.size(), (const char *)pchPicFileBuf, nPicFileLen);
memcpy(pchSendBuf + straMultipartFirst.size() + nPicFileLen, straMultipartInter.c_str(), straMultipartInter.size());
memcpy(pchSendBuf + straMultipartFirst.size() + nPicFileLen + straMultipartInter.size(), straMultipartEnd.c_str(), straMultipartEnd.size());
//
// send the request data.
//
HttpSendRequest(m_hRequest, NULL, 0, (LPVOID)pchSendBuf, cSendBufLen)