Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Go net/url 包详解

概述

net/url 包实现了 URL 的解析和查询转义功能,遵循 RFC 3986 规范。该包提供了 URL 结构体用于表示解析后的 URL,以及用于处理查询参数的 Values 类型。包中的函数可以解析绝对 URL 和相对 URL,并对 URL 的各个组件进行转义和反转义操作。

重要说明

  • ✓ 实现 RFC 3986 URL 规范
  • ✓ 支持绝对 URL 和相对 URL 解析
  • ✓ 提供查询参数的编码和解码
  • ✓ 支持路径转义和反转义
  • ✓ 提供 URL 解析和构建功能
  • ✓ 支持用户信息(用户名/密码)
  • ✓ Go 1.0+ 引入,持续增强

URL 格式

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

非双斜杠格式

scheme:opaque[?query][#fragment]

包导入

import (
    "net/url"
)

基本使用

1. 解析 URL

package main

import (
    "fmt"
    "net/url"
)

func main() {
    rawURL := "https://user:pass@example.com:8080/path?q=hello#anchor"
    
    u, err := url.Parse(rawURL)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Scheme: %s\n", u.Scheme)
    fmt.Printf("Host: %s\n", u.Host)
    fmt.Printf("Path: %s\n", u.Path)
    fmt.Printf("Query: %s\n", u.RawQuery)
    fmt.Printf("Fragment: %s\n", u.Fragment)
}

运行结果:

Scheme: https
Host: example.com:8080
Path: /path
Query: q=hello
Fragment: anchor

2. 构建 URL

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme:   "https",
        Host:     "example.com",
        Path:     "/path/to/resource",
        RawQuery: "key=value&foo=bar",
        Fragment: "section1",
    }
    
    fmt.Printf("URL: %s\n", u.String())
}

运行结果:

URL: https://example.com/path/to/resource?key=value&foo=bar#section1

3. 处理查询参数

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 解析查询参数
    values, _ := url.ParseQuery("name=john&age=30&city=beijing")
    fmt.Printf("Name: %s\n", values.Get("name"))
    fmt.Printf("Age: %s\n", values.Get("age"))
    
    // 构建查询参数
    params := url.Values{}
    params.Add("q", "golang")
    params.Add("page", "1")
    
    fmt.Printf("Encoded: %s\n", params.Encode())
}

运行结果:

Name: john
Age: 30
Encoded: page=1&q=golang

一、函数(按 a-z 排序)

JoinPath

定义:

func JoinPath(base string, elem ...string) (result string, err error)

说明:

  • 功能:将路径元素连接到基础 URL 的路径
  • 参数
    • base - 基础 URL 字符串
    • elem - 路径元素切片
  • 返回
    • result - 连接后的 URL 字符串
    • err - 错误信息
  • 特点
    • 自动清理 ./../ 元素
    • 路径元素必须已经是转义形式
  • 版本:Go 1.19+

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    base := "https://example.com/api/v1"
    
    result, err := url.JoinPath(base, "users", "123")
    if err != nil {
        panic(err)
    }
    
    fmt.Println(result)
    // 输出:https://example.com/api/v1/users/123
    
    // 包含相对路径元素
    result2, _ := url.JoinPath(base, "..", "v2", "posts")
    fmt.Println(result2)
    // 输出:https://example.com/api/v2/posts
}

PathEscape

定义:

func PathEscape(s string) string

说明:

  • 功能:转义字符串以安全地用于 URL 路径段
  • 参数
    • s - 要转义的字符串
  • 返回:转义后的字符串
  • 特点
    • 替换特殊字符(包括 /)为 %XX 序列
    • 不将 + 转义为空格
  • 版本:Go 1.8+

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 包含特殊字符的路径
    path := "my/cool+blog&about,stuff"
    escaped := url.PathEscape(path)
    
    fmt.Printf("Original: %s\n", path)
    fmt.Printf("Escaped: %s\n", escaped)
    // 输出:my%2Fcool+blog&about%2Cstuff
    
    // 中文路径
    chinese := "文件/测试.txt"
    fmt.Printf("Chinese: %s\n", url.PathEscape(chinese))
    // 输出:%E6%96%87%E4%BB%B6/%E6%B5%8B%E8%AF%95.txt
}

PathUnescape

定义:

func PathUnescape(s string) (string, error)

说明:

  • 功能:执行 PathEscape 的逆转换
  • 参数
    • s - 转义的字符串
  • 返回
    • string - 反转义后的字符串
    • error - 错误信息
  • 特点
    • %AB 转换为字节 0xAB
    • + 转换为空格(与 QueryUnescape 的区别)
  • 版本:Go 1.8+

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    escaped := "my%2Fcool+blog&about%2Cstuff"
    
    unescaped, err := url.PathUnescape(escaped)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Escaped: %s\n", escaped)
    fmt.Printf("Unescaped: %s\n", unescaped)
    // 输出:my/cool+blog&about,stuff(+ 保持不变)
    
    // 对比 QueryUnescape
    queryUnescaped, _ := url.QueryUnescape(escaped)
    fmt.Printf("QueryUnescaped: %s\n", queryUnescaped)
    // 输出:my/cool blog&about,stuff(+ 变为空格)
}

