windows學習記錄之MFC通過URL上傳下載檔案
**
HTTP四種常見的POST提交資料方式:
**
HTTP/1.1 協議規定的 HTTP 請求方法有 OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 這幾種。其中 POST 一般用來向服務端提交資料,本文主要討論 POST 提交資料的幾種方式。
我們知道,HTTP 協議是以 ASCII 碼傳輸,建立在 TCP/IP 協議之上的應用層規範。規範把 HTTP 請求分為三個部分:狀態行、請求頭、訊息主體。類似於下面這樣:
<method> <request-url> <version>
<headers >
<entity-body></entity-body></headers></version></request-url></method>
協議規定 POST 提交的資料必須放在訊息主體(entity-body)中,但協議並沒有規定資料必須使用什麼編碼方式。實際上,開發者完全可以自己決定訊息主體的格式,只要最後傳送的 HTTP 請求滿足上面的格式就可以。
但是,資料傳送出去,還要服務端解析成功才有意義。一般服務端語言如 php、python 等,以及它們的 framework,都內建了自動解析常見資料格式的功能。服務端通常是根據請求頭(headers)中的 Content-Type 欄位來獲知請求中的訊息主體是用何種方式編碼,再對主體進行解析。所以說到 POST 提交資料方案,包含了 Content-Type 和訊息主體編碼方式兩部分。下面就正式開始介紹它們。
application/x-www-form-urlencoded:
這應該是最常見的 POST 提交資料的方式了。瀏覽器的原生 form 表單,如果不設定 enctype 屬性,那麼最終就會以 application/x-www-form-urlencoded 方式提交資料。請求類似於下面這樣(無關的請求頭在本文中都省略掉了):
POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3
首先,Content-Type 被指定為 application/x-www-form-urlencoded;其次,提交的資料按照 key1=val1&key2=val2 的方式進行編碼,key 和 val 都進行了 URL 轉碼。大部分服務端語言都對這種方式有很好的支援。例如 PHP 中,
很多時候,我們用 Ajax 提交資料時,也是使用這種方式。例如 JQuery 和 QWrap 的 Ajax,Content-Type 預設值都是「application/x-www-form-urlencoded;charset=utf-8」。
multipart/form-data:
這又是一個常見的 POST 資料提交的方式。我們使用表單上傳檔案時,必須讓 form 的 enctyped 等於這個值。直接來看一個請求示例:
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=—-WebKitFormBoundaryrGKCBY7qhFd3TrwA
——WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name=”text”
title
——WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name=”file”; filename=”chrome.png”
Content-Type: image/png
PNG … content of chrome.png …
——WebKitFormBoundaryrGKCBY7qhFd3TrwA–
這個例子稍微複雜點。首先生成了一個 boundary 用於分割不同的欄位,為了避免與正文內容重複,boundary 很長很複雜。然後 Content-Type 裡指明瞭資料是以 mutipart/form-data 來編碼,本次請求的 boundary 是什麼內容。訊息主體裡按照欄位個數又分為多個結構類似的部分,每部分都是以 –boundary 開始,緊接著內容描述資訊,然後是回車,最後是欄位具體內容(文字或二進位制)。如果傳輸的是檔案,還要包含檔名和檔案型別資訊。訊息主體最後以 –boundary– 標示結束。關於 mutipart/form-data 的詳細定義,請前往 rfc1867 檢視。
這種方式一般用來上傳檔案,各大服務端語言對它也有著良好的支援。
上面提到的這兩種 POST 資料的方式,都是瀏覽器原生支援的,而且現階段原生 form 表單也只支援這兩種方式。但是隨著越來越多的 Web 站點,尤其是 WebApp,全部使用 Ajax 進行資料互動之後,我們完全可以定義新的資料提交方式,給開發帶來更多便利。
application/json
application/json 這個 Content-Type 作為響應頭大家肯定不陌生。實際上,現在越來越多的人把它作為請求頭,用來告訴服務端訊息主體是序列化後的 JSON 字串。由於 JSON 規範的流行,除了低版本 IE 之外的各大瀏覽器都原生支援 JSON.stringify,服務端語言也都有處理 JSON 的函式,使用 JSON 不會遇上什麼麻煩。
JSON 格式支援比鍵值對複雜得多的結構化資料,這一點也很有用。記得我幾年前做一個專案時,需要提交的資料層次非常深,我就是把資料 JSON 序列化之後來提交的。不過當時我是把 JSON 字串作為 val,仍然放在鍵值對裡,以 x-www-form-urlencoded 方式提交。
Google 的 AngularJS 中的 Ajax 功能,預設就是提交 JSON 字串。例如下面這段程式碼:
var data = {‘title’:’test’, ‘sub’ : [1,2,3]};
$http.post(url, data).success(function(result) {
…
});
最終傳送的請求是:
POST http://www.example.com HTTP/1.1
Content-Type: application/json;charset=utf-8
{“title”:”test”,”sub”:[1,2,3]}
這種方案,可以方便的提交複雜的結構化資料,特別適合 RESTful 的介面。各大抓包工具如 Chrome 自帶的開發者工具、Firebug、Fiddler,都會以樹形結構展示 JSON 資料,非常友好。但也有些服務端語言還沒有支援這種方式,例如 php 就無法通過 $_POST 物件從上面的請求中獲得內容。這時候,需要自己動手處理下:在請求頭中 Content-Type 為 application/json 時,從 php://input 裡獲得原始輸入流,再 json_decode 成物件。一些 php 框架已經開始這麼做了。
當然 AngularJS 也可以配置為使用 x-www-form-urlencoded 方式提交資料。如有需要,可以參考這篇文章。
text/xml
我之前提到過 XML-RPC(XML Remote Procedure Call)。它是一種使用 HTTP 作為傳輸協議,XML 作為編碼方式的遠端呼叫規範。典型的 XML-RPC 請求是這樣的:
POST http://www.example.com HTTP/1.1
Content-Type: text/xml
examples.getStateName
41
</params>
XML-RPC 協議簡單、功能夠用,各種語言的實現都有。它的使用也很廣泛,如 WordPress 的 XML-RPC Api,搜尋引擎的 ping 服務等等。JavaScript 中,也有現成的庫支援以這種方式進行資料互動,能很好的支援已有的 XML-RPC 服務。不過,我個人覺得 XML 結構還是過於臃腫,一般場景用 JSON 會更靈活方便。
**
MFC下載上傳檔案
**
我們知道,windows有關網路連線的API在wininet.h裡,而在MFC裡,這些API被封裝成了類。
CInternetSession類:直接繼承自CObject類,該類用來建立與某個Internet伺服器的會話
CInternetConnection類:幫助使用者管理與Internet伺服器的連線,同時還提供一些函式完成和響應伺服器的通訊
CInternetConnection類又派生出三個類:
CHttpConnection類:管理與HTTP伺服器的連線
CFtpConnection類:用於管理與FTP伺服器的連線
CGopherConnection類:管理與Gopher伺服器的連線
我們要實現根據URL下載並儲存檔案,就需要CInternetSession、CHttpConnection、CHttpFile。
CHttpFile用來做什麼呢?它可以關聯起URL指向的一個WEB物件,由於CHttpFile繼承與CFile,我們可以像讀取一個CFile檔案一樣,把它從HTTP伺服器上下載下來。
MFC就是通過對CHttpFile物件的讀寫來完成與HTTP伺服器的對話的,包括GET/POST提交資料,接收資料。
下面就是我自己寫的一個根據URL下載並儲存檔案的函式,注意使用時需要在MFC專案的預編譯標頭檔案里加入#include <afxinet.h>
,因為上面我提到的那些類全部在這個標頭檔案裡。
第一引數為URL,第二個引數為儲存路徑。
返回值:
0:成功下載儲存
-1:URL不正確
-2:建立網路連線失敗
-3:向伺服器發起GET請求失敗
-4:伺服器不接受請求
-5:引數一不能為空
-6:引數二不能為空
-7:儲存檔案時建立或寫入檔案失敗
程式碼如下:
.h檔案:
#ifndef CHTTPIMAGE_H
#define CHTTPIMAGE_H
#include <WinInet.h>
#include <afxinet.h>
class CHttpImage
{
public:
CHttpImage();
~CHttpImage();
bool HttpUpLoading(const char *phostName,const char *postData,int iLen,char *pResult,int *iResultLen);//上傳檔案
bool HttpDownload(const char *phostName,const char *pFileName);//下載檔案
bool HttpGetLogoURL(const char *phostName,const char *postData,int iLen,char *pResult,int *iResultLen);//獲取玩家影象URL地址
};
#endif
.cpp檔案:
#include "StdAfx.h"
#include "CHttpImage.h"
CHttpImage::CHttpImage()
{
}
CHttpImage::~CHttpImage()
{
}
bool CHttpImage::HttpUpLoading(const char *phostName,const char *postData,int iLen,char *pResult,int *iResultLen)//上傳檔案
{
unsigned short nPort; //用於儲存目標HTTP服務埠
CString strServer, strObject; //strServer用於儲存伺服器地址,strObject用於儲存檔案物件名稱
DWORD dwServiceType; //dwServiceType用於儲存服務型別,dwRet用於儲存提交GET請求返回的狀態號
//解析URL,獲取資訊
if(!AfxParseURL(phostName, dwServiceType, strServer, strObject, nPort))
{
//解析失敗,該Url不正確
return false;
}
CInternetSession session;
DWORD dwRet = 0;
//建立網路連線
CHttpConnection *pHtCon = session.GetHttpConnection(strServer,nPort);
if(pHtCon == NULL)
{
//建立網路連線失敗
session.Close();
return false;
}
//發起GET請求
CHttpFile* pFile=pHtCon->OpenRequest(CHttpConnection::HTTP_VERB_GET,strObject);
if(pFile == NULL)
{
//發起GET請求失敗
session.Close();
delete pHtCon;
pHtCon = NULL;
return false;
}
CString strHeaders = "Content-Type:application/x-www-form-urlencoded;charset=gbk"; // 請求頭
//開始傳送請求
pFile->SendRequest(strHeaders,strHeaders.GetLength(),(LPVOID)postData,iLen);
pFile->QueryInfoStatusCode(dwRet);
int index=0;
if (dwRet == HTTP_STATUS_OK)
{
CString newline;
while(pFile->ReadString(newline))
{//迴圈讀取每行內容
memcpy(pResult+index,newline.GetBuffer(),sizeof(char)*newline.GetLength());
index+=newline.GetLength();
}
}
else
{
session.Close();
if(pHtCon)
{
delete pHtCon;
pHtCon=NULL;
}
if(pFile)
{
delete pFile;
pFile=NULL;
}
return false;
}
*iResultLen=index;
session.Close();
if(pHtCon)
{
delete pHtCon;
pHtCon=NULL;
}
if(pFile)
{
delete pFile;
pFile=NULL;
}
std::string _cs(pResult);
if(-1!=_cs.find(std::string("status"),0))
{
return false;
}
return true;
}
bool CHttpImage::HttpDownload(const char *phostName,const char *pFileName)//下載檔案
{
unsigned short nPort; //用於儲存目標HTTP服務埠
CString strServer, strObject; //strServer用於儲存伺服器地址,strObject用於儲存檔案物件名稱
DWORD dwServiceType,dwRet; //dwServiceType用於儲存服務型別,dwRet用於儲存提交GET請求返回的狀態號
//解析URL,獲取資訊
if(!AfxParseURL(phostName, dwServiceType, strServer, strObject, nPort))
{
//解析失敗,該Url不正確
return false;
}
//建立網路連線物件,HTTP連線物件指標和用於該連線的HttpFile檔案物件指標,注意delete
CInternetSession intsess;
CHttpFile *pHtFile = NULL;
//建立網路連線
CHttpConnection *pHtCon = intsess.GetHttpConnection(strServer,nPort);
if(pHtCon == NULL)
{
//建立網路連線失敗
intsess.Close();
return false;
}
//發起GET請求
pHtFile =pHtCon->OpenRequest(CHttpConnection::HTTP_VERB_GET,strObject);
if(pHtFile == NULL)
{
//發起GET請求失敗
intsess.Close();
delete pHtCon;
pHtCon = NULL;
return false;
}
//提交請求
pHtFile->SendRequest();
//獲取伺服器返回的狀態號
pHtFile->QueryInfoStatusCode(dwRet);
if (dwRet != HTTP_STATUS_OK)
{
//伺服器不接受請求
intsess.Close();
delete pHtCon;
pHtCon = NULL;
delete pHtFile;
pHtFile = NULL;
return false;
}
//獲取檔案大小
long long i64FileLen = pHtFile->GetLength();
int iRead = 0; //用於標識讀了多少,為1是為了進入迴圈
int iWriteSize=1024*1024;//1M
//建立緩衝區
char *szBuffer = new char[iWriteSize];
try
{
HANDLE hWriteFileHandle=::CreateFile(pFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(INVALID_HANDLE_VALUE==hWriteFileHandle)
{
return false;
}
unsigned long iReadNums=0;
unsigned long iWriteNums=0;
do
{
//清空緩衝區
memset(szBuffer,0,sizeof(char)*(iWriteSize));
//讀取到緩衝區
iRead = pHtFile->Read(szBuffer,iWriteSize);
::FlushFileBuffers(hWriteFileHandle);
bool bWriteSuccess=WriteFile(hWriteFileHandle,szBuffer,iRead,&iWriteNums,NULL);
if(!bWriteSuccess)
{
break;
}
}while(iRead>0);
CloseHandle(hWriteFileHandle);
//釋放記憶體
delete[] szBuffer;
delete pHtFile;
pHtFile=NULL;
delete pHtCon;
pHtCon=NULL;
//關閉網路連線
intsess.Close();
return true;
}
catch(CFileException e)
{
//釋放記憶體
delete []szBuffer;
delete pHtFile;
pHtFile=NULL;
delete pHtCon;
pHtCon=NULL;
//關閉網路連線
intsess.Close();
return false; //讀寫檔案異常
}
return false;
}
bool CHttpImage::HttpGetLogoURL(const char *phostName,const char *postData,int iLen,char *pResult,int *iResultLen)//獲取玩家影象URL地址
{
unsigned short nPort; //用於儲存目標HTTP服務埠
CString strServer, strObject; //strServer用於儲存伺服器地址,strObject用於儲存檔案物件名稱
DWORD dwServiceType; //dwServiceType用於儲存服務型別,dwRet用於儲存提交GET請求返回的狀態號
//解析URL,獲取資訊
if(!AfxParseURL(phostName, dwServiceType, strServer, strObject, nPort))
{
//解析失敗,該Url不正確
return false;
}
CInternetSession session;
DWORD dwRet = 0;
CHttpFile* pFile =(CHttpFile *)session.OpenURL(phostName);
CString strHeaders = "Content-Type:application/x-www-form-urlencoded;charset=gbk"; // 請求頭
//開始傳送請求
pFile->SendRequest(strHeaders,strHeaders.GetLength(),(LPVOID)postData,iLen);
pFile->QueryInfoStatusCode(dwRet);
int index=0;
if (dwRet == HTTP_STATUS_OK)
{
CString newline;
while(pFile->ReadString(newline))
{//迴圈讀取每行內容
memcpy(pResult+index,newline.GetBuffer(),sizeof(char)*newline.GetLength());
index+=newline.GetLength();
}
}
else
{
session.Close();
if(pFile)
{
delete pFile;
pFile=NULL;
}
return false;
}
*iResultLen=index;
session.Close();
if(pFile)
{
delete pFile;
pFile=NULL;
}
std::string _cs(pResult);
if(-1!=_cs.find(std::string("status"),0))
{
return false;
}
return true;
}