如何在Go语言中检查主机名/URL.Hostname是否为有效域名?匹配.NET的URI.CheckHostName
Nice question! Replicating .NET's URI.CheckHostName exact behavior in Go requires implementing its specific validation rules, since Go's standard library doesn't include a direct equivalent out of the box. Let's break this down step by step, including how to validate a URL's Hostname() value.
Understanding .NET's URI.CheckHostName Behavior
First, let's recap what .NET's method does: it inspects a hostname and returns a UriHostNameType enum with these possible values:
Invalid: The hostname doesn't meet any valid format rulesIPv4: The hostname is a valid IPv4 addressIPv6: The hostname is a valid IPv6 addressDns: The hostname is a valid DNS domain (including internationalized domain names, or IDNs)
Our goal is to mirror this exact logic in Go.
Implementing the Equivalent in Go
We'll start by defining an enum matching .NET's type, then build a validation function that covers all the rules.
Step 1: Define the Hostname Type Enum
package main import ( "net" "strings" "golang.org/x/net/idna" ) // UriHostNameType matches .NET's UriHostNameType enum values type UriHostNameType int const ( UriHostNameTypeInvalid UriHostNameType = iota UriHostNameTypeIPv4 UriHostNameTypeIPv6 UriHostNameTypeDns )
Step 2: Build the Validation Function
This function will check for IPv4/IPv6 first, then validate DNS domain rules (including IDN support via Punycode conversion):
func CheckHostName(host string) UriHostNameType { // Handle empty or whitespace-only hostnames if strings.TrimSpace(host) == "" { return UriHostNameTypeInvalid } // Check if it's a valid IPv4 or IPv6 address ip := net.ParseIP(host) if ip != nil { if ip.To4() != nil { return UriHostNameTypeIPv4 } return UriHostNameTypeIPv6 } // Convert internationalized domain names (IDNs) to ASCII Punycode asciiHost, err := idna.ToASCII(host) if err != nil { return UriHostNameTypeInvalid } // Validate DNS domain structure rules // Rule 1: No leading/trailing dots or consecutive dots if strings.HasPrefix(asciiHost, ".") || strings.HasSuffix(asciiHost, ".") || strings.Contains(asciiHost, "..") { return UriHostNameTypeInvalid } // Rule 2: Total domain length can't exceed 253 characters if len(asciiHost) > 253 { return UriHostNameTypeInvalid } // Rule 3: Validate each domain label (split by dots) labels := strings.Split(asciiHost, ".") for _, label := range labels { // Labels must be 1-63 characters long if len(label) == 0 || len(label) > 63 { return UriHostNameTypeInvalid } // Labels can't start or end with a hyphen if strings.HasPrefix(label, "-") || strings.HasSuffix(label, "-") { return UriHostNameTypeInvalid } // Labels can only contain alphanumeric characters or hyphens for _, c := range label { if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-') { return UriHostNameTypeInvalid } } } // All checks passed: it's a valid DNS domain return UriHostNameTypeDns }
Validating a URL's Hostname() in Go
To validate the hostname extracted from a URL, use Go's net/url package to parse the URL, then pass the Hostname() result to our CheckHostName function. This automatically handles port numbers and IPv6 bracket stripping (matching .NET's URI.Host behavior):
import "net/url" func ValidateURLHostname(urlStr string) UriHostNameType { parsedURL, err := url.Parse(urlStr) if err != nil { return UriHostNameTypeInvalid } // Hostname() removes ports and IPv6 brackets for us hostname := parsedURL.Hostname() return CheckHostName(hostname) }
Test Cases to Verify Behavior
Here are some test cases to ensure our implementation matches .NET's behavior:
func main() { testCases := []struct { host string expected UriHostNameType }{ {"example.com", UriHostNameTypeDns}, {"192.168.1.1", UriHostNameTypeIPv4}, {"::1", UriHostNameTypeIPv6}, {"例子.com", UriHostNameTypeDns}, // IDN (international domain) {"example..com", UriHostNameTypeInvalid}, {"-example.com", UriHostNameTypeInvalid}, {"example-.com", UriHostNameTypeInvalid}, {"a" + strings.Repeat("a", 63) + ".com", UriHostNameTypeInvalid}, // Label too long {"[::1]", UriHostNameTypeInvalid}, // Bracketed IPv6 is invalid (use URL.Hostname() to fix) {"", UriHostNameTypeInvalid}, } for _, tc := range testCases { result := CheckHostName(tc.host) status := "PASS" if result != tc.expected { status = "FAIL" } println(status, ":", tc.host, "→ expected", tc.expected, "got", result) } }
内容的提问来源于stack exchange,提问作者user6216224