QueryEscape

定义:

func QueryEscape(s string) string

说明:

  • 功能:转义字符串以安全地用于 URL 查询
  • 参数
    • s - 要转义的字符串
  • 返回:转义后的字符串
  • 特点
    • 将空格转义为 +
    • 其他特殊字符转义为 %XX
  • 用途:构建查询字符串

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 包含特殊字符的查询值
    query := "hello world & golang"
    escaped := url.QueryEscape(query)
    
    fmt.Printf("Original: %s\n", query)
    fmt.Printf("Escaped: %s\n", escaped)
    // 输出:hello+world+%26+golang
    
    // 中文查询
    chinese := "搜索内容"
    fmt.Printf("Chinese: %s\n", url.QueryEscape(chinese))
    // 输出:%E6%90%9C%E7%B4%A2%E5%86%85%E5%AE%B9
    
    // 构建完整查询 URL
    baseURL := "https://example.com/search"
    fullURL := baseURL + "?q=" + url.QueryEscape("golang tutorial")
    fmt.Printf("Full URL: %s\n", fullURL)
}

QueryUnescape

定义:

func QueryUnescape(s string) (string, error)

说明:

  • 功能:执行 QueryEscape 的逆转换
  • 参数
    • s - 转义的字符串
  • 返回
    • string - 反转义后的字符串
    • error - 错误信息
  • 特点
    • %AB 转换为字节 0xAB
    • + 转换为空格
  • 用途:解析查询字符串

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    escaped := "hello+world+%26+golang"
    
    unescaped, err := url.QueryUnescape(escaped)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Escaped: %s\n", escaped)
    fmt.Printf("Unescaped: %s\n", unescaped)
    // 输出:hello world & golang
    
    // 错误处理
    _, err = url.QueryUnescape("invalid%GG")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        // 输出:invalid URL escape "%GG"
    }
}

二、类型(按 a-z 排序)

Error

定义:

type Error struct {
    Op  string // 操作名称
    URL string // 出错的 URL
    Err error  // 具体错误
}

说明:

  • 功能:报告 URL 操作错误
  • 字段
    • Op - 操作名称(如 “parse”)
    • URL - 出错的 URL
    • Err - 具体错误信息

方法:

Error

定义:

func (e *Error) Error() string

说明:

  • 功能:实现 error 接口
  • 返回:错误消息字符串

Temporary

定义:

func (e *Error) Temporary() bool

说明:

  • 功能:报告错误是否为临时错误
  • 返回:布尔值

Timeout

定义:

func (e *Error) Timeout() bool

说明:

  • 功能:报告错误是否为超时错误
  • 返回:布尔值

Unwrap

定义:

func (e *Error) Unwrap() error

说明:

  • 功能:解包内部错误
  • 返回:内部错误

EscapeError

定义:

type EscapeError string

说明:

  • 功能:表示无效的 URL 转义序列
  • 用途:当 % 后未跟两个十六进制数字时返回

方法:

Error

定义:

func (e EscapeError) Error() string

说明:

  • 功能:实现 error 接口
  • 返回:错误消息

InvalidHostError

定义:

type InvalidHostError string

说明:

  • 功能:表示无效的主机名字符
  • 用途:当主机名包含无效字符时返回

方法:

Error

定义:

func (e InvalidHostError) Error() string

说明:

  • 功能:实现 error 接口
  • 返回:错误消息

URL

定义:

type URL struct {
    Scheme   string
    Opaque   string    // 编码后的不透明数据
    User     *Userinfo // 用户名和密码
    Host     string    // host 或 host:port
    Path     string    // 路径(解码后形式)
    RawPath  string    // 编码后的路径(可选)
    OmitHost bool      // 省略主机(Go 1.23+)
    ForceQuery bool    // 强制查询(Go 1.23+)
    RawQuery string    // 编码后的查询字符串,没有 '?'
    Fragment string    // 引用的片段,没有 '#'
    RawFragment string // 编码后的片段,没有 '#'(Go 1.23+)
}

