# 网络地址解析 url
30 秒速记
Node里有两套URL解析:老的url.parse()(legacy,已废弃)和标准的new URL()(WHATWG,和浏览器一致)- 新代码一律用
new URL(input, base),非法输入直接抛TypeError,不会默默给你一个半残的对象 - 取参数用
url.searchParams.get('k'),自动解码 - 分清
origin/host/hostname/pathname,安全校验比对hostname,别用includes - 外部
URL还要限制协议、端口、凭据和最终解析出来的IP,防SSRF
Node 里解析 URL 现在就用全局的 new URL(),它和浏览器是同一套 WHATWG 标准,老的 url.parse() 官方已经标记废弃了。 老接口的问题是太宽松,畸形地址也能解析出个结果,而且和浏览器的解析规则不一致,历史上出过好几次主机名混淆导致的安全问题。new URL() 遇到非法输入直接抛 TypeError,参数用 searchParams 取,自动解码。做跳转白名单、SSRF 防护时,一定拿解析后的 hostname 精确比对,url.includes('trusted.com') 这种写法碰到 trusted.com.evil.io 就破了。
理解「网络地址解析 url」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
老接口和新接口对比一下:
const url = require('url');
// 老接口:url.parse,宽松、已废弃
const a = url.parse('http://user:pw@example.com:8080/p?a=1#h', true);
console.log(a.hostname, a.query.a); // example.com 1
// 新接口:WHATWG URL,全局可用,和浏览器一致
const b = new URL('http://user:pw@example.com:8080/p?a=1#h');
console.log(b.hostname); // example.com
console.log(b.searchParams.get('a')); // '1'
console.log(b.origin); // http://example.com:8080
// 非法输入:新接口直接报错
try { new URL('not a url'); } catch (e) { console.log(e.code); } // ERR_INVALID_URL
几个容易混的字段:host 带端口(example.com:8080),hostname 不带;origin 是协议加主机加端口,跨域判断用它;pathname 只有路径;search 带问号,searchParams 是可操作的对象。
官方文档已经把 url.parse() 列为废弃(DEP0169),原因是它对某些畸形主机名的解析和浏览器不一致,可能被用来绕过安全校验。url.format() 还能用,而且可以直接接收 URL 对象。
面试官追问
追问 1跳转白名单写的是 target.includes('mysite.com'),有什么问题?
https://mysite.com.evil.io 和 https://evil.io/?mysite.com 都能过。要 new URL(target).hostname === 'mysite.com',或者精确匹配后缀 .mysite.com。
追问 2new URL('/a/b') 为什么报错?
相对地址没有基准没法解析,要传第二个参数:new URL('/a/b', 'https://example.com')。只是想判断能不能解析,Node 19.9 起可以用 URL.canParse(input, base),不用 try/catch。
追问 3服务端要请求用户提交的图片地址,除了解析成功还要校验什么?
协议只放 https:,拒绝带 username / password 的地址,端口限制在白名单,再把 hostname 解析出的 IP 和内网段比对。跟随重定向时每一跳都要重新校验,不然一个 302 就跳进内网了。
追问 4老项目到处是 url.parse(),要不要一次性全换掉?
涉及安全判断的地方(白名单、代理、SSRF 校验)优先换;纯展示的可以慢慢迁。注意两者对畸形输入的行为不同,迁移时要补异常输入的测试。
# 模块概述
30 秒速记
url模块老三件套:parse(字符串 → 对象)、format(对象 → 字符串)、resolve(相对地址拼成绝对地址)- 这三个都属于
legacy API,新代码用全局URL和URLSearchParams - 还有几个实用工具:
url.fileURLToPath()、url.pathToFileURL(),ESM里拿__dirname常用 - 解析成功 ≠ 地址合法,业务校验要单独做
url 模块就是把 URL 字符串拆成好操作的结构,或者反过来拼回去,老 API 一共三个方法:parse、format、resolve。 这几个在老项目里非常常见,但现在官方推荐用全局的 URL 类,它是 WHATWG 标准,和浏览器行为一致,拼参数也有 URLSearchParams。url 模块里现在最常用的反而是 fileURLToPath,在 ESM 里 fileURLToPath(import.meta.url) 就能拿到当前文件路径。不管用哪套,拆出来结构只是第一步,地址是否允许访问还得业务自己校验。
nodejs中,提供了url这个非常实用的模块,用来做URL的解析。在做node服务端的开发时会经常用到。使用很简单,总共只有3个方法。
理解「模块概述」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
面试官追问
追问 1能被 url.parse() 解析出对象,是不是就说明链接合法?
不是。url.parse 非常宽松,畸形字符串也能给你一个对象,只是字段缺一块。合法性要另外校验:协议、hostname、白名单,这些都不是解析器的职责。
追问 2ESM 里没有 __dirname,怎么拿当前文件所在目录?
path.dirname(fileURLToPath(import.meta.url)),fileURLToPath 从 url 模块里导入。Node 20.11 起也可以直接用 import.meta.dirname。
追问 3拼查询参数,手写字符串和用 URLSearchParams 有区别吗?
手写容易漏编码,参数里有 &、=、中文就坏了。new URLSearchParams({ q: 'a&b' }).toString() 会得到 q=a%26b,编码自动处理。
追问 4团队想封装一个统一的 URL 工具,底层选 url.parse 还是 URL?
选 URL。行为跟浏览器一致,非法输入抛错,前后端同构代码也能共用;url.parse 是废弃接口,新封装没理由再基于它。
# 模块方法概述
30 秒速记
url.parse(str):字符串 → 对象,读写字段方便url.format(obj):对象 → 字符串,parse的反向操作,也接受URL对象url.resolve(from, to):以from为基准把to解析成完整地址,规则和浏览器点链接一样resolve的坑:from末尾有没有/结果不同,to以/开头会替换整个路径- 新写法:
url.resolve(from, to)≈new URL(to, from).href
三个方法分别管「拆开」「拼回」「相对转绝对」。 parse 把字符串变对象,format 反过来;resolve 最容易答错,它的规则和浏览器里点一个相对链接一模一样:url.resolve('http://a.com/one/two', 'three') 得到 http://a.com/one/three,因为 two 被当成文件名替换掉了;基准写成 /one/two/ 才会得到 /one/two/three;to 以 / 开头就直接从根路径开始。新代码里同样的效果写成 new URL(to, from).href。
url模块三个方法分别是:
- .parse(urlString):将url字符串,解析成object,便于开发者进行操作。
- .format(urlObj):.parse() 方法的反向操作。
- .resove(from, to):以from作为起始地址,解析出完整的目标地址(还是看直接看例子好些)
url.resolve
