/*
* Url.h
* Created on: 2011-10-6
* Author: qiuxiong
* CUrl類的作用:
* 解析URL,,構(gòu)造HTTP請求報文,發(fā)送給目標服務(wù)器.
* URL的通用形式是:
* scheme://hostname[:port][/path][;params][?query][#fragment]
* scheme 表示協(xié)議名稱,,例如http,ftp,mailto等等
* hostname 表示主機號,,其實就是該主機對應(yīng)的因特網(wǎng)域名
* port 表示端口號
* path 表示URL路徑
* params 表示對象參數(shù)
* query 表示查詢信息,也經(jīng)常記為request
* fragment 表示片段標識
*/
#ifndef URL_H_
#define URL_H_
#include<string>
using namespace std;
const unsigned int URL_LEN=256;//URL最大長度
const unsigned int HOST_LEN=256;//主機號的最大長度
const int DEFAULT_HTTP_PORT=80;//HTTP默認的端口號為80
const int DEFAULT_FTP_PORT=21;//FTP默認的端口號為21
//自定義的枚舉類型:表示協(xié)議類型,有HTTP協(xié)議,,F(xiàn)TP協(xié)議,,其他的無效協(xié)議
enum url_scheme{SCHEME_HTTP,SCHEME_FTP,SCHEME_INVALID};
class CUrl
{
public:
string m_sUrl;//URL字符串
enum url_scheme m_eScheme;//URL解析出的協(xié)議類型
string m_sHost;//URL解析出的主機號
int m_nPort;//URL解析出的端口號
string m_sPath;//URL解析出的請求資源
public:
CUrl();
~CUrl();
bool ParseUrlEx(string strUrl);//解析是不是HTTP協(xié)議的URL,不是返回FALSE,,否則進一步解析URL對應(yīng)的協(xié)議名稱,,主機號,端口號,,請求資源賦值給成員變量,,最后返回TRUE
//真正的解析URL的函數(shù)
void ParseUrlEx(const char *url,char *protocol,int lprotocol,char *host,int lhost,char *request,int lrequest,int *port);
char *GetIpByHost(const char *host);//通過主機號得到IP字符串
bool IsValidHost(const char *host);//判斷該主機號是不是有效的主機號
bool IsForeignHost(string host);//判斷該主機號是否為外國主機號
bool IsImageUrl(string url);//判斷該URL是否為圖片鏈接
bool IsValidIp(const char *ip);//判斷該IP字符串是否有效
bool IsVisitedUrl(const char *url);//判斷該URL是否訪問過
bool IsUnReachedUrl(const char *url);//----沒有實現(xiàn)---------
bool IsValidHostChar(char ch);//判斷該字符是否為構(gòu)成主機號的字符
private:
void ParseScheme(const char *url);//解析出URL所以對應(yīng)的協(xié)類型
};
#endif /* URL_H_ */
/*
*Url.cpp
* Created on: 2011-10-6
* Author: qiuxiong
* 有了URL搜集系統(tǒng)就可以根據(jù)URL的標識抓取其對應(yīng)的網(wǎng)頁
*/
#include<iostream>
#include<string>
#include<sys/socket.h>
#include<netdb.h>
#include "Http.h"
#include "Tse.h"
#include "Url.h"
#include "Md5.h"
#include "StrFun.h"
//DNS緩存:我們不能每次解析出了一個主機號就交給DNS server來得到IP,建立一個DNS緩存,,防止出現(xiàn)類似于拒絕訪問攻擊的作用
map<string,string>mapCacheHostLookup;
//這個set容器保存的是已經(jīng)訪問過的URL對應(yīng)的MD5值
extern set<string>setVisitedUrlMD5;//
extern map<unsigned long ,unsigned long>mapIpBlock;//
typedef map<string,string>::value_type valTypeCHL;
pthread_mutex_t mutexCacheHost=PTHREAD_MUTEX_INITIALIZER;//線程的互斥變量初始化
//自定義協(xié)議類-端口-可以訪問,,例如 "http://" 80 1
struct scheme_data
{
char *leading_string;
int default_port;
int enabled;
};
static struct scheme_data supported_schemes[]=
{
{"http://",DEFAULT_HTTP_PORT,1},
{"ftp://",DEFAULT_FTP_PORT,1},
{NULL,-1,0}
};
//解析出URL所以對應(yīng)的協(xié)議類型,然后將協(xié)議類型賦值給成員變量m_eScheme
void CUrl::ParseScheme(const char *url)
{
int i;
for(i=0;supported_schemes[i].leading_string;i++)
if(strncasecmp(url,supported_schemes[i].leading_string,strlen(supported_schemes[i].leading_string))==0)
{
if(supported_schemes[i].enabled)
{
this->m_eScheme=(enum url_scheme)i;//強制轉(zhuǎn)化為枚舉類型
return;
}
else
{
this->m_eScheme=SCHEME_INVALID;
return;
}
}
this->m_eScheme=SCHEME_INVALID;
return;
}
//解析是不是HTTP協(xié)議的URL,不是返回FALSE,,否則進一步解析URL對應(yīng)的協(xié)議名稱,,主機號,端口號,,請求資源賦值給成員變量,,最后返回TRUE
bool CUrl::ParseUrlEx(string strUrl)
{
char protocol[10];
char host[HOST_LEN];
char request[256];
int port=-1;
memset(protocol,0,sizeof(protocol));
memset(host,0,sizeof(host));
memset(request,0,sizeof(request));
this->ParseScheme(strUrl.c_str());//得到協(xié)議類型
if(this->m_eScheme!=SCHEME_HTTP)//我們只抓取所有協(xié)議名為HTTP的WEB網(wǎng)頁
return false;
ParseUrlEx(strUrl.c_str(),protocol,sizeof(protocol),host,sizeof(host),request,sizeof(request),&port);
m_sUrl=strUrl;
m_sHost=host;
m_sPath=request;
if(port>0)
m_nPort=port;
return true;
}
//真正的解析URL的函數(shù)
void CUrl::ParseUrlEx(const char *url,char *protocol,int lprotocol,char *host,int lhost,char *request,int lrequest,int *port)
{
char *ptr,*ptr2,*work;
*protocol=*host=*request=0;
*port=DEFAULT_HTTP_PORT;
int len=strlen(url);
work=new char[len+1];
memset(work,0,len+1);
strncpy(work,url,len);
ptr=strchr(work,':');//查找work字符串中首次出現(xiàn)':'的位置指針
//得到協(xié)議名稱
if(ptr!=NULL)
{
*(ptr++)=0;
strncpy(protocol,work,lprotocol);
}
else
{
strncpy(protocol,"HTTP",lprotocol);
ptr=work;
}
//跳過"http://"字符
if((*ptr=='/')&&(*(ptr+1)=='/'))
ptr+=2;
ptr2=ptr;
while(IsValidHostChar(*ptr2)&&*ptr2)
ptr2++;
*ptr2=0;
//得到主機號
strncpy(host,ptr,lhost);
int offset=ptr2-work;
const char *pStr=url+offset;
//得到請求資源
strncpy(request,pStr,lrequest);
ptr=strchr(host,':');
//得到端口號
if(ptr!=NULL)
{
*ptr=0;//不能小看這個地方,很有用,,將主機號+端口號相分離
*port=atoi(ptr+1);
}
delete []work;
work=NULL;
}
//無參構(gòu)造函數(shù)
CUrl::CUrl()
{
this->m_sUrl="";
this->m_eScheme=SCHEME_INVALID;
this->m_sHost="";
this->m_sPath="";
this->m_nPort=DEFAULT_HTTP_PORT;
}
//析構(gòu)函數(shù)
CUrl::~CUrl()
{
}
//通過主機號得到IP字符串
char *CUrl::GetIpByHost(const char *host)
{
if(!host)
return NULL;
if(!IsValidHost(host))
return NULL;
unsigned long inaddr=0;
int len=0;
char *result=NULL;
inaddr=(unsigned long)inet_addr(host);//將字符串IP轉(zhuǎn)化為32二進制的網(wǎng)絡(luò)字節(jié)序
if(inaddr!=INADDR_NONE)//表示該主機號就是用IP地址表示的
{
len=strlen(host);
result=new char[len+1];
memset(result,0,len+1);
memcpy(result,host,len);
return result;
}
else//否則,,我在DNS緩存中看能否查找該主機號對應(yīng)的IP字符串
{
map<string,string>::iterator it=mapCacheHostLookup.find(host);
if(it!=mapCacheHostLookup.end())//在DNS緩存中查找到了
{
const char *strHostIp;
strHostIp=(*it).second.c_str();
inaddr=(unsigned long)inet_addr(strHostIp);
if(inaddr!=INADDR_NONE)
{
len=strlen(strHostIp);
result=new char[len+1];
memset(result,0,len+1);
memcpy(result,strHostIp,len);
return result;
}//end if
}//end if
}//end else
//通過上面的方法我們都沒有查找,這個時候我們只能通過DNS server查找了,,這種帶寬的消耗是必要的,!
struct hostent*hp;
hp=gethostbyname(host);//通過主機號或者說是域名得到hostent結(jié)構(gòu),這個結(jié)構(gòu)包含主機號或者說域名的很多信息,,例如我們要找的IP字符串就在其中
if(hp==NULL)
{
cout<<"url.cpp: gethostbyname()函數(shù)出錯!"<<endl;
return NULL;
}
struct in_addr in;
bcopy(*(hp->h_addr_list),(caddr_t)&in,hp->h_length);
char abuf[INET_ADDRSTRLEN];
//inet_ntop()將32位的二進制網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)化為IP字符串
if(inet_ntop(AF_INET,(void*)&in,abuf,sizeof(abuf))==NULL)
{
cout<<"url.cpp: inet_ntop()函數(shù)出錯!"<<endl;
return NULL;
}
else
{
pthread_mutex_lock(&mutexCacheHost);//互斥的鎖定函數(shù)
if(mapCacheHostLookup.find(host)==mapCacheHostLookup.end())
mapCacheHostLookup.insert(valTypeCHL(host,abuf));//將新得到的主機號<-->IP字符串插入DNS緩存中
pthread_mutex_unlock(&mutexCacheHost);//互斥的解鎖函數(shù)
}
len=strlen(abuf);
result=new char[len+1];
memset(result,0,len+1);
memcpy(result,abuf,len);
return result;
}
//判斷該字符是否為構(gòu)成主機號的字符
bool CUrl::IsValidHostChar(char ch)
{//主機號只能是由26個大小寫字符,,所10個阿拉伯數(shù)字,以及. : - _構(gòu)造而成
return (isalpha(ch)||isdigit(ch)||ch=='.'||ch==':'||ch=='_'||ch=='-');
}
//判斷該主機號是不是有效的主機號
bool CUrl::IsValidHost(const char *host)
{
if(!host)//空的主機號,,我們認為是無效的主機號
return false;
if(strlen(host)<6)//主機號長度小于6,,我們認為ieshi無效的主機號
return false;
unsigned int i;
for(i=0;i<strlen(host);i++)
if(!IsValidHostChar(host[i]))
return false;
return true;
}
//判斷該URL是否訪問過
bool CUrl::IsVisitedUrl(const char *url)
{
if(!url)
return true;
CMD5 iMD5;
iMD5.GenerateMD5((unsigned char*)url,strlen(url));
string strDigest=iMD5.ToString();
if(setVisitedUrlMD5.find(strDigest)!=setVisitedUrlMD5.end())
return true;
return false;
}
//判斷該IP字符串是否有效
bool CUrl::IsValidIp(const char *ip)
{
if(ip==NULL)
return false;
unsigned long inaddr=(unsigned long)inet_addr(ip);
if(inaddr==INADDR_NONE)//顯然該IP參數(shù)不是正確的字符串IP
return false;
if(mapIpBlock.size()>0)
{
map<unsigned long,unsigned long>::iterator pos;
for(pos=mapIpBlock.begin();pos!=mapIpBlock.end();pos++)
{
unsigned long ret;
ret=inaddr&~((*pos).second);
if(ret==(*pos).first)
return true;
}//end for
return false;
}//end if
return true;
}
//判斷該主機號是否為外國主機號
bool CUrl::IsForeignHost(string host)
{
if(host.empty())
return true;
if(host.size()>HOST_LEN)
return true;
unsigned long inaddr=0;
inaddr=(unsigned long)inet_addr(host.c_str());
if(inaddr!=INADDR_NONE)//顯然該HOST是用IP字符串表示的,我們認為它不是外國主機號
return false;//目前無法通過字符串IP判斷是不是外國主機號,所以一律當作國內(nèi)主機號
string::size_type idx=host.rfind('.');
string tmp;//tmp得到的是主機號中的頂級域名
if(idx!=string::npos)
tmp=host.substr(idx+1);
CStrFun::Str2Lower(tmp,tmp.size());
//下面10個頂級域名是我們能訪問的頂級域名,,其他都視為不能訪問的
const char *home_host[]={"cn","com","net","org","info","biz","tv","cc","hk","tw"};
int home_host_num=10;
for(int i=0;i<home_host_num;i++)
if(tmp==home_host[i])
return false;
return true;
}
//判斷該URL是否為圖片鏈接
bool CUrl::IsImageUrl(string url)
{
if(url.empty())
return false;
if(url.size()>URL_LEN)
return false;
string::size_type idx=url.rfind('.');
string tmp;
if(idx!=string::npos)
tmp=url.substr(idx+1);
CStrFun::Str2Lower(tmp,tmp.size());
const char *image_type[]={"jpg","bmp","gif","jpeg","png","tif","psd"};
int image_type_num=7;
for(int i=0;i<image_type_num;i++)
if(tmp==image_type[i])
return true;
return false;
}
//目前不知道系統(tǒng)設(shè)計這個函數(shù)的作用----我在這里讓它一律返回false
bool CUrl::IsUnReachedUrl(const char *url)
{
return false;
}