说明:

  • 功能:表示解析后的 URL(技术上是 URI 引用)
  • 字段
    • Scheme - 协议方案(如 “http”、“https”)
    • Opaque - 不透明数据(用于非双斜杠 URL)
    • User - 用户信息(用户名/密码)
    • Host - 主机名或主机名:端口
    • Path - 路径(解码后形式)
    • RawPath - 编码后的路径(可选)
    • RawQuery - 编码后的查询字符串(不含 ?
    • Fragment - 片段标识符(不含 #
    • RawFragment - 编码后的片段(可选)

注意

  • Path 字段以解码形式存储:/%47%6f%2f 变成 /Go/
  • Host 字段包含主机和端口:"example.com:8080"
  • IPv6 地址必须用方括号括起:"[fe80::1]:80"

方法:

Parse

定义:

func Parse(rawURL string) (*URL, error)

说明:

  • 功能:解析原始 URL 字符串
  • 参数
    • rawURL - URL 字符串(可以是绝对或相对)
  • 返回
    • *URL - 解析后的 URL 对象
    • error - 错误信息
  • 用途:解析 URL 字符串

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 绝对 URL
    u1, _ := url.Parse("https://example.com:8080/path?query=value")
    fmt.Printf("Scheme: %s\n", u1.Scheme)
    fmt.Printf("Host: %s\n", u1.Host)
    fmt.Printf("Path: %s\n", u1.Path)
    
    // 相对 URL
    u2, _ := url.Parse("/relative/path")
    fmt.Printf("Relative Path: %s\n", u2.Path)
    
    // 包含用户信息
    u3, _ := url.Parse("ftp://user:pass@host.com/path")
    fmt.Printf("User: %s\n", u3.User.Username())
    
    // 错误处理
    _, err := url.Parse("://invalid")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
}

ParseRequestURI

定义:

func ParseRequestURI(rawURL string) (*URL, error)

说明:

  • 功能:解析 HTTP 请求中的 URL
  • 参数
    • rawURL - URL 字符串
  • 返回
    • *URL - 解析后的 URL 对象
    • error - 错误信息
  • 特点
    • 假设 URL 是绝对的或绝对路径
    • 假设没有 #fragment 后缀
    • 比 Parse 更严格

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 有效
    u1, _ := url.ParseRequestURI("/path/to/resource")
    fmt.Printf("Path: %s\n", u1.Path)
    
    // 有效
    u2, _ := url.ParseRequestURI("https://example.com/path")
    fmt.Printf("Host: %s\n", u2.Host)
    
    // 无效(包含 fragment)
    _, err := url.ParseRequestURI("/path#fragment")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
}

AppendBinary

定义:

func (u *URL) AppendBinary(b []byte) ([]byte, error)

说明:

  • 功能:将 URL 追加到字节切片
  • 参数
    • b - 目标字节切片
  • 返回
    • []byte - 追加后的字节切片
    • error - 错误信息

EscapedFragment

定义:

func (u *URL) EscapedFragment() string

说明:

  • 功能:返回转义后的 Fragment
  • 返回:转义后的片段字符串
  • 特点:当 RawFragment 是有效转义时返回 RawFragment

EscapedPath

定义:

func (u *URL) EscapedPath() string

说明:

  • 功能:返回转义后的路径
  • 返回:转义后的路径字符串
  • 特点
    • 当 RawPath 有效时返回 RawPath
    • 否则返回 Path 的转义形式
  • 用途:获取原始编码的路径

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u, _ := url.Parse("https://example.com/path%2Fwith%2Fslashes")
    
    fmt.Printf("Path: %s\n", u.Path)           // /path/with/slashes(解码后)
    fmt.Printf("EscapedPath: %s\n", u.EscapedPath()) // /path%2Fwith%2Fslashes(编码后)
}

Hostname

定义:

func (u *URL) Hostname() string

说明:

  • 功能:返回主机名(不含端口)
  • 返回:主机名字符串
  • 特点:自动去除端口号和方括号

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u1, _ := url.Parse("https://example.com:8080/path")
    fmt.Printf("Hostname: %s\n", u1.Hostname()) // example.com
    
    u2, _ := url.Parse("https://[::1]:8080/path")
    fmt.Printf("IPv6 Hostname: %s\n", u2.Hostname()) // ::1
}

IsAbs

定义:

func (u *URL) IsAbs() bool

说明:

  • 功能:检查 URL 是否为绝对 URL
  • 返回:布尔值
  • 判断标准:是否有 Scheme 字段

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u1 := &url.URL{Host: "example.com", Path: "path"}
    fmt.Printf("IsAbs: %v\n", u1.IsAbs()) // false
    
    u1.Scheme = "https"
    fmt.Printf("IsAbs: %v\n", u1.IsAbs()) // true
}

JoinPath

定义:

func (u *URL) JoinPath(elem ...string) *URL

说明:

  • 功能:将路径元素连接到现有路径
  • 参数
    • elem - 路径元素
  • 返回:新的 URL 对象
  • 特点
    • 自动清理 ./../
    • 路径元素必须已转义
  • 版本:Go 1.19+

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    base, _ := url.Parse("https://example.com/api/v1")
    
    // 连接路径
    result := base.JoinPath("users", "123")
    fmt.Printf("URL: %s\n", result.String())
    // 输出:https://example.com/api/v1/users/123
    
    // 包含相对路径
    result2 := base.JoinPath("..", "v2", "posts")
    fmt.Printf("URL: %s\n", result2.String())
    // 输出:https://example.com/api/v2/posts
}

MarshalBinary

定义:

func (u *URL) MarshalBinary() (text []byte, err error)

