随笔-156  评论-223  文章-30  trackbacks-0
   在开发HTTP相关程序时,经常会碰到从网络链接URL中提取协议名、服务器、路径等目标对象,如果使用C/C++字符串操作函数,那么则显得有点麻烦且代码不易维护,其实关于文本内容的解析工作,都可优先考虑使用正则表达式库来解决处理,C++方面的正则库也有很多种,如atl、pcre、boost。下面就使用boost中的regex来解析URL提取协议名、服务器、路径为目标说明其用法。

协议名
   可有可无,如果有时则后面必跟着://,如果没有,则默认为使用http协议。通常还有其它的协议如https、ssl、ftp、mailto等。因此匹配协议名的正则表达式应该是(?:(mailto|ssh|ftp|https?)://)?,注意这个表达式本身捕获了协议名,但不包括://。
   
服务器
   或是域名,如www.csdn.net;或是IP地址,如192.168.1.1,可带端口号,如192.168.1.1:8080。匹配域名的正则表达式为(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+(?:com|net|edu|biz|gov|org|in(?:t|fo)|(?-i:[a-z][a-z])),表达式"(?:com|net|edu|biz|gov|org|in(?:t|fo)"匹配了com、net、edu、biz、gov、org、int、info等常见的域名,而(?-i:[a-z][a-z])匹配了国家代码,而且只允许小写为合法的,如www.richcomm.com.cn。匹配IP要尽量精确,考虑到IP每部分应为数字且范围在0-255之间,因此表达式应为(?:[01]?\d\d?|2[0-4]\d|25[0-5])\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])。注意以上域名或IP的正则式本身不捕获它们,这是为了留在后面作为整体捕获。
   端口号的正则表达式为(?::(\d{1,5}))?,这里限制了端口号为1至5位的数字,更精确的匹配如要求在某范围如[1024,65535]间则可参考以上IP正则模式。综上所得,匹配服务器的正则表达式为((?:(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+(?:com|net|edu|biz|gov|org|in(?:t|fo)|(?-i:[a-z][a-z]))|(?:[01]?\d\d?|2[0-4]\d|25[0-5])\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])\.(?:[01]?\d\d?|2[0-4]\d|25[0-5])))(?::(\d{1,5}))?,这个正则式作为整体捕获了域名或IP,及端口号(若有),如www.csdn.net,则得到www.csdn.net和空(没有端口,http默认为80,https默认为443)子串;192.168.1.1:8080则得到192.168.1.1和8080子串。
   
