/*
* Url.h
* Created on: 2011-10-6
* Author: qiuxiong
* CUrl类的作用:
* 解析URL,构造HTTP请求报文,发送给目标服务器.
* URL的通用形式是:
* scheme://hostname[:port][/path][;params][?query][#fragment]
* scheme 表示协议名称,例如http,ftp,mailto等等
* hostname 表示主机号,其实就是该主机对应的因特网域名
* port 表示端口号
* path 表示URL路径
* params 表示对象参数
* query 表示查询信息,也经常记为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
//自定义的枚举类型:表示协议类型,有HTTP协议,FTP协议,其他的无效协议
enum url_scheme{SCHEME_HTTP,SCHEME_FTP,SCHEME_INVALID};
class CUrl
{
public:
string m_sUrl;//URL字符串
enum url_scheme m_eScheme;//URL解析出的协议类型
string m_sHost;//URL解析出的主机号
int m_nPort;//URL解析出的端口号
string m_sPath;//URL解析出的请求资源
public:
CUrl();
~CUrl();
bool ParseUrlEx(string strUrl);//解析是不是HTTP协议的URL,不是返回FALSE,否则进一步解析URL对应的协议名称,主机号,端口号,请求资源赋值给成员变量,最后返回TRUE
//真正的解析URL的函数
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);//----没有实现---------
bool IsValidHostChar(char ch);//判断该字符是否为构成主机号的字符
private:
void ParseScheme(const char *url);//解析出URL所以对应的协类型
};
#endif /* URL_H_ */
/*
*Url.cpp
* Created on: 2011-10-6
* Author: qiuxiong
* 有了URL搜集系统就可以根据URL的标识抓取其对应的网页
*/
#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缓存,防止出现类似于拒绝访问攻击的作用
map<string,string>mapCacheHostLookup;
//这个set容器保存的是已经访问过的URL对应的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;//线程的互斥变量初始化
//自定义协议类-端口-可以访问,例如 "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所以对应的协议类型,然后将协议类型赋值给成员变量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;//强制转化为枚举类型
return;
}
else
{
this->m_eScheme=SCHEME_INVALID;
return;
}
}
this->m_eScheme=SCHEME_INVALID;
return;
}
//解析是不是HTTP协议的URL,不是返回FALSE,否则进一步解析URL对应的协议名称,主机号,端口号,请求资源赋值给成员变量,最后返回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());//得到协议类型
if(this->m_eScheme!=SCHEME_HTTP)//我们只抓取所有协议名为HTTP的WEB网页
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的函数
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字符串中首次出现':'的位置指针
//得到协议名称
if(ptr!=NULL)
{
*(ptr++)=0;
strncpy(protocol,work,lprotocol);
}
else
{
strncpy(protocol,"HTTP",lprotocol);
ptr=work;
}
//跳过"//"字符
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;
}
//无参构造函数
CUrl::CUrl()
{
this->m_sUrl="";
this->m_eScheme=SCHEME_INVALID;
this->m_sHost="";
this->m_sPath="";
this->m_nPort=DEFAULT_HTTP_PORT;
}
//析构函数
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转化为32二进制的网络字节序
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缓存中看能否查找该主机号对应的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结构,这个结构包含主机号或者说域名的很多信息,例如我们要找的IP字符串就在其中
if(hp==NULL)
{
cout<<"url.cpp: gethostbyname()函数出错!"<<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位的二进制网络字节序转化为IP字符串
if(inet_ntop(AF_INET,(void*)&in,abuf,sizeof(abuf))==NULL)
{
cout<<"url.cpp: inet_ntop()函数出错!"<<endl;
return NULL;
}
else
{
pthread_mutex_lock(&mutexCacheHost);//互斥的锁定函数
if(mapCacheHostLookup.find(host)==mapCacheHostLookup.end())
mapCacheHostLookup.insert(valTypeCHL(host,abuf));//将新得到的主机号<-->IP字符串插入DNS缓存中
pthread_mutex_unlock(&mutexCacheHost);//互斥的解锁函数
}
len=strlen(abuf);
result=new char[len+1];
memset(result,0,len+1);
memcpy(result,abuf,len);
return result;
}
//判断该字符是否为构成主机号的字符
bool CUrl::IsValidHostChar(char ch)
{//主机号只能是由26个大小写字符,所10个阿拉伯数字,以及. : - _构造而成
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参数不是正确的字符串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判断是不是外国主机号,所以一律当作国内主机号
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;
}
//目前不知道系统设计这个函数的作用----我在这里让它一律返回false
bool CUrl::IsUnReachedUrl(const char *url)
{
return false;
}