实际开发过程中,有时候会遇到 URL 的校验问题,其实我也是直接调用了第三方库,但是也引发了一个思考,Go 语言中有哪些方法去验证一个字符串是否满足 URL 格式呢?
URL 代表唯一资源定位符,是 URI 的子类型(尽管许多人可以互换使用这两个术语)。URL 是对网络资源的引用,通常被视为网址(例如 https://golang.org
)。
下面你可以看到一个 URL 的结构,它符合 URI 的结构
URI = scheme:[//authority]path[?query][#fragment] authority = [userinfo@]host[:port]
官方 URL 包
在 Golang 中利用 url.ParseRequestURI
可以简单验证我们的 URL。
func ParseRequestURI(rawurl string) (*URL, error)
ParseRequestURI 将 rawurl 解析成 URL 结构。它假定在 HTTP 请求中接收到 rawurl,因此 rawurl 仅被解释为绝对 URI 或绝对路径。假定字符串 rawurl 没有 #fragment 后缀。(Web 浏览器在将 URL 发送到 Web 服务器之前去除 #fragment。)
ParseRequestURI 与 Parse
还有另一种方法应该用于解析 URL 字符串,但有一些注意事项。它允许相对 URL 使验证更加宽松。它是url.Parse
func Parse(rawurl string) (*URL, error)
如文档中所述:
Parse 将 rawurl 解析成 URL 结构。 rawurl 可以是相对的(路径,没有主机)或绝对的(以方案开头)。尝试在没有方案的情况下解析主机名和路径是无效的,但由于解析歧义,不一定会返回错误。
比如如下的例子:
package main import ( "fmt" "net/url" ) func main() { str := "//yuzhou1u.com" var validURL bool _, err := url.Parse(str) if err != nil { fmt.Println(err) validURL = false } else { validURL = true } fmt.Printf("%s is a valid URL : %v n", str, validURL) }
使用 ParseRequestURI
在 Google 解决方法的时候,根据这篇教程中 How to check if a string is a valid URL in Golang? 提供的方法,写了一个函数:
func isValidUrl(u1 string) bool { _, err := url.ParseRequestURI(u1) if err != nil { return false } u, err := url.Parse(u1) if err != nil || u.Scheme == "" || u.Host == "" { return false } // Check if the URL has a valid scheme (http or https) if u.Scheme != "http" && u.Scheme != "https" { return false } return true }
使用这个方法也有个缺陷,如果是多个 schema:https
,也是检查不出来的,例如下面的示例:
package main import ( "fmt" "net/url" ) func main() { fmt.Println(isValidUrl("testURL")) fmt.Println(isValidUrl("test.test/")) fmt.Println(isValidUrl("http://goglang.org")) fmt.Println(isValidUrl("https://goglang.org")) fmt.Println(isValidUrl("https://https://https://../google.com")) } func isValidUrl(u1 string) bool { _, err := url.ParseRequestURI(u1) if err != nil { return false } u, err := url.Parse(u1) if err != nil || u.Scheme == "" || u.Host == "" { return false } // Check if the URL has a valid scheme (http or https) if u.Scheme != "http" && u.Scheme != "https" { return false } return true }
运行结果如图:
使用 url-verifier
包
安装:go get -u github.com/davidmytton/url-verifier
package main import ( "fmt" urlverifier "github.com/davidmytton/url-verifier" ) func main() { url := "https://https://https://../google.com" verifier := urlverifier.NewVerifier() ret, err := verifier.Verify(url) if err != nil { fmt.Errorf("Error: %s", err) } fmt.Printf("Result: %+vn", ret) }
运行结果:
后面在研究这部分 verifier.go
源码时,发现这个用了 govalidator
这个包,如图:
于是,我们何不直接使用 govalidator
包来判断一个字符串是否是 URL 呢?
使用 govalidator
包
govalidator
是一个针对字符串、结构体和集合的验证器和包。基于 validator.js。
GitHub 地址:https://github.com/asaskevich/govalidator , 目前收获了 5.7k 的 star
安装:go get github.com/asaskevich/govalidator
package main import ( "fmt" "github.com/asaskevich/govalidator" ) func main() { str := "https://https://https://../google.com" validURL := govalidator.IsURL(str) fmt.Printf("%s is a valid URL : %v n", str, validURL) }
运行结果如下:
正则表达式匹配
本来想自己写正则表达式匹配的,然后发现 govalidator
包的作者背后的原理也是用了正则表达式的,
然后就偷懒了,直接把他源码中的部分集合到一个 main.go
函数中:
package mainimport (
"fmt"
"net/url"
"regexp"
"strings"
"unicode/utf8"
)const (
URLSchema string = `((ftp|tcp|udp|wss?|https?)://)`
URLUsername string = `(S+(:S*)?@)`
URLPath string = `((/|?|#)[^s]*)`
URLPort string = `(:(d{1,5}))`
URLIP string = `([1-9]d?|1dd|2[01]d|22[0-3]|24d|25[0-5])(.(d{1,2}|1dd|2[0-4]d|25[0-5])){2}(?:.([0-9]d?|1dd|2[0-4]d|25[0-5]))`
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]).){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
URLSubdomain string = `((www.)|([a-zA-Z0-9]+([-_.]?[a-zA-Z0-9])*[a-zA-Z0-9].[a-zA-Z0-9]+))`
URL = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|([` + IP + `])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Zx{00a1}-x{ffff}0-9]+-?-?)*[a-zA-Zx{00a1}-x{ffff}0-9]+)(?:.([a-zA-Zx{00a1}-x{ffff}]{1,}))?)).?` + URLPort + `?` + URLPath + `?$`/*MaxURLRuneCount : maxima cantidad de runas por contar*/
MaxURLRuneCount = 2083
/*MinURLRuneCount : minima cantidad de runas por contar*/
MinURLRuneCount = 3
)var rxURL = regexp.MustCompile(URL)
// IsURL checks if the string is an URL.
func IsURL(str string) bool {
if str == "" || utf8.RuneCountInString(str) >= MaxURLRuneCount || len(str)