应使用url.Values.Encode()拼接多参数,避免手拼字符串或对整个URL调用url.QueryEscape;后者会错误转义:、/、?导致400错误。

直接用 url.Values.Encode() 拼多参数,别手拼字符串,也别对整个 URL 调 url.QueryEscape——后者会把 :、/、? 全部转义,请求直接 400。
什么时候该用 url.QueryEscape
它只负责 ?key=value 里的 value 部分,比如用户输入的搜索词、API key、JSON 字符串等。行为严格遵循 application/x-www-form-urlencoded 规范:
- 空格 →
+(不是%20) - 中文、单引号、括号 →
%E4%B8%AD、%27、%28 -
/、:、@等在 query 值中合法的字符,默认不编码——这不是 bug,是规范要求
错误用法:url.QueryEscape("https://api.com?q=1") 会把 :// 和 ? 变成 %3A%2F%2F、%3F,服务端根本收不到有效路径。
多个参数必须用 url.Values.Encode()
手动拼 "?q=" + url.QueryEscape(q) + "&page=" + url.QueryEscape(p) 容易漏空值、错顺序、多加 &、忽略重复 key。标准库的 url.Values 自动处理所有边界情况:
立即学习“go语言免费学习笔记(深入)”;
-
v := url.Values{}创建映射 -
v.Set("q", "hello world")→ 键唯一,值自动编码 -
v.Add("tag", "go")→ 支持同名多值(如tag=go&tag=web) -
v.Encode()返回完整 query 字符串,空格变+,中文变%E4%B8%AD,且保证=和&不被误编码
示例:v.Set("script", "g.V().has('name', '张三')") → script=g.V().has(%27name%27%2C+%27%E5%BC%A0%E4%B8%89%27)
url.PathEscape 和 url.QueryEscape 别混用
路径段(如 /user/张三)和 query 值(如 ?q=张三)的编码逻辑完全不同:
-
url.QueryEscape("a/b c")→"a%2Fb+c"(/编码,空格变+) -
url.PathEscape("a/b c")→"a%2Fb%20c"(/编码,空格变%20) - 误用
QueryEscape编码路径会导致/变成%2F,API 认不出路由 - 误用
PathEscape编码 query 值会导致=、&全部被转义,服务端收到的是字面量,根本不会当 query 解析
真实需求:想把整个 URL 当作一个参数传(比如回调地址),那就该先 url.QueryEscape 整个 URL 字符串,而不是用 PathEscape。
解码时必须检查 error
url.QueryUnescape 对输入非常严格:残缺的 %AB、非十六进制字符如 %XZ、或混入不可见控制符,都会直接返回 error。常见来源包括:
- 日志里截断的 URL
- 前端没 encode 就发来的参数
- 第三方 API 返回的“看似编码”实则脏数据
安全做法:不要依赖 net/http 自动解码后还手动调一次 QueryUnescape——req.URL.Query().Get("q") 已经是解码后的结果,再解一次可能出错。只有当你从非标准来源(比如 header、body)拿到已编码字符串,且确认没被解析过,才手动 QueryUnescape。
最常被忽略的一点:Go 的 url.URL 在拼接时会自动标准化,但前提是传给 Path 的是原始未编码字符串、传给 RawQuery 的是已编码字符串。混用二者(比如把 %25 直接塞进 Path),就会触发双重转义,出现 %2525 这种问题。


















