📄 httpdownload.cpp
字号:
////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2000-2001 Softelf Inc. All rights reserved.
////////////////////////////////////////////////////////////////////////////////
//
// Author : Telan
// Date : 2000-10-04
// Purpose : Try to implement a Http Download Class that Support
// Resume Download,WWW Authorization,Proxy And Proxy
// Authorization,Redirect Support,Timeout Config,Retry
// Config,Notify Support,Job management.
// TODO : Job Management,Multi-Thread,Cookie Support
// History :
// 1.0 : 2000-09-25 - Resume Download,Redirect Support,Proxy Support
// 1.1 : 2000-09-26 - Timeout Config,Retry Config,Notify Support
// 1.2 : 2000-09-27 - WWW Authorization,Proxy Authorization
// 2.0 : 2000-10-04 - Change form using direct winsock to TE_Socket Functions
// - Add Socks-Proxy Support( socks4,socks4a,socks5 )
// - More Robust,More Extensible,More Wieldy
// Mailto : telan@263.net ( Bugs' Report or Comments )
// Notes : This source code may be used in any form in any way you desire. It is
// provided "as is" without express or implied warranty.Use it at your own
// risk! The author accepts no liability for any damage/loss of business
// that this product may cause.
//
////////////////////////////////////////////////////////////////////////////////
// HttpDownload.cpp: implementation of the CHttpDownload class.
#include "stdafx.h"
#include "TE_Socket.h"
#include "SocksPacket.h" // Socks Proxy Support
#include "HttpDownload.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//缓冲大小 10K
#define READ_BUFFER_SIZE (10*1024)
// 用于BASE64编码、解码的常量
CString CHttpDownload::m_strBase64TAB = _T( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" );
UINT CHttpDownload::m_nBase64Mask[]= { 0, 1, 3, 7, 15, 31, 63, 127, 255 };
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CHttpDownload::CHttpDownload()
{
m_strDownloadUrl = _T("");
m_strSavePath = _T("");
m_strTempSavePath = _T("");
// 停止下载
m_bStopDownload = FALSE;
// 强制重新下载(不管已有的文件是否与远程文件相同)
m_bForceDownload = FALSE;
// 是否支持断点续传(假定不支持)
m_bSupportResume = FALSE;
// 文件以及下载大小
m_dwFileSize = 0; // 文件总的大小
m_dwFileDownloadedSize = 0; // 文件总共已经下载的大小
m_dwDownloadSize = 0; // 本次Request需要下载的大小
m_dwDownloadedSize = 0; // 本次Request已经下载的大小
m_dwHeaderSize = 0; // HTTP协议头的长度
m_strHeader = _T(""); // HTTP协议头
// Referer
m_strReferer = _T("");
// UserAgent
m_strUserAgent = _T("HttpDownload/2.0");
// 超时TIMEOUT 连接超时、发送超时、接收超时(单位:毫秒)
m_dwConnectTimeout = DOWNLOAD_CONNECT_TIMEOUT;
m_dwReceiveTimeout = DOWNLOAD_RECV_TIMEOUT;
m_dwSendTimeout = DOWNLOAD_SEND_TIMEOUT;
// 重试机制
m_nRetryType = RETRY_TYPE_NONE; //重试类型(0:不重试 1:重试一定次数 2:总是重试)
m_nRetryTimes = 0; //重试次数
m_nRetryDelay = 0; //重试延迟(单位:毫秒)
m_nRetryMax = 0; //重试最大次数
// 错误处理
m_nErrorCount = 0; //错误次数
m_strError = _T(""); //错误信息
// 向其他窗口发送消息
m_bNotify = FALSE; // 是否向外发送通知消息
m_hNotifyWnd = NULL; // 被通知的窗口
m_nNotifyMessage = 0; // 被通知的消息
// 是否进行验证 : Request-Header: Authorization
m_bAuthorization = FALSE;
m_strUsername = _T("");
m_strPassword = _T("");
// 是否使用代理
m_bProxy = FALSE;
m_strProxyServer = _T("");
m_nProxyPort = 0;
m_nProxyType = PROXY_NONE;
// 代理是否需要验证: Request-Header: Proxy-Authorization
m_bProxyAuthorization = FALSE;
m_strProxyUsername = _T("");
m_strProxyPassword = _T("");
// 下载过程中所用的变量
m_strServer = _T("");
m_strObject = _T("");
m_strFileName = _T("");
m_nPort = DEFAULT_HTTP_PORT ;
// SOCKET 和 BufSocket
m_hSocket = INVALID_SOCKET;
m_pBSD = NULL;
}
// 析构函数
CHttpDownload::~CHttpDownload()
{
CloseSocket();
}
// 创建SOCKET
BOOL CHttpDownload::CreateSocket()
{
CloseSocket();
m_hSocket = TE_CreateSocket(AF_INET,SOCK_STREAM,0);
if (m_hSocket == INVALID_SOCKET)
return FALSE;
m_pBSD = TE_BSocketAttach(m_hSocket,READ_BUFFER_SIZE);
if( m_pBSD == NULL )
return FALSE;
return TRUE;
}
// 关闭SOCKET
void CHttpDownload::CloseSocket()
{
if( m_pBSD != NULL )
{
TE_BSocketDetach(m_pBSD,FALSE);
m_pBSD = NULL;
}
if (m_hSocket != INVALID_SOCKET)
{
TE_CloseSocket(m_hSocket,TRUE);
m_hSocket = INVALID_SOCKET;
}
}
// 下载入口
UINT CHttpDownload::Download(LPCTSTR lpszDownloadUrl,LPCTSTR lpszSavePath,BOOL bForceDownload /*= FALSE */)
{
m_bStopDownload = FALSE;
m_bForceDownload = bForceDownload;
m_nRetryTimes = 0;
// 检验要下载的URL是否为空
m_strDownloadUrl = lpszDownloadUrl;
m_strDownloadUrl.TrimLeft();
m_strDownloadUrl.TrimRight();
if( m_strDownloadUrl.IsEmpty() )
return DOWNLOAD_RESULT_FAIL;
// 检验要下载的URL是否有效
if ( !ParseURL(m_strDownloadUrl, m_strServer, m_strObject, m_nPort))
{
// 在前面加上"http://"再试
m_strDownloadUrl = _T("http://") + m_strDownloadUrl;
if ( !ParseURL(m_strDownloadUrl,m_strServer, m_strObject, m_nPort) )
{
TRACE(_T("Failed to parse the URL: %s\n"), m_strDownloadUrl);
return DOWNLOAD_RESULT_FAIL;
}
}
// 检查本地保存路径
m_strSavePath = lpszSavePath;
m_strSavePath.TrimLeft();
m_strSavePath.TrimRight();
if( m_strSavePath.IsEmpty() )
return DOWNLOAD_RESULT_FAIL;
m_strTempSavePath = m_strSavePath;
m_strTempSavePath += ".down";
m_dwDownloadedSize = 0;
m_dwFileDownloadedSize = 0;
m_dwFileSize = 0;
m_dwDownloadSize = 0;
BOOL bSendOnce = TRUE; // 用于控制向hWndNotify窗口发送消息
ReDownload:
UINT nRequestRet = SendRequest( FALSE ) ;
switch(nRequestRet)
{
case SENDREQUEST_SUCCESS:
break;
case SENDREQUEST_STOP:
return DOWNLOAD_RESULT_STOP;
break;
case SENDREQUEST_FAIL:
return DOWNLOAD_RESULT_FAIL;
break;
case SENDREQUEST_ERROR:
// 是否应该停止下载
if (m_bStopDownload)
return DOWNLOAD_RESULT_STOP;
switch( m_nRetryType )
{
case RETRY_TYPE_NONE:
return DOWNLOAD_RESULT_FAIL;
break;
case RETRY_TYPE_ALWAYS:
if( m_nRetryDelay > 0 )
Sleep(m_nRetryDelay);
goto ReDownload;
break;
case RETRY_TYPE_TIMES:
if( m_nRetryTimes > m_nRetryMax )
return DOWNLOAD_RESULT_FAIL;
m_nRetryTimes++;
if( m_nRetryDelay > 0 )
Sleep( m_nRetryDelay );
goto ReDownload;
break;
default:
return DOWNLOAD_RESULT_FAIL;
break;
}
break;
default:
return DOWNLOAD_RESULT_FAIL;
break;
}
if (m_dwDownloadSize == 0 /*|| m_dwHeaderSize == 0*/)
return DOWNLOAD_RESULT_FAIL;
if( !m_bForceDownload ) // 非强制下载,不检查Last-Modified
{
CFileStatus fileStatus;
if (CFile::GetStatus(m_strSavePath,fileStatus))
{
// 可能会存在1秒的误差
if (( fileStatus.m_mtime - m_TimeLastModified <=2 && m_TimeLastModified-fileStatus.m_mtime<=2 ) && (DWORD)fileStatus.m_size == m_dwFileSize )
return DOWNLOAD_RESULT_SAMEAS;
}
}
CFile fileDown;
if(! fileDown.Open(m_strTempSavePath,CFile::modeCreate|CFile::modeNoTruncate|CFile::modeWrite|CFile::shareDenyWrite) )
return DOWNLOAD_RESULT_FAIL;
// 应该判断一下是否支持断点续传
if( m_bSupportResume )
{
try
{
fileDown.SeekToEnd();
}
catch(CFileException* e)
{
e->Delete();
return DOWNLOAD_RESULT_FAIL;
}
}
// 获取的文件名
//m_strFileName = m_strSavePath.Right(m_strSavePath.GetLength()-m_strSavePath.ReverseFind('\\')-1);
int nSlash = m_strObject.ReverseFind(_T('/'));
if (nSlash == -1)
nSlash = m_strObject.ReverseFind(_T('\\'));
if (nSlash != -1 && m_strObject.GetLength() > 1)
m_strFileName = m_strObject.Right(m_strObject.GetLength() - nSlash - 1);
else
m_strFileName = m_strObject;
if( bSendOnce && m_bNotify )
{
DOWNLOADSTATUS DownloadStatus;
DownloadStatus.dwFileSize = m_dwFileSize;
DownloadStatus.strFileName = m_strFileName;
DownloadStatus.dwFileDownloadedSize = m_dwFileDownloadedSize;
DownloadStatus.nStatusType = STATUS_TYPE_FILESIZE;
::SendMessage(m_hNotifyWnd,m_nNotifyMessage,MSG_DOWNLOAD_STATUS,(LPARAM)&DownloadStatus);
DownloadStatus.nStatusType = STATUS_TYPE_FILENAME;
::SendMessage(m_hNotifyWnd,m_nNotifyMessage,MSG_DOWNLOAD_STATUS,(LPARAM)&DownloadStatus);
DownloadStatus.nStatusType = STATUS_TYPE_FILEDOWNLOADEDSIZE;
::SendMessage(m_hNotifyWnd,m_nNotifyMessage,MSG_DOWNLOAD_STATUS,(LPARAM)&DownloadStatus);
bSendOnce = FALSE;
}
m_dwDownloadedSize = 0;
// 现在开始读取数据
char szReadBuf[READ_BUFFER_SIZE+1];
do
{
// 是否应该停止下载
if (m_bStopDownload)
return DOWNLOAD_RESULT_STOP;
ZeroMemory(szReadBuf,READ_BUFFER_SIZE+1);
int nRet = TE_BSocketGetData(m_pBSD,szReadBuf,READ_BUFFER_SIZE,m_dwReceiveTimeout);
if (nRet <= 0)
{
fileDown.Close();
m_nErrorCount++;
goto ReDownload; //再次发送请求
}
// 将数据写入文件
try
{
fileDown.Write(szReadBuf,nRet);
}
catch(CFileException* e)
{
e->Delete();
fileDown.Close();
goto ReDownload;
}
m_dwDownloadedSize += nRet;
m_dwFileDownloadedSize += nRet;
// 通知消息
if( m_bNotify )
{
DOWNLOADSTATUS DownloadStatus;
DownloadStatus.nStatusType = STATUS_TYPE_FILEDOWNLOADEDSIZE;
DownloadStatus.dwFileDownloadedSize = m_dwFileDownloadedSize;
DownloadStatus.dwFileSize = m_dwFileSize;
DownloadStatus.strFileName = m_strFileName;
::SendMessage(m_hNotifyWnd,m_nNotifyMessage,MSG_DOWNLOAD_STATUS,(LPARAM)&DownloadStatus);
}
}while(m_dwDownloadedSize < m_dwDownloadSize);
// 关闭文件
fileDown.Close();
//关闭SOCKET
CloseSocket();
// 文件改名
//首先将已有的文件删除
try
{
CFile::Remove(m_strSavePath);
}
catch(CFileException *e)
{
e->Delete();
}
//再将新下载的文件改名
try
{
CFile::Rename(m_strTempSavePath,m_strSavePath);
}
catch(CFileException *e)
{
e->Delete();
}
//再将新下载的文件的时间改回去
CFileStatus fileStatus;
CFile::GetStatus(m_strSavePath,fileStatus);
fileStatus.m_mtime = m_TimeLastModified;
CFile::SetStatus(m_strSavePath,fileStatus);
// 不再进行其他操作
//m_bStopDownload = TRUE;
return DOWNLOAD_RESULT_SUCCESS;
}
// 发送请求
// 重定向的时候要加上Referer
UINT CHttpDownload::SendRequest(BOOL bHead /* = FALSE */)
{
CString strVerb;
if( bHead )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -