最近做的程序需要用到HTTP上传文件和表单提交的功能,在各大网站这方面的资料实在是少的可怜。找了好久,最后在国外的一家网站找到了相关的文档。做完工程后总结,开始的主要问题出在报头的格式上。现将报头的写法和相关的函数摘出来。程序的实现是用CHTTP类。
//报头格式,类似于这样--------------------------------------
LPCSTR szDefUsrAgent = "Ryeol HTTP Client Class" ;
LPCSTR szGET = "GET" ;
LPCSTR szPost = "POST" ;
LPCSTR szHTTP = "HTTP://" ;
LPCSTR szHTTPS = "HTTPS://" ;
LPCSTR szSlash = "/" ;
LPCSTR szCacheControl = "Cache-Control" ;
LPCSTR szNoCache = "no-cache" ;
LPCSTR szContentType = "Content-Type" ;
LPCSTR szMultipartFormDataBoundary = "multipart/form-data; boundary=" ;
LPCSTR szFormUrlEncoded = "application/x-www-form-urlencoded" ;
LPCSTR szDefBoundary = "----FB3B405B7EAE495aB0C0295C54D4E096-" ; //这行分隔符很重要的,开始的程序就是没加这个,一直不行。
LPCSTR szDefUploadContType = "multipart/form-data; boundary=" "----FB3B405B7EAE495aB0C0295C54D4E096-" ;
LPCSTR szNULL = "NULL" ;
LPCSTR szEmptyString = "" ;
LPCSTR szColonSlashSlash = "://" ;
下面是提出来的一段函数代码:
void CHttpToolA::AddHeader (HINTERNET hRequest, LPCSTR szName, LPCSTR szValue, UINT /* CodePage */)
throw (Exception &)
{
HTTPTOOL_ASSERT (hRequest != NULL, "CHttpToolA::AddHeader: hRequest can not be NULL.") ;
HTTPTOOL_ASSERT (szName != NULL, "CHttpToolA::AddHeader: szName can not be NULL.") ;
::SafeInt<size_t> cbHeader ;
::SafeInt<DWORD> cchHeader ;
try {
cbHeader = ::strlen (szName) ;
cbHeader += szValue ? ::strlen (szValue) : 0 ;
cbHeader += (4 + 1) ; // for ": ", "/r/n", '/0'
cchHeader = cbHeader - 1 ;
} catch (::SafeIntException & e) {
ThrowException (e) ;
}
PSTR szHeader = (PSTR) ::malloc (sizeof (CHAR) * (cbHeader.Value ())) ;
if ( szHeader == NULL )
ThrowException (HTTPCLIENT_ERR_OUT_OF_MEMORY) ;
::strcpy (szHeader, szName) ;
::strcat (szHeader, ": ") ;
::strcat (szHeader, szValue ? szValue : "") ;
::strcat (szHeader, "/r/n") ;
// Adds a header
if ( !::HttpAddRequestHeadersA (
hRequest
, szHeader // headers to append to the request.
, cchHeader.Value () // header length
, HTTP_ADDREQ_FLAG_ADD // flags
)
) {
SAFEFREE (szHeader) ;
ThrowException (HTTPCLIENT_ERR_HTTPADDREQUESTHEADERS_FAILED, ::GetLastError ()) ;
}
SAFEFREE (szHeader) ;
}
void CHttpToolA::SendRequest (HINTERNET hRequest, LPCSTR szPosted, UINT /* CodePage */)
throw (Exception &)
{
HTTPTOOL_ASSERT (hRequest != NULL, "CHttpToolA::SendRequest: hRequest can not be NULL.") ;
::SafeInt<DWORD> cchPosted ;
try {
cchPosted = szPosted ? ::strlen (szPosted) : 0 ;
} catch (::SafeIntException & e) {
ThrowException (e) ;
}
if ( !::HttpSendRequestA (
hRequest
, NULL // Additional header
, 0 // The length of the additional header
, (void *) szPosted // A posted data
, cchPosted.Value () // The length of the posted data
) )
ThrowException (HTTPCLIENT_ERR_HTTPSENDREQUEST_FAILED, ::GetLastError ()) ;
}
void CHttpToolA::SendRequestEx (HINTERNET hRequest, DWORD dwPostedSize)
throw (Exception &)
{
HTTPTOOL_ASSERT (hRequest != NULL, "CHttpToolA::SendRequestEx: hRequest can not be NULL.") ;
INTERNET_BUFFERSA BufferIn;
BufferIn.dwStructSize = sizeof( INTERNET_BUFFERS ); // Must be set or error will occur
BufferIn.Next = NULL;
BufferIn.lpcszHeader = NULL;
BufferIn.dwHeadersLength = 0;
BufferIn.dwHeadersTotal = 0;
BufferIn.lpvBuffer = NULL;
BufferIn.dwBufferLength = 0;
BufferIn.dwBufferTotal = dwPostedSize; // This is the only member used other than dwStructSize
BufferIn.dwOffsetLow = 0;
BufferIn.dwOffsetHigh = 0;
if ( !::HttpSendRequestExA (
hRequest
, &BufferIn
, NULL
, 0
, 0
) )
ThrowException (HTTPCLIENT_ERR_HTTPSENDREQUESTEX_FAILED, ::GetLastError ()) ;
}
// The returned string must be freed by using the ::free () function.
LPSTR CHttpToolA::CreateUploadBoundary (void)
throw ()
{
GUID guid ;
if ( FAILED ( ::CoCreateGuid (&guid)) )
return NULL ;
PSTR szBoundary = (PSTR) ::malloc (sizeof (CHAR) * 44) ;
if ( szBoundary == NULL )
return NULL ;
::sprintf (szBoundary, "----LYOUL-%.08x%.04x%.04x%.02x%.02x%.02x%.02x%.02x%.02x%.02x%.02x-"
, guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1]
, guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
return szBoundary ;
}
void CHttpClientT<HttpTool, HttpEncoder>::_StartUploadContext (HINTERNET hInternet, HINTERNET hConnection, PCSZ szUrl
, DWORD dwFlags, PCSZ szReferer, PCSZ szUsrName, PCSZ szUsrPwd)
throw (Exception &)
{
// Closes any existing Post Context
_EndPostContext () ;
BOOL bBorrowedInternet = TRUE ;
BOOL bBorrowedConnection = TRUE ;
HINTERNET hRequest = NULL ;
HANDLE * ahFileHandles = NULL ;
LPSTR * aszMimeTypes = NULL ;
::SafeInt<DWORD> nPostedFileCount = 0 ;
try {
// Calculates the nubmer of bytes to upload
::SafeInt<size_t> nTotalByte = 0 ;
::SafeInt<size_t> nValuesTotalByte = 0 ;
size_t cchBoundary = HttpTool::StringLen (_GetUploadBoundary ()) ;
PCSZ szFirstParamName = NULL ;
PCSZ szFirstParamFileName = NULL ;
size_t cbFirstParamValue = 0 ;
BOOL bFirstParamIsFile = FALSE ;
if ( !m_mapParam.Empty () ) {
try {
ConstMapIter iter ;
// Get the number of files
for (iter = m_mapParam.Begin (); iter != m_mapParam.End (); ++iter) {
if ( (iter->second).dwFlag & ParamFile )
nPostedFileCount++ ;
}
if ( nPostedFileCount.Value () ) {
// Allocates memory for handles and MimeTypes of the uploaded files
ahFileHandles = (HANDLE *) ::calloc (nPostedFileCount.Value (), sizeof (HANDLE)) ;
if ( ahFileHandles == NULL )
HttpTool::ThrowException (HTTPCLIENT_ERR_OUT_OF_MEMORY) ;
// Initializes the file handles
for (DWORD i = 0; i < nPostedFileCount; i++)
ahFileHandles[i] = INVALID_HANDLE_VALUE ;
aszMimeTypes = (LPSTR *) ::calloc (nPostedFileCount.Value (), sizeof (LPSTR)) ;
if ( aszMimeTypes == NULL )
HttpTool::ThrowException (HTTPCLIENT_ERR_OUT_OF_MEMORY) ;
}
/*
Calculates the total upload size
<1> The number of bytes to upload If the parameter is not a file
= strlen ("--") + strlen (Boundary) + strlen ("/r/n")
+ strlen ("Content-Disposition: form-data; name=/"/"/r/n/r/n")
+ strlen (The name of the parameter) + strlen (The value of the parameter) + strlen ("/r/n") ;
<2> The number of bytes to upload If the parameter is a file
= strlen ("--") + strlen (Boundary) + strlen ("/r/n")
+ strlen ("Content-Disposition: form-data; name=/"/"; filename=/"/"/r/n")
+ strlen (The name of the parameter) + strlen (The value of the parameter)
+ strlen ("Content-Type: /r/n/r/n") + strlen (The Mime Type of the file)
+ The length of the file + strlen ("/r/n")
The last boundary
= strlen ("--") + strlen (Boundary) + strlen ("--/r/n")
The total upload size
= ( <1> * The number of normal parameters)
+ ( <2> * The number of file parameters)
+ The last boundary
*/
::SafeInt<size_t> cbValue ;
DWORD nFileIdx = 0 ;
nTotalByte = 0 ;
nValuesTotalByte = 0 ;
for (iter = m_mapParam.Begin (); iter != m_mapParam.End (); ++iter) {
if ( !((iter->second).dwFlag & ParamFile) ) {
// If the parameter is not a file
// = strlen ("--") + strlen (Boundary) + strlen ("/r/n")
// + strlen ("Content-Disposition: form-data; name=/"/"/r/n/r/n")
// + strlen (The name of the parameter) + strlen (The value of the parameter) + strlen ("/r/n") ;
nTotalByte += (cchBoundary + 4) ;
nTotalByte += 43 ;
if ( m_bUseUtf8 ) {
nTotalByte += _Utf8EncodeLen (iter->first) ;
cbValue = _Utf8EncodeLen ((iter->second).szValue) ;
} else {
nTotalByte += _String2AnsiLen (iter->first) ;
cbValue = _String2AnsiLen ((iter->second).szValue) ;
}
nTotalByte += cbValue ;
nValuesTotalByte += cbValue ;
nTotalByte += 2 ;
// Saves the state of the first parameter
if ( iter == m_mapParam.Begin () ) {
szFirstParamName = iter->first ;
cbFirstParamValue = cbValue.Value () ;
bFirstParamIsFile = FALSE ;
szFirstParamFileName = NULL ;
}
} else {
// If the parameter is a file
// = strlen ("--") + strlen (Boundary) + strlen ("/r/n")
// + strlen ("Content-Disposition: form-data; name=/"/"; filename=/"/"/r/n")
// + strlen (The name of the parameter) + strlen (The value of the parameter)
// + strlen ("Content-Type: /r/n/r/n") + strlen (The Mime Type of the file)
// + The length of the file + strlen ("/r/n")
nTotalByte += (cchBoundary + 4) ;
nTotalByte += 54 ;
if ( m_bUseUtf8 ) {
nTotalByte += _Utf8EncodeLen (iter->first) ;
nTotalByte += _Utf8EncodeLen ((iter->second).szValue) ;
} else {
nTotalByte += _String2AnsiLen (iter->first) ;
nTotalByte += _String2AnsiLen ((iter->second).szValue) ;
}
nTotalByte += 18 ;
// Get the file size and MimeType
cbValue = 0 ;
if ( (iter->second).szValue ) {
// Open the file
ahFileHandles[nFileIdx] = HttpTool::OpenFile ((iter->second).szValue) ;
// Get the file size
if ( ahFileHandles[nFileIdx] != INVALID_HANDLE_VALUE )
cbValue = HttpTool::GetFileSize (ahFileHandles[nFileIdx], (iter->second).szValue) ;
}
// Throws an exception
if ( m_bStrictFileCheck && (ahFileHandles[nFileIdx] == INVALID_HANDLE_VALUE) )
HttpTool::ThrowException (HTTPCLIENT_ERR_OPENFILE_FAILED, ::GetLastError (), (iter->second).szValue) ;
// Get the MimeType of the file
aszMimeTypes[nFileIdx] = HttpTool::GetMimeType (ahFileHandles[nFileIdx], m_nAnsiCodePage) ;
nTotalByte += CHttpToolA::StringLen (aszMimeTypes[nFileIdx]) ;
nTotalByte += cbValue ;
nValuesTotalByte += cbValue ;
nTotalByte += 2 ;
nFileIdx++ ;
// Saves the state of the first parameter
if ( iter == m_mapParam.Begin () ) {
szFirstParamName = iter->first ;
cbFirstParamValue = cbValue.Value () ;
bFirstParamIsFile = TRUE ;
szFirstParamFileName = (iter->second).szValue ;
}
}
}
// The last boundary
// = strlen ("--") + strlen (Boundary) + strlen ("--/r/n")
nTotalByte += (cchBoundary + 6) ;
} catch (::SafeIntException & e) {
HttpTool::ThrowException (e) ;
}
} else {
// The total upload size
// = (strlen ("/r/n") + strlen ("--") + strlen (Boundary) + strlen ("--/r/n")
nTotalByte = cchBoundary + 8 ;
nValuesTotalByte = 0 ;
}
::SafeInt<DWORD> dwTotalByte ;
try {
dwTotalByte = nTotalByte ;
} catch (::SafeIntException & e) {
HttpTool::ThrowException (e) ;
}
// Get WinInet handles
if ( hInternet == NULL ) {
hInternet = OpenInternet () ;
bBorrowedInternet = FALSE ;
}
if ( hConnection == NULL ) {
hConnection = OpenConnection (hInternet, szUrl, szUsrName, szUsrPwd) ;
bBorrowedConnection = FALSE ;
}
hRequest = OpenRequest (hConnection, HttpTool::szPost, szUrl, dwFlags, szReferer) ;
AddRequestHeader (hRequest) ; // Adds user's custom header
// Adds the Content-Type header
HttpTool::AddHeader (hRequest, HttpTool::szContentType, _GetUploadContType (), m_nAnsiCodePage) ;
// Make a connection to the server
HttpTool::SendRequestEx (hRequest, dwTotalByte.Value ()) ;
// Activates the Post Context
m_objPostStat._MakeActive (nTotalByte.Value (), nValuesTotalByte.Value (), m_mapParam.Count (), nPostedFileCount.Value ()) ;
m_bBorrowedInternet = bBorrowedInternet ;
m_hInternet = hInternet ;
m_bBorrowedConnection = bBorrowedConnection ;
m_hConnection = hConnection ;
m_hRequest = hRequest ;
m_ahPostedFiles = ahFileHandles ;
ahFileHandles = NULL ;
m_aszMimeTypes = aszMimeTypes ;
aszMimeTypes = NULL ;
m_bIsPost = FALSE ;
// Saves the initial Post context
if ( !m_mapParam.Empty () ) {
m_objInitialStat = m_objPostStat ;
// It always does not throw an overflow exception.
// So it's safe. (doesn't need to restore the internal states)
m_objInitialStat._StartNewEntry (szFirstParamName, cbFirstParamValue
, bFirstParamIsFile, szFirstParamFileName) ;
}
} catch (Exception &) {
HttpTool::CloseRequest (hRequest) ;
if ( !bBorrowedConnection ) HttpTool::CloseRequest (hConnection) ;
if ( !bBorrowedInternet ) HttpTool::CloseRequest (hInternet) ;
for (DWORD i = 0; i < nPostedFileCount; i++) {
if ( ahFileHandles ) {
if ( ahFileHandles[i] != INVALID_HANDLE_VALUE ) {
::CloseHandle (ahFileHandles[i]) ;
ahFileHandles[i] = INVALID_HANDLE_VALUE ;
}
}
if ( aszMimeTypes )
SAFEFREE ( (aszMimeTypes[i]) ) ;
}
SAFEFREE (ahFileHandles) ;
SAFEFREE (aszMimeTypes) ;
throw ;
}
}
CHttpClientT<HttpTool, HttpEncoder>::_ProceedUploadContext (DWORD nDesired)
throw (Exception &)
{
// If the Post context is not started
if ( !m_objPostStat.IsActive () )
HttpTool::ThrowException (HTTPCLIENT_ERR_POST_NOT_STARTED) ;
HTTPCLIENT_ASSERT (m_hInternet != NULL, "CHttpClientT::_ProceedUploadContext: m_hInternet can not be NULL.") ;
HTTPCLIENT_ASSERT (m_hConnection != NULL, "CHttpClientT::_ProceedUploadContext: m_hConnection can not be NULL.") ;
HTTPCLIENT_ASSERT (m_hRequest != NULL, "CHttpClientT::_ProceedUploadContext: m_hRequest can not be NULL.") ;
HTTPCLIENT_ASSERT (nDesired != 0, "CHttpClientT::_ProceedUploadContext: nDesired can not be zero.") ;
try {
// If all parameters are posted
// releases the Post context
if ( m_objPostStat._IsComplete () ) {
HttpTool::EndRequest (m_hRequest) ;
return _ReleasePostResponse () ;
}
LPCSTR szBoundary = _GetUploadBoundaryA () ;
// If there is no parameter to upload
if ( m_objPostStat.TotalCount () == 0 ) {
// Writes the last boundary
_WritePost ("/r/n--") ;
_WritePost (szBoundary) ;
_WritePost ("--/r/n") ;
return NULL ;
}
// If the current parameter is completed
if ( m_objPostStat.CurrParamIsComplete () ) {
// If all parameters are sent
if ( m_objPostStat.TotalCount () == m_objPostStat.PostedCount () ) {
// Writes the last boundary
_WritePost ("--") ;
_WritePost (szBoundary) ;
_WritePost ("--/r/n") ;
return NULL ;
}
DWORD nNextIdx = m_objPostStat.PostedCount () ;
DWORD nNextFileIdx = m_objPostStat.PostedFileCount () ;
PCSZ szEntryFile = NULL ;
::SafeInt<size_t> nEntryValueTotalByte = 0 ;
ConstMapIter iter = m_mapParam.Begin () ;
for (size_t i = 0; i < nNextIdx; i++, ++iter) ;
if ( (iter->second).dwFlag & ParamFile ) {
// If the parameter is a file parameter
szEntryFile = (iter->second).szValue ;
if ( m_ahPostedFiles[nNextFileIdx] != INVALID_HANDLE_VALUE )
nEntryValueTotalByte = HttpTool::GetFileSize (m_ahPostedFiles[nNextFileIdx], szEntryFile) ;
// Writes the boundary and headers
_WritePost ("--") ;
_WritePost (szBoundary) ;
_WritePost ("/r/n") ;
_WritePost ("Content-Disposition: form-data; name=/"") ;
_WritePost (m_bUseUtf8, iter->first) ; // Writes form name
_WritePost ("/"; filename=/"") ;
_WritePost (m_bUseUtf8, szEntryFile) ; // Writes file path
_WritePost ("/"/r/nContent-Type: ") ;
_WritePost (m_aszMimeTypes[nNextFileIdx]) ;
_WritePost ("/r/n/r/n") ;
} else {
// If the parameter is not a file parameter
if ( (iter->second).szValue && (iter->second).szValue[0] != '/0' ) {
LPCSTR szPostedValue ;
BOOL bNeedToFree ;
if ( m_bUseUtf8 ) {
szPostedValue = _Utf8Encode ((iter->second).szValue) ;
bNeedToFree = TRUE ;
} else
// Converts into a Ansi string
szPostedValue = _String2Ansi ((iter->second).szValue, bNeedToFree) ;
nEntryValueTotalByte = CHttpToolA::StringLen (szPostedValue) ;
_SetPostedValue (szPostedValue, bNeedToFree) ;
} else {
_SetPostedValue (NULL, FALSE) ;
nEntryValueTotalByte = 0 ;
}
// Writes the boundary and headers
_WritePost ("--") ;
_WritePost (szBoundary) ;
_WritePost ("/r/n") ;
_WritePost ("Content-Disposition: form-data; name=/"") ;
_WritePost (m_bUseUtf8, iter->first) ; // Write form name
_WritePost ("/"/r/n/r/n") ;
}
// Starts a new parameter
m_objPostStat._StartNewEntry (iter->first, nEntryValueTotalByte.Value ()
, static_cast<BOOL> ((iter->second).dwFlag & ParamFile), szEntryFile) ;
if ( nEntryValueTotalByte == 0 )
_WritePost ("/r/n") ;
return NULL ;
}
// Writes the requested number of bytes
DWORD cbToWrite = nDesired ;
if ( cbToWrite > m_objPostStat.CurrParamRemainByte () )
cbToWrite = static_cast<DWORD> (m_objPostStat.CurrParamRemainByte ()) ;
DWORD cbWritten = 0 ;
if ( m_objPostStat.CurrParamIsFile () ) {
_InitPostCntxBuff () ;
DWORD cbBuff ; // The number of bytes to read
DWORD cbRead ; // The number of bytes read from the file
DWORD nFileIdx = m_objPostStat.PostedFileCount () - 1 ;
while ( cbWritten < cbToWrite ) {
cbBuff = cbToWrite - cbWritten >
HTTPCLIENT_POSTCNTX_BUFF_SIZE ? HTTPCLIENT_POSTCNTX_BUFF_SIZE : cbToWrite - cbWritten ;
// Read from file
if ( !::ReadFile (m_ahPostedFiles[nFileIdx]
, m_pbyCntxBuff
, cbBuff
, &cbRead
, NULL)
)
HttpTool::ThrowException (HTTPCLIENT_ERR_READFILE_FAILED, ::GetLastError ()) ;
// cbBuff and cbRead must be the same
if ( cbBuff != cbRead )
HttpTool::ThrowException (HTTPCLIENT_ERR_READ_UNEXPECTED_SIZE) ;
_WritePost (m_pbyCntxBuff, cbBuff, TRUE) ;
cbWritten += cbBuff ;
}
} else {
_WritePost (
reinterpret_cast<const BYTE *> (m_szPostedValue + m_objPostStat.CurrParamPostedByte ())
, cbToWrite, TRUE) ;
cbWritten = cbToWrite ;
}
// If all bytes are written, writes the last new line character
if ( m_objPostStat.CurrParamRemainByte () == 0 )
_WritePost ("/r/n") ;
} catch (::SafeIntException & e) {
_EndPostContext () ; // Aborts the POST context if an error occurs
HttpTool::ThrowException (e) ;
} catch (Exception &) {
_EndPostContext () ; // Aborts the POST context if an error occurs
throw ;
}
return NULL ;
}
这个类基本的实现就是这样,由于没时间,还没来的及去深入研究,等过几天试验一个简单的上传类再把代码发上来。
具体的代码大家可以去http://www.codeproject.com/去找。