路径
   最简单的形式为(/.*)?,更精确的形式为/[^.!,?;"'<>()\[\]{}\s\x7F-\xFF]*(?:[.!,?]+[^.!,?;"'<>()\[\]{}\s\x7F-\xFF]+)*。
   
   以上所有正则表达式均为ascii字符集,对于unicode字符集则在其前加L即可。
   
   为方便使用,封装成了两个自由模板函数,如下所示
 1template<typename charT>
 2inline bool boost_match(const charT* pattern,const charT* text,unsigned int flags=boost::regex::normal,boost::match_results<const charT*>* result=NULL)
 3{
 4    boost::basic_regex<charT,boost::regex_traits<charT> > expression(pattern,flags); 
 5    if(NULL==result)
 6        return boost::regex_match(text,expression);
 7    return boost::regex_match(text,*result,expression);
 8}

 9
10template<typename charT>
11inline bool boost_search(const charT* pattern,const charT* text,unsigned int flags=boost::regex::normal,boost::match_results<const charT*>* result=NULL)
12{
13    boost::basic_regex<charT,boost::regex_traits<charT> > expression(pattern,flags); 
14    if(NULL==result)
15        return boost::regex_search(text,expression);
16    return boost::regex_search(text,*result,expression);
17}
   
   测试示例如下      
 1static const string protocol = "(?:(mailto|ssh|ftp|https?)://)?";
 2static const string hostname = "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)+(?:com|net|edu|biz|gov|org|in(?:t|fo)|(?-i:[a-z][a-z]))";
 3static const string ip = "(?:[01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.(?:[01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.(?:[01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.(?:[01]?\\d\\d?|2[0-4]\\d|25[0-5])";
 4static const string port = "(?::(\\d{1,5}))?";
 5static const string path = "(/.*)?";
 6static const string pattern = protocol + "((?:" + hostname + "|" + ip + "))" + port + path;
 7
 8int _tmain(int argc, _TCHAR* argv[])
 9{
10    using namespace boost;
11
12    //形式1: 带协议名,服务器为名称,不带端口号
13    bool ret;
14    string text = "http://www.cppblog.com/qinqing1984";
15    boost::cmatch what;
16    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
17    assert(ret);
18    assert(what[1].str()=="http");
19    assert(what[2].str()=="www.cppblog.com");
20    assert(what[3].str()=="");
21    assert(what[4].str()=="/qinqing1984");
22
23    //形式2: 不带协议名,服务器为名称,带端口号
24    text = "www.cppblog.com:80/qinqing1984";
25    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
26    assert(ret);
27    assert(what[1].str()=="");
28    assert(what[2].str()=="www.cppblog.com");
29    assert(what[3].str()=="80");
30    assert(what[4].str()=="/qinqing1984");
31
32    //形式3: 不带协议名,服务器为名称,不带路径
33    text = "www.cppblog.com:80";
34    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
35    assert(ret);
36    assert(what[1].str()=="");
37    assert(what[2].str()=="www.cppblog.com");
38    assert(what[3].str()=="80");
39    assert(what[4].str()=="");
40
41    //形式4: 协议为https,服务器为IP,带端口号
42    text = "https://192.168.1.1:443/index.html";
43    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
44    assert(ret);
45    assert(what[1].str()=="https");
46    assert(what[2].str()=="192.168.1.1");
47    assert(what[3].str()=="443");
48    assert(what[4].str()=="/index.html");
49
50    //形式5: 端口超过5位数
51    text = "ftp://192.168.1.1:888888";
52    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
53    assert(!ret);
54
55    //形式6: 没有协议名
56    text = "//192.168.1.1/index.html";
57    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
58    assert(!ret);
59
60    //形式7: 没有服务器
61    text = "http:///index.html";
62    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
63    assert(!ret);
64
65    //形式8: 不合法的服务器
66    text = "cppblog/index.html";
67    ret=boost_match(pattern.c_str(),text.c_str(),regex::icase|regex::perl,&what);
68    assert(!ret);
69
70    return 0;
71}
   对URL的解析,因时间有限,本文所述不尽详细,只是略作分析,以点带面,更多的精确匹配则依赖于实际的应用需求。
posted on 2011-11-27 17:22 春秋十二月 阅读(7842) 评论(5)  编辑 收藏 引用 所属分类: Opensrc

评论:
# re: 使用boost regex解析URL 2011-11-27 23:08 | guest
最后一个是合法的,最常见的情况就是在在局域网内,有台叫cppblog的机器,直接用cppblog访问。  回复  更多评论
  
# re: 使用boost regex解析URL 2011-11-28 08:57 | 万连文
如果程序仅限win平台,使用InternetCrackUrl;否则的话请移植chrome等开源的解析,否则完备性不够的话,后面隐藏的BUG会让你疯狂。  回复  更多评论
  
# re: 使用boost regex解析URL 2011-11-28 09:29 | 春秋十二月
你说的有道理,我明白@万连文
  回复  更多评论
  
# re: 使用正则表达式解析URL 2014-07-17 03:03 | lixubin
linux 下编译能通过,但是表达式好像有问题

'boost::bad_expression'
what(): Invalid preceding regular expression
Aborted (core dumped)  回复  更多评论
  
# re: 使用正则表达式解析URL 2015-08-11 14:35 | mz

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理