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- 出错的 URLErr- 具体错误信息
方法:
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- 解析后的绝对 URLerror- 错误信息
- 用途:解析相对 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:password或username
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)