上一篇指北,大致介绍了设计背景和思路,然后科普了一下正则表达式的基本常识,在这一篇中,将详细介绍整个URL提取的详细实现。包括获取Scheme和TLDs,Schemes、Domain、IP+Port….等URL模式的regex表达式拼接。
1 | [协议类型]://[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID] |
资源准备
IANA全称为Internet Assigned Numbers Authority, 负责分配和维护用于驱动互联网的技术标准(“协议”)的唯一代码和编号系统。
IANA的官网上可以获取到官方的Schemes和TLDs的文件。
1 | Schemes: "https://www.iana.org/assignments/uri-schemes/uri-schemes-1.csv" |
将资源get下来,并利用go的template模板库生成go文件,文件内容是string[]的常量。Scheme.go生成代码如下:
1 | // 构建template |
经过以上脚本的操作,分别get了Schemes和TLDs的资源,并生成了如下实例的go文件:
1 | package extraURLs |
1 | package extraURLs |
Domain匹配
根据Domain+TLds格式进行域名匹配,域名的规范如下:
- 只能使用英文字母(a-z,不区分大小写)、数字(0~9)以及连接符(-)。不支持使用空格及以下字符:
!?%$等 - 连接符(-)不能连续出现、不能单独注册,也不能放在开头和结尾。
1 | letter := `\p{L}` |
WebURL匹配
网络URL可能包含domain,IPAddress,port,pathCont。其中 domain和IP只能出现一个,port、pathCont可存在可不存在。
1 | WebURL = (domain | ip):(port)?(pathCont)? |
Domain我们已经拼接好了。那么考虑如何实现IPAddress和port的拼接。
- 1.0.0.0 ~ 255.255.255.255
- IPv6二进位制下为128位长度,以16位为一组,每组以冒号“:”隔开,可以分为8组,每组以4位十六进制方式表示
- 例如:2001:0db8:86a3:08d3:1319:8a2e:0370:7344
- 注意:IPv6IPv6在某些条件下可以省略:
- 每项数字前导的0可以省略,省略后前导数字仍是0则继续
- 可以用双冒号“::”表示一组0或多组连续的0,但只能出现一次
- 如果这个地址实际上是IPv4的地址,后32位可以用10进制数表示
1 | // ip地址分为 ipv4 ipv6 |
PathCont匹配
介绍Scheme匹配之前,需要先介绍以下PathCont的匹配方式。PathCont指的是出去Schemes和WebURL之后,后边URL附带的所属内容。包括文件路径、查询和片段等。一种思路是按照这种顺序依次匹配路径、查询和片段,但是比较复杂。这里使用的参考了xurls库中的实现,默认为只要后边path中出现的字符是合法的,都是路径的一部分。
1 | // 现在我们只需要划定字符范围就可以了 path可以分为: midchar endchar. 即路径中的字符和路径末尾的字符。 |
Scheme匹配
前文已经将大致的匹配模块都讲述完了,剩下最后一个Scheme匹配。之所以放在最后将,是因为Scheme可以适用两种匹配方式。第一种,我们强制的认为,合法的URL必须是Scheme + :// + WebURL。第二种,我们可以将WebURL中的host也当做path的一部分,即WebUR+PathCont。
两种方式各有利弊,第二种肯定可以匹配更全面的URL, 但是我在业务中更强调提取出连接的可用性,所以选择了第一种,强制提取Scheme+WebURL。
1 | // 两种Scheme 第一种后接 :// 第二种后接 : |
代码实现
将拼接好的String传入regexp生成regexp结构体对象,设置匹配格式为longest()。
使用的时候,用户只需调用结构体的匹配方法。regexp::FindString regexp::FindAllString
1 | //StrictExp = Schemes + pathCont |