写点什么

如何在 Go 中验证一个字符串是否是 URL?

作者:宇宙之一粟
  • 2023-04-17
    中国香港
  • 本文字数:3708 字

    阅读完需:约 12 分钟

如何在 Go 中验证一个字符串是否是 URL?

前言

在实际开发过程中,有时候会遇到 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: %+v\n", 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 main
import ( "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?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|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-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{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) <= MinURLRuneCount || strings.HasPrefix(str, ".") { return false } strTemp := str if strings.Contains(str, ":") && !strings.Contains(str, "://") { // support no indicated urlscheme but with colon for port number // http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString strTemp = "http://" + str } u, err := url.Parse(strTemp) if err != nil { return false } if strings.HasPrefix(u.Host, ".") { return false } if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) { return false } return rxURL.MatchString(str)}
func main() {
fmt.Println(IsURL("testURL"))
fmt.Println(IsURL("test.test/"))
fmt.Println(IsURL("http://goglang.org"))
fmt.Println(IsURL("https://goglang.org"))
fmt.Println(IsURL("https://https://https://../google.com"))}
复制代码


运行结果:



除了校验 URL,这个包还提供了众多的字符串校验方法,例如校验邮箱、信用卡格式、IP...


总结

数据校验是每个程序员日常开发过程的一部分,尤其是在从事后端服务时,数据验证必须严格,保持正确。


在这篇文章中,我们讨论了如何在 Go 语言中正确验证一个字符串是否是 URL,当然利用了官方包和优秀的第三方包,在实际过程中,可能我们为了简便会直接使用别人开发好的工具,但是在学习过程中,不妨也去思考别人实现的原理,结合实际业务需要,进而扩展成自己的工具包。


希望本文能对你有所帮助,如果喜欢本文,可以点个关注.


下一篇文章见!宇宙古今无有穷期,一生不过须臾,当思奋争。


参考链接:


发布于: 18 小时前阅读数: 4
用户头像

宇宙古今无有穷期,一生不过须臾,当思奋争 2020-05-07 加入

🏆InfoQ写作平台-签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于外企开发,弹性不加班 热衷分享,执着于阅读写作,佛系不水文 同名公众号:《宇宙之一粟》

评论

发布
暂无评论
如何在 Go 中验证一个字符串是否是 URL?_正则表达式_宇宙之一粟_InfoQ写作社区