Golang實現(xiàn)驗證一個字符串是否為URL
前言
在實際開發(fā)過程中,有時候會遇到 URL 的校驗問題,其實我也是直接調(diào)用了第三方庫,但是也引發(fā)了一個思考,Go 語言中有哪些方法去驗證一個字符串是否滿足 URL 格式呢?
URL 代表唯一資源定位符,是 URI 的子類型(盡管許多人可以互換使用這兩個術(shù)語)。URL 是對網(wǎng)絡(luò)資源的引用,通常被視為網(wǎng)址(例如 https://golang.org)。
下面你可以看到一個 URL 的結(jié)構(gòu),它符合 URI 的結(jié)構(gòu)
URI = scheme:[//authority]path[?query][#fragment]
authority = [userinfo@]host[:port]
官方 URL 包
在 Golang 中利用 url.ParseRequestURI 可以簡單驗證我們的 URL。
func ParseRequestURI(rawurl string) (*URL, error)
ParseRequestURI 將 rawurl 解析成 URL 結(jié)構(gòu)。它假定在HTTP請求中接收到 rawurl,因此 rawurl 僅被解釋為絕對 URI 或絕對路徑。假定字符串 rawurl 沒有 #fragment 后綴。(Web 瀏覽器在將 URL 發(fā)送到 Web 服務(wù)器之前去除 #fragment。)
ParseRequestURI 與 Parse
還有另一種方法應(yīng)該用于解析 URL 字符串,但有一些注意事項。它允許相對 URL 使驗證更加寬松。它是url.Parse
func Parse(rawurl string) (*URL, error)
如文檔中所述:
Parse 將 rawurl 解析成 URL 結(jié)構(gòu)。
rawurl 可以是相對的(路徑,沒有主機)或絕對的(以方案開頭)。嘗試在沒有方案的情況下解析主機名和路徑是無效的,但由于解析歧義,不一定會返回錯誤。
比如如下的例子:
package main
import (
"fmt"
"net/url"
)
func main() {
str := "http://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 解決方法的時候,根據(jù)這篇教程中 How to check if a string is a valid URL in Golang? 提供的方法,寫了一個函數(shù):
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
}運行結(jié)果如圖:

使用 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)
}運行結(jié)果:

后面在研究這部分 verifier.go 源碼時,發(fā)現(xiàn)這個用了 govalidator 這個包,如圖:

于是,我們何不直接使用 govalidator 包來判斷一個字符串是否是 URL 呢?
使用 govalidator 包
govalidator 是一個針對字符串、結(jié)構(gòu)體和集合的驗證器和包?;?validator.js。
GitHub 地址: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)
}
運行結(jié)果如下:

正則表達式匹配
本來想自己寫正則表達式匹配的,然后發(fā)現(xiàn) govalidator 包的作者背后的原理也是用了正則表達式的,
然后就偷懶了,直接把他源碼中的部分集合到一個 main.go 函數(shù)中:
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"))
}
運行結(jié)果:

除了校驗 URL,這個包還提供了眾多的字符串校驗方法,例如校驗郵箱、信用卡格式、IP...

總結(jié)
數(shù)據(jù)校驗是每個程序員日常開發(fā)過程的一部分,尤其是在從事后端服務(wù)時,數(shù)據(jù)驗證必須嚴格,保持正確。
在這篇文章中,我們討論了如何在 Go 語言中正確驗證一個字符串是否是 URL,當(dāng)然利用了官方包和優(yōu)秀的第三方包,在實際過程中,可能我們?yōu)榱撕啽銜苯邮褂脛e人開發(fā)好的工具,但是在學(xué)習(xí)過程中,不妨也去思考別人實現(xiàn)的原理,結(jié)合實際業(yè)務(wù)需要,進而擴展成自己的工具包。
以上就是Golang實現(xiàn)驗證一個字符串是否為URL的詳細內(nèi)容,更多關(guān)于Golang驗證字符串是否為URL的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go標(biāo)準(zhǔn)庫http與fasthttp服務(wù)端性能對比場景分析
這篇文章主要介紹了Go標(biāo)準(zhǔn)庫http與fasthttp服務(wù)端性能比較,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
go mayfly開源項目代碼結(jié)構(gòu)設(shè)計
這篇文章主要為大家介紹了go mayfly開源項目代碼結(jié)構(gòu)設(shè)計詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
Go如何優(yōu)雅的關(guān)閉goroutine協(xié)程
本文將介紹首先為什么需要主動關(guān)閉goroutine,并介紹如何在Go語言中關(guān)閉goroutine的常見套路,包括傳遞終止信號和協(xié)程內(nèi)部捕捉終止信號,之后,文章列舉了需要主動關(guān)閉協(xié)程運行的常見場景,希望通過本文的介紹,讀者能夠掌握如何在適當(dāng)?shù)臅r候關(guān)閉goroutine2023-05-05
Go基于GORM 獲取當(dāng)前請求所執(zhí)行的 SQL 信息(思路詳解)
這篇文章主要介紹了Go基于GORM 獲取當(dāng)前請求所執(zhí)行的 SQL 信息(思路詳解),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
golang中結(jié)構(gòu)體嵌套接口的實現(xiàn)
本文主要介紹了golang中結(jié)構(gòu)體嵌套接口的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04

