遇到在http下载时,需要加Cookie的情况.
如果一个url要匹配一个Cookie才能下载时,可以用wireshark观察类似软件可以正常下载时的发送的http请求内容。
然后修正自己的实现,直到我们可以发送正确的http请求内容.
对于多任务多线程的http下载,确实比单线程http下载块多了.
任务做完了, 整理一个Demo记录一下~.
工程下载点 : srcHttpDownload.rar
测试程序:
/// @file srcHttpDownload.cpp
/// @brief 记录在http下载操作中,加入Cookie的实现
/// @note 当http请求失败时,看看wireshark的抓包结果,看看服务器返回的信息
/// 看看正确的http请求的发送内容,
/// 修改自己的实现,一直到发送出和样例软件一样的结果就ok了
#include "stdafx.h"
#include "http_request.h"
/// @note 有些http下载是要加Cookie的,Cookie不对,会被http服务器拒绝
/// 远程url, 需要下载的目标
#define DL_URL \
L"https://download-installer.cdn.mozilla.net/pub/" \
L"firefox/releases/31.0/win32/en-GB/Firefox%20Setup%2031.0.exe"
/// 需要加的Cookie
#define DL_COOKIE L"Cookie: my_cookie"
/// file save to
#define DL_FILE_SAVE_TO L"d:\\my_dl.dat"
DWORD DownloadUrl(const WCHAR* pcUrl, const WCHAR* pcCookie, const WCHAR* pcFileSaveTo);
int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwRc = S_FALSE;
dwRc = DownloadUrl(DL_URL, DL_COOKIE, DL_FILE_SAVE_TO);
_tprintf(L"download %s\r\n", (S_OK == dwRc) ? L"ok" : L"failed");
_tprintf(L"END, press any key to quit\r\n");
/** runresult
>> DownloadUrl(https://download-installer.cdn.mozilla.net/pub/firefox/releases/3
1.0/win32/en-GB/Firefox%20Setup%2031.0.exe, Cookie: my_cookie)
downloading...
...
downloading..., read back 32768/31948800 bytes
downloading..., read back 32768/31981568 bytes
downloading..., read back 32768/32014336 bytes
downloading..., read back 14888/32029224 bytes
downloading over
download ok
END, press any key to quit
*/
getwchar();
return 0;
}
DWORD DownloadUrl(const WCHAR* pcUrl, const WCHAR* pcCookie, const WCHAR* pcFileSaveTo)
{
DWORD dwRc = S_FALSE;
CLsHttpRequest Dl;
_ASSERT(NULL != pcUrl);
_ASSERT(NULL != pcCookie);
_ASSERT(NULL != pcFileSaveTo);
_tprintf(L">> DownloadUrl(%s, %s)\r\n", pcUrl, pcCookie);
dwRc = Dl.DownloadUrl(pcUrl, pcCookie, pcFileSaveTo);
return dwRc;
}
http请求类实现:
/// @file http_request.h
/// @brief http请求的操作
#ifndef __HTTP_REQUEST_H__
#define __HTTP_REQUEST_H__
#include "stdafx.h"
#include <atlconv.h>
#include <tchar.h>
#include <string>
#include <wininet.h>
#pragma comment(lib, "wininet.lib")
#include <UrlMon.h>
#pragma comment(lib, "urlmon.lib")
#ifndef HTTP_READ_FILE_BUFFER_SIZE
/// 32KB read once form remote
#define HTTP_READ_FILE_BUFFER_SIZE ((long)1024 * 32)
#endif
class CLsHttpRequest
{
public:
CLsHttpRequest();
virtual ~CLsHttpRequest();
DWORD DownloadUrl(
const WCHAR* pcUrl,
const WCHAR* pcCookie,
const WCHAR* pcFileSaveTo);
private:
HINTERNET create_session();
HINTERNET create_connect();
HINTERNET open_request();
DWORD send_request();
DWORD http_read();
void FreeResource();
std::wstring GetSystemUserAgent();
void GetUrlComponent(
const WCHAR* pcUrl,
std::wstring& strHost,
std::wstring& strObject,
INTERNET_SCHEME& nScheme);
void SetHttpMaxConnectNumberEx(HINTERNET hHandle);
BOOL add_http_headers(HINTERNET& http_request, const WCHAR * pcHttpRange);
BOOL add_http_headers_item(HINTERNET& http_request, const WCHAR * pcHttpHeaderItem);
private:
HINTERNET m_hSession;
HINTERNET m_hConnect;
HINTERNET m_http_request;
std::wstring m_strUrl;
std::wstring m_strCookie;
std::wstring m_strFileSaveTo;
std::wstring m_strHost;
std::wstring m_strObject;
INTERNET_SCHEME m_nScheme;
DWORD m_dwFlagOpenHttpRequest;
};
#endif // #ifndef __HTTP_REQUEST_H__
/// @file http_request.cpp
/// @brief ...
#include "stdafx.h"
#include <vector>
#include "http_request.h"
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) \
if (NULL != (p)) \
{ \
delete (p); \
(p) = NULL; \
}
#endif
#ifndef SAFE_CLOSE_HANDLE
#define SAFE_CLOSE_HANDLE(h) \
if (NULL != (h)) \
{ \
CloseHandle((h)); \
(h) = NULL; \
}
#endif
/// @note http 操作形成的句柄, 关闭时要用 InternetCloseHandle
/// 如果用 CloseHandle, 会崩溃
#ifndef SAFE_CLOSE_HTTP_HANDLE
#define SAFE_CLOSE_HTTP_HANDLE(h) \
if (NULL != (h)) \
{ \
InternetCloseHandle((h)); \
(h) = NULL; \
}
#endif
CLsHttpRequest::CLsHttpRequest()
{
m_hSession = NULL;
m_hConnect = NULL;
m_http_request = NULL;
m_strUrl = L"";
m_strCookie = L"";
m_strFileSaveTo = L"";
m_strHost = L"";
m_strObject = L"";
m_nScheme = INTERNET_SCHEME_UNKNOWN;
/// 需要指定 INTERNET_FLAG_NO_COOKIES, 才能使用自己的Cookie !!!
m_dwFlagOpenHttpRequest = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_COOKIES /*| INTERNET_FLAG_NO_CACHE_WRITE*/;
}
CLsHttpRequest::~CLsHttpRequest()
{
FreeResource();
}
void CLsHttpRequest::FreeResource()
{
SAFE_CLOSE_HTTP_HANDLE(m_http_request);
SAFE_CLOSE_HTTP_HANDLE(m_hConnect);
SAFE_CLOSE_HTTP_HANDLE(m_hSession);
}
DWORD CLsHttpRequest::DownloadUrl(
const WCHAR* pcUrl,
const WCHAR* pcCookie,
const WCHAR* pcFileSaveTo)
{
DWORD dwRc = S_FALSE;
if ((NULL == pcUrl)
|| (NULL == pcFileSaveTo))
{
goto END_DownloadUrl;
}
m_strUrl = pcUrl;
m_strFileSaveTo = pcFileSaveTo;
m_strCookie = (NULL != pcCookie) ? pcCookie : L"";
if (NULL == create_session())
goto END_DownloadUrl;
if (NULL == create_connect())
goto END_DownloadUrl;
if (NULL == open_request())
goto END_DownloadUrl;
if (S_OK != send_request())
goto END_DownloadUrl;
if (S_OK != http_read())
goto END_DownloadUrl;
dwRc = S_OK;
END_DownloadUrl:
FreeResource();
return dwRc;
}
std::wstring CLsHttpRequest::GetSystemUserAgent()
{
USES_CONVERSION;
DWORD n = 1024;
char t[1024] = {0};
ObtainUserAgentString(0, t, &n);
return A2W(t);
}
HINTERNET CLsHttpRequest::create_session()
{
if (NULL == m_hSession)
{
m_hSession = InternetOpen(
GetSystemUserAgent().c_str(),
INTERNET_OPEN_TYPE_DIRECT, // INTERNET_OPEN_TYPE_PRECONFIG,
NULL,
NULL,
0);
}
return m_hSession;
}
HINTERNET CLsHttpRequest::create_connect()
{
INTERNET_PORT nPort = INTERNET_DEFAULT_HTTP_PORT;
_ASSERT(m_strUrl.size() > 0);
GetUrlComponent(m_strUrl.c_str(), m_strHost, m_strObject, m_nScheme);
if (INTERNET_SCHEME_HTTPS == m_nScheme)
{
nPort = INTERNET_DEFAULT_HTTPS_PORT;
}
_ASSERT(NULL != m_hSession);
m_hConnect = InternetConnect(
m_hSession,
m_strHost.c_str(),
nPort,
NULL,
NULL,
INTERNET_SERVICE_HTTP,
0,
1);
if (NULL != m_hConnect)
{
/// 试验过了, 在程序开始设置 HINTERNE 为NULL, 那种http连接数, 是没用的
/// 只有在InternetConnect 所在线程中, 对有效httpConnect设置指定连接数, 才有效
SetHttpMaxConnectNumberEx(m_hConnect);
}
return m_hConnect;
}
void CLsHttpRequest::GetUrlComponent(
const WCHAR* pcUrl,
std::wstring& strHost,
std::wstring& strObject,
INTERNET_SCHEME& nScheme)
{
BOOL bRc = FALSE;
DWORD dwBufferSize = 0;
URL_COMPONENTS uc;
std::vector<TCHAR> vec1;
std::vector<TCHAR> vec2;
_ASSERT(NULL != pcUrl);
ZeroMemory(&uc, sizeof(uc));
dwBufferSize = _tcslen(pcUrl) + sizeof(WCHAR);
vec1.resize(dwBufferSize, (TCHAR)0);
vec2.resize(dwBufferSize, (TCHAR)0);
uc.dwStructSize = sizeof(uc);
uc.lpszHostName = &vec1[0];
uc.dwHostNameLength = dwBufferSize;
uc.lpszUrlPath = &vec2[0];
uc.dwUrlPathLength = dwBufferSize;
bRc = InternetCrackUrl(pcUrl, _tcslen(pcUrl), 0, &uc);
_ASSERT(bRc);
strHost = &vec1[0];
strObject = &vec2[0];
nScheme = uc.nScheme;
}
void CLsHttpRequest::SetHttpMaxConnectNumberEx(HINTERNET hHandle)
{
BOOL bRc = FALSE;
DWORD dwErr = 0;
DWORD dwHttpConnectMax = 50/*5 * 5 * 2 + 10*/; ///< (5条任务,一个任务5条线程) * 2 + 10(备用数目)
DWORD dwRecvBufLen = 0;
/// 设置不成功, 设置成功的,取出来还是原来的值.
bRc = InternetSetOption(hHandle,INTERNET_OPTION_MAX_CONNS_PER_SERVER,&dwHttpConnectMax,sizeof(DWORD));
dwErr = ::GetLastError();
// _ASSERT(bRc); ///< 虽然为FALSE, 但是已经有效果了
bRc = InternetSetOption(hHandle,INTERNET_OPTION_MAX_CONNS_PER_1_0_SERVER,&dwHttpConnectMax,sizeof(DWORD));
dwErr = ::GetLastError();
// _ASSERT(bRc); ///< 虽然为FALSE, 但是已经有效果了
/// 这里得到的连接数, 也是旧的, 但是从程序运行效果上看,已经生效了
}
HINTERNET CLsHttpRequest::open_request()
{
const WCHAR * szAcceptTypes[] = {L"*/*", NULL};
if (INTERNET_SCHEME_HTTPS == m_nScheme)
{
m_dwFlagOpenHttpRequest |= INTERNET_FLAG_SECURE ;
}
_ASSERT(NULL != m_hConnect);
m_http_request = HttpOpenRequest(
m_hConnect,
L"GET",
m_strObject.c_str(),
L"HTTP/1.1",
NULL,
szAcceptTypes,
m_dwFlagOpenHttpRequest,
1);
return m_http_request;
}
BOOL CLsHttpRequest::add_http_headers_item(HINTERNET& http_request, const WCHAR * pcHttpHeaderItem)
{
BOOL bRc = FALSE;
DWORD dwRc = 0;
_ASSERT(NULL != pcHttpHeaderItem);
bRc = ::HttpAddRequestHeaders(
http_request,
pcHttpHeaderItem,
-1,
HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
dwRc = ::GetLastError();
_ASSERT(bRc);
return bRc;
}
BOOL CLsHttpRequest::add_http_headers(HINTERNET& http_request, const WCHAR * pcHttpRange)
{
std::wstring strTmp = L"";
BOOL bRc = FALSE;
DWORD dwErrSn = 0;
strTmp = L"Accept: */*";
bRc = add_http_headers_item(http_request, strTmp.c_str());
_ASSERT(bRc);
strTmp = L"Connection: keep-alive";
bRc = add_http_headers_item(http_request, strTmp.c_str());
_ASSERT(bRc);
strTmp = L"Host: ";
strTmp += m_strHost.c_str();
bRc = add_http_headers_item(http_request, strTmp.c_str());
_ASSERT(bRc);
if (NULL != pcHttpRange)
{
strTmp = pcHttpRange;
bRc = add_http_headers_item(http_request, strTmp.c_str());
_ASSERT(bRc);
}
/// 需要保留 user agent,可以增加下载速度
strTmp = L"User-Agent: ";
strTmp += GetSystemUserAgent().c_str();
bRc = add_http_headers_item(http_request, strTmp.c_str());
_ASSERT(bRc);
strTmp = L"Content-Length: 0";
bRc = add_http_headers_item(http_request, strTmp.c_str());
_ASSERT(bRc);
/// 加入自己的Cookie !!!
strTmp = m_strCookie.c_str();
bRc = add_http_headers_item(http_request, strTmp.c_str());
_ASSERT(bRc);
return TRUE;
}
DWORD CLsHttpRequest::send_request()
{
BOOL bRc = FALSE;
_ASSERT(NULL != m_http_request);
add_http_headers(m_http_request, L"Range: bytes=0-\r\n");
bRc = ::HttpSendRequest(
m_http_request,
NULL,
0,
NULL,
0);
return bRc ? S_OK : S_FALSE;
}
DWORD CLsHttpRequest::http_read()
{
BOOL bReadOkOnce = FALSE;
BYTE* pcBufRead = new BYTE[HTTP_READ_FILE_BUFFER_SIZE];
_ASSERT(NULL != pcBufRead);
BOOL bReadResult = FALSE;
DWORD dwReadBack = 0;
LONGLONG llRdBackTotal = 0;
_tprintf(L"downloading...\r\n");
while (true)
{
bReadResult = InternetReadFile(
m_http_request,
pcBufRead,
HTTP_READ_FILE_BUFFER_SIZE,
&dwReadBack);
if (bReadResult && (dwReadBack > 0))
{
bReadOkOnce = TRUE;
llRdBackTotal += dwReadBack;
_tprintf(L"downloading..., read back %d/%I64d bytes\r\n",
dwReadBack,
llRdBackTotal);
/// @todo
/// please cat pcBufRead[0] ~ pcBufRead[dwReadBack - 1] to disk file m_strFileSaveTo
}
else
{
break;
}
}
_tprintf(L"downloading over\r\n");
END_proc_http_request:
SAFE_DELETE(pcBufRead);
return bReadOkOnce ? S_OK : S_FALSE;
}