说明:

  • 功能:实现 encoding.BinaryMarshaler 接口
  • 返回
    • []byte - 二进制表示
    • error - 错误信息

Parse

定义:

func (u *URL) Parse(ref string) (*URL, error)

说明:

  • 功能:基于当前 URL 解析相对引用
  • 参数
    • ref - 相对 URL 引用
  • 返回
    • *URL - 解析后的绝对 URL
    • error - 错误信息
  • 用途:解析相对 URL

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    base, _ := url.Parse("https://example.com/path/to/page")
    
    // 解析相对 URL
    rel, _ := base.Parse("../other/page.html")
    fmt.Printf("Relative: %s\n", rel.String())
    // 输出:https://example.com/path/other/page.html
    
    // 解析绝对 URL
    abs, _ := base.Parse("https://other.com/link")
    fmt.Printf("Absolute: %s\n", abs.String())
    // 输出:https://other.com/link
}

Port

定义:

func (u *URL) Port() string

说明:

  • 功能:返回端口号
  • 返回:端口字符串
  • 特点:如果无端口则返回空字符串

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u1, _ := url.Parse("https://example.com:8080/path")
    fmt.Printf("Port: %s\n", u1.Port()) // 8080
    
    u2, _ := url.Parse("https://example.com/path")
    fmt.Printf("Port: '%s'\n", u2.Port()) // 空字符串
}

Query

定义:

func (u *URL) Query() Values

说明:

  • 功能:解析查询字符串为 Values
  • 返回:Values 对象
  • 用途:访问查询参数

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u, _ := url.Parse("https://example.com/search?q=golang&lang=zh&page=1")
    
    values := u.Query()
    fmt.Printf("Query: %s\n", values.Get("q"))      // golang
    fmt.Printf("Language: %s\n", values.Get("lang")) // zh
    fmt.Printf("Page: %s\n", values.Get("page"))     // 1
}

Redacted

定义:

func (u *URL) Redacted() string

说明:

  • 功能:返回去除密码信息的 URL 字符串
  • 返回:脱敏后的 URL
  • 用途:安全日志记录

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u, _ := url.Parse("https://user:password@example.com/path")
    
    fmt.Printf("Original: %s\n", u.String())
    // 输出:https://user:password@example.com/path
    
    fmt.Printf("Redacted: %s\n", u.Redacted())
    // 输出:https://user:xxxxx@example.com/path
}

RequestURI

定义:

func (u *URL) RequestURI() string

说明:

  • 功能:返回 HTTP 请求中的 URL 表示
  • 返回:请求 URI 字符串
  • 用途:构建 HTTP 请求行

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u, _ := url.Parse("https://example.com/path?query=value")
    
    fmt.Printf("RequestURI: %s\n", u.RequestURI())
    // 输出:/path?query=value
}

ResolveReference

定义:

func (u *URL) ResolveReference(ref *URL) *URL

说明:

  • 功能:解析相对 URL 引用
  • 参数
    • ref - 相对 URL 对象
  • 返回:解析后的绝对 URL
  • 用途:将相对 URL 转换为绝对 URL

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    base, _ := url.Parse("https://example.com/path/to/page")
    ref, _ := url.Parse("../other/file.html")
    
    resolved := base.ResolveReference(ref)
    fmt.Printf("Resolved: %s\n", resolved.String())
    // 输出:https://example.com/path/other/file.html
}

String

定义:

func (u *URL) String() string

说明:

  • 功能:返回 URL 的字符串表示
  • 返回:完整的 URL 字符串
  • 用途:序列化 URL

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme:   "https",
        Host:     "example.com:8080",
        Path:     "/path/to/resource",
        RawQuery: "key=value",
        Fragment: "section",
    }
    
    fmt.Printf("URL: %s\n", u.String())
    // 输出:https://example.com:8080/path/to/resource?key=value#section
}

UnmarshalBinary

定义:

func (u *URL) UnmarshalBinary(text []byte) error

说明:

  • 功能:实现 encoding.BinaryUnmarshaler 接口
  • 参数
    • text - 二进制数据
  • 返回:错误信息

Userinfo

定义:

type Userinfo struct {
    // 未导出字段
}

说明:

  • 功能:存储用户信息(用户名和密码)
  • 用途:URL 中的用户认证信息

函数:

User

定义:

func User(username string) *Userinfo

说明:

  • 功能:创建只有用户名的 Userinfo
  • 参数
    • username - 用户名
  • 返回*Userinfo 对象

UserPassword

定义:

func UserPassword(username, password string) *Userinfo

说明:

  • 功能:创建用户名和密码的 Userinfo
  • 参数
    • username - 用户名
    • password - 密码
  • 返回*Userinfo 对象

方法:

Password

定义:

func (u *Userinfo) Password() (string, bool)

说明:

  • 功能:获取密码
  • 返回
    • string - 密码
    • bool - 是否设置了密码

String

定义:

func (u *Userinfo) String() string

