在开发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