⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 httpdownload.cpp

📁 基于Winsock2的支持断点续传和SOCKS代理的HTTP下载类 CHttpDownload
💻 CPP
📖 第 1 页 / 共 3 页
字号:
////////////////////////////////////////////////////////////////////////////////
//	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 + -