说明:

  • 功能:返回用户信息的字符串表示
  • 返回username:passwordusername

Username

定义:

func (u *Userinfo) Username() string

说明:

  • 功能:获取用户名
  • 返回:用户名字符串

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 创建用户信息
    user := url.User("john")
    fmt.Printf("User: %s\n", user.Username())
    
    // 创建带密码的用户信息
    userPass := url.UserPassword("john", "secret123")
    fmt.Printf("Username: %s\n", userPass.Username())
    
    pass, ok := userPass.Password()
    fmt.Printf("Password: %s, Set: %v\n", pass, ok)
    
    // 在 URL 中使用
    u := &url.URL{
        Scheme: "https",
        Host:   "example.com",
        User:   userPass,
        Path:   "/path",
    }
    fmt.Printf("URL: %s\n", u.String())
    // 输出:https://john:secret123@example.com/path
}

Values

定义:

type Values map[string][]string

说明:

  • 功能:键值对映射,用于存储查询参数
  • 底层map[string][]string
  • 用途:表示 URL 查询参数或表单数据
  • 特点:一个键可以对应多个值

函数:

ParseQuery

定义:

func ParseQuery(query string) (Values, error)

说明:

  • 功能:解析查询字符串为 Values
  • 参数
    • query - 查询字符串(不含 ?
  • 返回
    • Values - 解析后的键值对
    • error - 错误信息
  • 格式:支持 key=value&key2=value2 格式

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 解析查询字符串
    values, err := url.ParseQuery("name=john&age=30&hobbies=reading&hobbies=coding")
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Name: %s\n", values.Get("name"))
    fmt.Printf("Age: %s\n", values.Get("age"))
    fmt.Printf("Hobbies: %v\n", values["hobbies"])
    // 输出:[reading coding]
    
    // 错误处理
    _, err = url.ParseQuery("invalid=percent%GG")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
}

方法:

Add

定义:

func (v Values) Add(key, value string)

说明:

  • 功能:添加键值对
  • 参数
    • key - 键
    • value - 值
  • 特点:保留已有值,添加为新值

示例:

values := url.Values{}
values.Add("hobby", "reading")
values.Add("hobby", "coding")
// values["hobby"] = ["reading", "coding"]

Del

定义:

func (v Values) Del(key string)

说明:

  • 功能:删除指定键的所有值
  • 参数
    • key - 要删除的键

Encode

定义:

func (v Values) Encode() string

说明:

  • 功能:编码为查询字符串
  • 返回:URL 编码的字符串
  • 格式key1=value1&key2=value2
  • 用途:构建查询字符串或表单数据

示例:

package main

import (
    "fmt"
    "net/url"
)

func main() {
    params := url.Values{}
    params.Add("q", "golang")
    params.Add("page", "1")
    params.Add("tags", "go")
    params.Add("tags", "programming")
    
    encoded := params.Encode()
    fmt.Printf("Encoded: %s\n", encoded)
    // 输出:page=1&q=golang&tags=go&tags=programming
}

Get

定义:

func (v Values) Get(key string) string

说明:

  • 功能:获取第一个值
  • 参数
    • key - 键
  • 返回:第一个值或空字符串
  • 特点:如果有多个值,只返回第一个

示例:

values := url.Values{}
values.Add("color", "red")
values.Add("color", "blue")
fmt.Println(values.Get("color")) // 输出:red

Has

定义:

func (v Values) Has(key string) bool

说明:

  • 功能:检查键是否存在
  • 参数
    • key - 要检查的键
  • 返回:布尔值
  • 版本:Go 1.17+

示例:

values := url.Values{}
values.Add("key", "value")
fmt.Println(values.Has("key"))   // true
fmt.Println(values.Has("other")) // false

Set

定义:

func (v Values) Set(key, value string)

说明:

  • 功能:设置键值对
  • 参数
    • key - 键
    • value - 值
  • 特点:替换已有值

示例:

values := url.Values{}
values.Set("color", "red")
values.Set("color", "blue") // 替换 red
fmt.Println(values.Get("color")) // 输出:blue

三、典型示例

示例 1:URL 解析和构建

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 解析完整 URL
    rawURL := "https://user:pass@example.com:8080/path/to/resource?key=value#fragment"
    u, _ := url.Parse(rawURL)
    
    fmt.Printf("Scheme: %s\n", u.Scheme)
    fmt.Printf("User: %s\n", u.User.Username())
    fmt.Printf("Host: %s\n", u.Host)
    fmt.Printf("Hostname: %s\n", u.Hostname())
    fmt.Printf("Port: %s\n", u.Port())
    fmt.Printf("Path: %s\n", u.Path)
    fmt.Printf("Query: %s\n", u.RawQuery)
    fmt.Printf("Fragment: %s\n", u.Fragment)
    
    // 构建 URL
    u2 := &url.URL{
        Scheme:   "https",
        Host:     "example.com",
        Path:     "/api/v1/users",
        RawQuery: "page=1&limit=10",
        Fragment: "results",
    }
    fmt.Printf("\nBuilt URL: %s\n", u2.String())
}

