动态

详情 返回 返回

如何在 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)

}

image.png

使用 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
}

運行結果如圖:

image.png

使用 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)

}

運行結果:

image.png

後面在研究這部分 verifier.go 源碼時,發現這個用了 govalidator 這個包,如圖:

image.png

於是,我們何不直接使用 govalidator 包來判斷一個字符串是否是 URL 呢?

使用 govalidator

govalidator 是一個針對字符串、結構體和集合的驗證器和包。基於 validator.js。

GitHub 地址:https://github.com/asaskevich/govalidator , 目前收穫了 5.7k 的 star

安裝:go get github.com/asaskevich/govalidator

image.png

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)
}

運行結果如下:

image.png

正則表達式匹配

本來想自己寫正則表達式匹配的,然後發現 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"))
}

運行結果:

image.png

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

image.png

總結

數據校驗是每個程序員日常開發過程的一部分,尤其是在從事後端服務時,數據驗證必須嚴格,保持正確。

在這篇文章中,我們討論瞭如何在 Go 語言中正確驗證一個字符串是否是 URL,當然利用了官方包和優秀的第三方包,在實際過程中,可能我們為了簡便會直接使用別人開發好的工具,但是在學習過程中,不妨也去思考別人實現的原理,結合實際業務需要,進而擴展成自己的工具包。

希望本文能對你有所幫助,如果喜歡本文,可以點個關注.

下一篇文章見!宇宙古今無有窮期,一生不過須臾,當思奮爭。

參考鏈接:

  • url-verifier: A Go library for URL validation and verification: does this URL actually work?
  • How To Validate Url In Go
  • govalidator: [Go] Package of validators and sanitizers for strings, numerics, slices and structs

Add a new 评论

Some HTML is okay.