杂记 › 技术笔记 › 网址里哪一段会到服务器?六段拆分与 %20、+、二次编码

网址里哪一段会到服务器?六段拆分与 %20、+、二次编码

日志里找不到页面跳转时带的那段锚点,中文搜索词在日志里变成一串百分号,密钥里的加号莫名变成空格——这三件事都指向同一个源头:一条网址并不是一整块发给服务器的,它被切成六段,每一段的命运不一样。

六段分别在哪里

段本文实测用的地址会不会发给服务器
协议 schemehttp决定端口与是否加密
主机 host127.0.0.1会
端口 port8198会;省略时按协议取默认值,http 是 80、https 是 443
路径 path/a/b/c会
查询 queryx=1&y=2会,带的是编码原文
锚点 fragmentsection-3不会

实测一:锚点根本没出浏览器

在本地起了一个最小服务,把收到的字段原样打印出来。请求 http://127.0.0.1:8198/a/b/c?x=1&y=2#section-3 时,服务端记录的是 REQUEST_URI 等于 /a/b/c?x=1&y=2,QUERY_STRING 等于 x=1&y=2,解析后拿到 x=1、y=2,而 section-3 哪里都没有出现。所以后台日志查不到锚点不是字段丢了,是它从来不上网:页内定位、前端路由的 hash 都由浏览器自己处理。

实测二:加号能当空格,反过来不行

同一组请求里,q=a+b+c 和 q=a%20b%20c 在服务端都解析成 a b c;而 token=a%2Bb 解析出来是字面的 a+b,写成 token=a+b 得到的却是「a b」。区别在于查询串的解码规则把加号当成空格的一种写法,所以你要传一个真正的加号就必须写成 %2B。直接拼上去会被解成空格,很多「密钥莫名不对」的排查最后就落在这一个字符上。

实测三:中文与二次编码

请求 q=%E4%B8%AD%E6%96%87,QUERY_STRING 里看到的是编码原文,而解析后的参数已经是中文。如果同一个串被编码了两遍,地址里就成 q=%25E4%25B8%25AD,服务端解一次得到的是 %E4%B8%AD——仍然带着百分号,说明只解了一层。这类现象没有玄学,唯一可靠的解法是数清楚自己解了几次:传输层解一次,业务层若还要再解就得显式约定。

实测四:同名键、没有键的值、非法转义

重复的键名不会自动变成数组。?q=a&q=b 解析后只剩最后一个值 b;要同时拿到两个,得写成 q[]=a&q[]=b,服务端才给出含两项的数组。只有值没有键的片段更干脆,?=onlyvalue&k= 解析出来只有一个空字符串的 k,那个孤零零的 onlyvalue 直接消失,任何代码都取不到它。

键名里也能藏编码。?a=1&%20b=2&c=%20 这一串,服务端解出的键是 a 和 b——前导空格被吃掉,而值末尾的 %20 老实变成一个空格保留下来。非法写法同样不报错:q=100%、q=a%zzb 都原样到了服务端,解析结果里仍然是 100% 和 a%zzb。这只说明本地这个解析器宽容——百分号本该由两个十六进制字符收尾,写成裸 % 时不能指望链路上每个中间件都用同样的方式放过它。真要传一个字面的百分号,就写 %25,实测 q=100%25 解析出来是 100%。

哪些字符在查询值里必须转义

要盯的是那几个承担结构职责的字符:& 会被当成分隔符,= 会被当成键值分界,+ 会被当成空格,# 会当场把整条查询截断,% 是所有转义的起始符。实测把 q=%3D%2B%26%25 发出去,服务端解出的是 =+&% 四个字符,一个不多一个不少,说明一次解码就还原到位。斜杠反而不用管,?path=/x/y 原样通过。

路径段是另一套:# 一旦以本来面目出现,它后面的内容全部留在浏览器里;写成 %23 就只是路径里的三个字符,实测 /a%23b/c?q=1 到服务端的 REQUEST_URI 仍是 /a%23b/c,查询照常解析。同类地,/a%2Fb/c 里的 %2F 会保持编码态传到服务端,是否被解成一个额外的路径分隔,就交给各框架的路由规则决定了——这正是「路径里带斜杠要不要转义」不能靠猜的原因。

表单体和查询串不是同一套习惯

以 application/x-www-form-urlencoded 提交时,请求体里的编码规则和查询串一致,空格同样写成 +;换成 JSON 请求体之后,这些字符由字符串本身承载,不需要百分号转义。把查询串的编码习惯带进 JSON,就会存下带 %20 的脏数据。同一段中文,走查询要写成 %E4%B8%AD%E6%96%87,走 JSON 只需要正确的 UTF-8 编码。

拆分怎么做省事

排查时我习惯把整条地址粘进 URL解析工具,让它把六段列出来,顺带把 QUERY_STRING 原文与解析后的参数并排看清;需要手工往某个值里塞中文或特殊字符时,用 URL编码解码 生成转义结果,别靠肉眼拼百分号,长度一长必错。

Q:路径里的中文和查询里的中文编码规则一样吗

都用百分号编码,但允许的字符集不同:路径段里有若干字符可以原样出现,查询串则要把当分隔符用的等号、与号转义掉。整串交给标准编码函数,比逐字符判断安全。

Q:为什么日志里存的是编码态

因为访问日志记的是 QUERY_STRING 原文,解码发生在框架解析参数的那一步。上面实测输出的同一行里,两个字段分别就是编码原文和解码结果,可以直接对照。

Q:hash 路由的页面切换能被后端统计到吗

不能,锚点不出浏览器。要按页面维度统计,就得让不同视图有不同的路径或查询参数,这也是前端路由从 hash 模式改走 history 模式的主要原因。

Q:用 hash 还是 query 存页面状态

看要不要离开浏览器。改 hash 不会发出请求,不进服务端日志、不被爬虫当成不同页面,适合纯前端的展开、定位、分页游标;一旦这个值需要服务端读到——筛选条件、分享链接、埋点维度——就必须放进路径或查询,因为实测里 REQUEST_URI 从头到尾不含 # 之后的任何东西。

📎 相关文章

身份证最后一位为什么是 X?前 17 位加权取模算出的校验位 2026-10-19 子网掩码写成 /22 能带多少台设备?主机位、网络地址与广播地址 2026-10-19 排版稿没内容可填?占位文本怎么写才不像模板 2026-10-15 两列数据要列出所有组合?分列合并与笛卡尔积 2026-10-15 测试上传限制要多大文件?按需造一个指定大小的 2026-10-15