运行结果:

Scheme: https
User: user
Host: example.com:8080
Hostname: example.com
Port: 8080
Path: /path/to/resource
Query: key=value
Fragment: fragment

Built URL: https://example.com/api/v1/users?page=1&limit=10#results

示例 2:查询参数处理

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // 解析查询参数
    values, _ := url.ParseQuery("name=john&age=30&hobbies=reading&hobbies=coding")
    
    fmt.Println("=== Get Values ===")
    fmt.Printf("Name: %s\n", values.Get("name"))
    fmt.Printf("Age: %s\n", values.Get("age"))
    fmt.Printf("Hobbies: %v\n", values["hobbies"])
    
    // 构建查询参数
    params := url.Values{}
    params.Set("q", "golang tutorial")
    params.Add("page", "1")
    params.Add("page", "2") // 多值
    params.Add("sort", "date")
    
    fmt.Println("\n=== Encode Values ===")
    fmt.Printf("Encoded: %s\n", params.Encode())
    
    // 检查和删除
    fmt.Println("\n=== Check and Delete ===")
    fmt.Printf("Has 'page': %v\n", params.Has("page"))
    params.Del("page")
    fmt.Printf("Has 'page' after delete: %v\n", params.Has("page"))
}

运行结果:

=== Get Values ===
Name: john
Age: 30
Hobbies: [reading coding]

=== Encode Values ===
Encoded: page=1&page=2&q=golang+tutorial&sort=date

=== Check and Delete ===
Has 'page': true
Has 'page' after delete: false

示例 3:URL 转义和反转义

package main

import (
    "fmt"
    "net/url"
)

func main() {
    // Query 转义
    query := "hello world & golang"
    escaped := url.QueryEscape(query)
    unescaped, _ := url.QueryUnescape(escaped)
    
    fmt.Printf("QueryEscape:\n")
    fmt.Printf("  Original: %s\n", query)
    fmt.Printf("  Escaped: %s\n", escaped)
    fmt.Printf("  Unescaped: %s\n\n", unescaped)
    
    // Path 转义
    path := "path/with/slashes & special"
    pathEscaped := url.PathEscape(path)
    pathUnescaped, _ := url.PathUnescape(pathEscaped)
    
    fmt.Printf("PathEscape:\n")
    fmt.Printf("  Original: %s\n", path)
    fmt.Printf("  Escaped: %s\n", pathEscaped)
    fmt.Printf("  Unescaped: %s\n\n", pathUnescaped)
    
    // 对比 + 的处理
    test := "a+b"
    qUnescaped, _ := url.QueryUnescape(url.QueryEscape(test))
    pUnescaped, _ := url.PathUnescape(url.PathEscape(test))
    
    fmt.Printf("Plus handling:\n")
    fmt.Printf("  Query: %s -> %s\n", test, qUnescaped)
    fmt.Printf("  Path: %s -> %s\n", test, pUnescaped)
}

运行结果:

QueryEscape:
  Original: hello world & golang
  Escaped: hello+world+%26+golang
  Unescaped: hello+world+%26+golang

PathEscape:
  Original: path/with/slashes & special
  Escaped: path%2Fwith%2Fslashes%20%26%20special
  Unescaped: path/with/slashes & special

Plus handling:
  Query: a+b -> a+b
  Path: a+b -> a+b

示例 4:相对 URL 解析

package main

import (
    "fmt"
    "net/url"
)

func main() {
    base, _ := url.Parse("https://example.com/path/to/page.html")
    
    // 解析相对 URL
    testCases := []string{
        "other.html",
        "../other/file.html",
        "/absolute/path",
        "?query=param",
        "#fragment",
        "https://other.com/link",
    }
    
    for _, tc := range testCases {
        ref, _ := url.Parse(tc)
        resolved := base.ResolveReference(ref)
        fmt.Printf("%-25s -> %s\n", tc, resolved.String())
    }
}

运行结果:

other.html                -> https://example.com/path/to/other.html
../other/file.html        -> https://example.com/path/other/file.html
/absolute/path            -> https://example.com/absolute/path
?query=param              -> https://example.com/path/to/page.html?query=param
#fragment                 -> https://example.com/path/to/page.html#fragment
https://other.com/link    -> https://other.com/link

示例 5:URL 路径连接(Go 1.19+)

package main

import (
    "fmt"
    "net/url"
)

func main() {
    base := "https://example.com/api/v1"
    
    // 连接路径
    result1, _ := url.JoinPath(base, "users", "123")
    fmt.Println(result1)
    // https://example.com/api/v1/users/123
    
    // 包含相对路径元素
    result2, _ := url.JoinPath(base, "..", "v2", "posts")
    fmt.Println(result2)
    // https://example.com/api/v2/posts
    
    // 使用 URL 对象
    u, _ := url.Parse(base)
    result3 := u.JoinPath("resources", "{id}")
    fmt.Println(result3.String())
    // https://example.com/api/v1/resources/{id}
}

