Express解析request.query百分号编码异常如何获取原始传参
这个报错和Express、底层的qs库没有关系,本质是你发送的URL不符合百分号编码规范。
按照编码规则,%是转义序列的起始标识,只要出现在URL里就必须被编码为%25,否则解析器会默认把%和它后面紧跟的两个字符当做一个转义单元处理。你发的请求http://localhost/resource?offer=20% discount里,%后面直接跟了空格,不满足“%后接两位十六进制字符”的合法转义规则,自然会解析出错,调用decodeURIComponent也会报URI格式错误。
另外你提到的“浏览器不自动编码%”的疑惑,核心是浏览器的自动编码只作用于受控场景:比如原生表单提交、调用fetch/XMLHttpRequest时传入参数对象由API自动拼接、地址栏输入内容后回车由浏览器自动规整。如果你是手动拼接URL字符串直接写了未转义的%、或者用接口测试工具手动输入了带裸%的地址,客户端不会主动帮你修正非法格式,会直接把错误的请求发出去。
顺带提一句,你贴的解析结果是{ badge: '20%%20discount' },参数名从offer变成了badge,大概率是笔误或者你代码里有其他逻辑改写了query,默认解析逻辑不会随便修改参数名。
分两种场景处理:
场景1:你能控制请求发送端(推荐)
这是最彻底的解决方案,从源头保证URL格式合法,不需要改服务端任何代码。
所有query参数在拼接前统一做编码,不要手动拼接带特殊字符的裸字符串:
- 前端发请求优先用
URLSearchParams处理参数,它会自动完成所有特殊字符的转义:
const params = new URLSearchParams() params.append('offer', '20% discount') // 最终生成的query是 offer=20%25%20discount,Express默认解析就能直接拿到正确的"20% discount" fetch(`/resource?${params.toString()}`)
- 如果要手动拼接URL,记得对每个参数的键和值单独调用
encodeURIComponent转义:
const offerValue = encodeURIComponent('20% discount') // 转义结果为20%25%20discount const requestUrl = `/resource?offer=${offerValue}`
场景2:无法控制请求端,必须兼容非法格式URL
如果你的服务必须接收这类不符合规范的请求,就关掉Express默认的query解析,自己写容错逻辑处理原始URL:
- 首先关闭Express默认的解析器:
const express = require('express') const app = express() app.set('query parser', false) // 关闭后req.query会为空,原始query串可以从req.url里拿到
- 加一个自定义解析中间件,做容错解码,遇到非法转义序列不抛错,保留原始内容:
// 容错解码:遇到非法%转义时不抛出异常 function safeDecode(str) { return str .replace(/\+/g, ' ') // 先把表单格式的+替换为空格 .replace(/%([0-9A-Fa-f]{2})/g, match => { try { return decodeURIComponent(match) } catch { return match } }) .replace(/%(?![0-9A-Fa-f]{2})/g, '%') // 处理单独存在的非法% } // 注册query解析中间件 app.use((req, res, next) => { const queryPart = req.url.split('?')[1] || '' req.query = Object.fromEntries( queryPart.split('&').filter(Boolean).map(kv => { const [key, val = ''] = kv.split('=') return [safeDecode(key), safeDecode(val)] }) ) next() })
加上这段逻辑之后,哪怕请求是?offer=20% discount这种非法格式,也能正确解析出req.query.offer的值为20% discount。
内容的提问来源于stack exchange,提问作者ptvty