运行结果:

https://example.com/api/v1/users/123
https://example.com/api/v2/posts
https://example.com/api/v1/resources/{id}

示例 6:构建搜索 URL

package main

import (
    "fmt"
    "net/url"
)

func buildSearchURL(baseURL, query string, page int, filters []string) string {
    u, _ := url.Parse(baseURL)
    
    // 设置查询参数
    params := u.Query()
    params.Set("q", query)
    params.Set("page", fmt.Sprintf("%d", page))
    
    // 添加多个过滤器
    for _, filter := range filters {
        params.Add("filter", filter)
    }
    
    u.RawQuery = params.Encode()
    return u.String()
}

func main() {
    baseURL := "https://example.com/search"
    
    url1 := buildSearchURL(baseURL, "golang", 1, []string{"go", "programming"})
    fmt.Println(url1)
    
    url2 := buildSearchURL(baseURL, "rust", 2, []string{"systems"})
    fmt.Println(url2)
}

运行结果:

https://example.com/search?filter=go&filter=programming&page=1&q=golang
https://example.com/search?filter=systems&page=2&q=rust

示例 7:URL 安全日志

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/url"
)

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 使用 Redacted 避免泄露密码
        if r.URL != nil {
            log.Printf("%s %s", r.Method, r.URL.Redacted())
        }
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello"))
    })
    
    handler := loggingMiddleware(mux)
    
    // 模拟请求
    req, _ := http.NewRequest("GET", "https://user:secret@example.com/path", nil)
    w := &mockResponseWriter{}
    handler.ServeHTTP(w, req)
}

type mockResponseWriter struct{}

func (m *mockResponseWriter) Header() http.Header {
    return http.Header{}
}

func (m *mockResponseWriter) Write(b []byte) (int, error) {
    return len(b), nil
}

func (m *mockResponseWriter) WriteHeader(statusCode int) {}

运行结果:

GET https://user:xxxxx@example.com/path

示例 8:OAuth 回调 URL 处理

package main

import (
    "fmt"
    "net/url"
)

func handleOAuthCallback(callbackURL string) (code, state string, err error) {
    u, err := url.Parse(callbackURL)
    if err != nil {
        return "", "", err
    }
    
    params := u.Query()
    code = params.Get("code")
    state = params.Get("state")
    
    if code == "" {
        return "", "", fmt.Errorf("missing code parameter")
    }
    
    return code, state, nil
}

func main() {
    callback := "https://myapp.com/callback?code=abc123&state=xyz789"
    
    code, state, err := handleOAuthCallback(callback)
    if err != nil {
        panic(err)
    }
    
    fmt.Printf("Code: %s\n", code)
    fmt.Printf("State: %s\n", state)
}

运行结果:

Code: abc123
State: xyz789

四、最佳实践

1. 始终检查错误

// ✓ 正确:检查解析错误
u, err := url.Parse(rawURL)
if err != nil {
    return err
}

// ✗ 错误:忽略错误
u, _ := url.Parse(rawURL)

2. 使用 Query 方法获取参数

// ✓ 正确:使用 Query 方法
u, _ := url.Parse("https://example.com?a=1&b=2")
params := u.Query()
value := params.Get("a")

// ✗ 错误:手动解析 RawQuery
// 不要手动分割字符串

3. 正确转义查询参数

// ✓ 正确:使用 QueryEscape
query := "hello world"
encoded := url.QueryEscape(query)
fullURL := "https://example.com/search?q=" + encoded

// ✗ 错误:不转义特殊字符
fullURL := "https://example.com/search?q=" + query

4. 使用 Values 构建参数

// ✓ 正确:使用 Values
params := url.Values{}
params.Add("q", "golang")
params.Add("page", "1")
fullURL := "https://example.com/search?" + params.Encode()

// ✗ 错误:手动拼接
fullURL := "https://example.com/search?q=golang&page=1"

5. 使用 EscapedPath 获取原始路径

// ✓ 正确:需要原始编码路径时使用 EscapedPath
u, _ := url.Parse("https://example.com/path%2Fwith%2Fslashes")
originalPath := u.EscapedPath() // /path%2Fwith%2Fslashes

// ✗ 错误:Path 是解码后的
decodedPath := u.Path // /path/with/slashes

6. 使用 Redacted 记录日志

// ✓ 正确:使用 Redacted 避免泄露密码
log.Printf("Request to: %s", u.Redacted())

// ✗ 错误:直接记录完整 URL
log.Printf("Request to: %s", u.String()) // 可能包含密码

7. 使用 JoinPath 连接路径

// ✓ 正确:使用 JoinPath(Go 1.19+)
base := "https://example.com/api"
result, _ := url.JoinPath(base, "v1", "users")

// ✗ 错误:手动拼接(可能出错)
result := base + "/v1/users"

8. 处理多值参数

// ✓ 正确:使用 Add 添加多值
params := url.Values{}
params.Add("tags", "go")
params.Add("tags", "programming")

// ✗ 错误:使用 Set 会覆盖
params.Set("tags", "go")
params.Set("tags", "programming") // 覆盖 go

五、与其他包配合

1. 与 net/http 配合

import (
    "net/http"
    "net/url"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // 获取查询参数
    query := r.URL.Query()
    name := query.Get("name")
    
    // 重定向
    redirectURL, _ := url.Parse("/success")
    params := redirectURL.Query()
    params.Set("user", name)
    redirectURL.RawQuery = params.Encode()
    
    http.Redirect(w, r, redirectURL.String(), http.StatusFound)
}

2. 与 encoding/json 配合

import (
    "encoding/json"
    "net/url"
)

type APIResponse struct {
    URL     string `json:"url"`
    Success bool   `json:"success"`
}

func parseResponse(data []byte) (*url.URL, error) {
    var resp APIResponse
    if err := json.Unmarshal(data, &resp); err != nil {
        return nil, err
    }
    
    return url.Parse(resp.URL)
}

3. 与 context 配合

import (
    "context"
    "net/http"
    "net/url"
    "time"
)

func fetchWithTimeout(urlStr string, timeout time.Duration) (*url.URL, error) {
    ctx, cancel := context.WithTimeout(context.Background(), timeout)
    defer cancel()
    
    client := &http.Client{
        Timeout: timeout,
    }
    
    req, _ := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    return resp.Request.URL, nil
}

六、快速参考

函数速查

函数功能返回
Parse(rawURL)解析 URL*URL, error
ParseRequestURI(rawURL)解析请求 URI*URL, error
ParseQuery(query)解析查询字符串Values, error
QueryEscape(s)转义查询字符串string
QueryUnescape(s)反转义查询string, error
PathEscape(s)转义路径string
PathUnescape(s)反转义路径string, error
JoinPath(base, ...)连接路径string, error

URL 字段速查

字段说明示例
Scheme协议https
Host主机:端口example.com:8080
Path路径(解码)/path/to/file
RawQuery查询(无 ?key=value
Fragment片段(无 #section1
User用户信息user:pass

Values 方法速查

方法功能
Get(key)获取第一个值
Set(key, value)设置值(覆盖)
Add(key, value)添加值(保留已有)
Del(key)删除键
Has(key)检查键是否存在
Encode()编码为字符串

URL 方法速查

方法功能
String()URL 字符串
Query()查询参数
Hostname()主机名(无端口)
Port()端口号
EscapedPath()转义的路径
Parse(ref)解析相对引用
ResolveReference(ref)解析相对 URL
JoinPath(...)连接路径
Redacted()脱敏 URL
IsAbs()是否绝对 URL

七、注意事项

1. Path 是解码形式

// Path 存储解码形式
u, _ := url.Parse("https://example.com/path%2Fwith%2Fslashes")
fmt.Println(u.Path)        // /path/with/slashes
fmt.Println(u.EscapedPath()) // /path%2Fwith%2Fslashes

2. QueryUnescape vs PathUnescape

// QueryUnescape 将 + 转为空格
url.QueryUnescape("a+b") // "a b"

// PathUnescape 保持 + 不变
url.PathUnescape("a+b") // "a+b"

3. Host 包含端口

u, _ := url.Parse("https://example.com:8080/path")
fmt.Println(u.Host)     // example.com:8080
fmt.Println(u.Hostname()) // example.com
fmt.Println(u.Port())   // 8080

4. 相对 URL 解析

// Parse 可以解析相对 URL
u, _ := url.Parse("/relative/path")
fmt.Println(u.IsAbs()) // false

// 需要基础 URL 来解析
base, _ := url.Parse("https://example.com")
resolved := base.ResolveReference(u)
fmt.Println(resolved.String()) // https://example.com/relative/path

5. 多值参数

// Values 支持多值
params := url.Values{}
params.Add("tag", "go")
params.Add("tag", "golang")
fmt.Println(params["tag"]) // [go golang]

6. 空值处理

// Get 返回空字符串如果键不存在
params := url.Values{}
fmt.Println(params.Get("nonexistent")) // ""
fmt.Println(params.Has("nonexistent")) // false

7. 特殊字符处理

// 空格在查询中转义为 +
url.QueryEscape("hello world") // "hello+world"

// 路径中的空格转义为 %20
url.PathEscape("hello world") // "hello%20world"

8. IPv6 地址

// IPv6 地址在 Host 中用方括号括起
u, _ := url.Parse("http://[::1]:8080/path")
fmt.Println(u.Host)     // [::1]:8080
fmt.Println(u.Hostname()) // ::1

最后更新: 2026-04-05
Go 版本: Go 1.0+(JoinPath 为 Go 1.19+)
包文档: https://pkg.go.dev/net/url
相关 RFC: RFC 3986 (URI Syntax)