Java使用Net URL请求含%20的URL时返回HTTP 400错误排查
问题梳理
- 开发Java网页爬虫,目标为批量下载站点
https://billwurtz.com/songs.html下的全部歌曲资源 - 站点旧资源链接中,空格统一转义为
%20形式,例:https://billwurtz.com/can%20i.mp3;新链接已改用-直接替代空格 - 访问旧资源时触发HTTP 400错误,排查发现Java实际发送的请求URL中,原本的
%20被还原为普通空格,不符合HTTP协议规范
测试代码:
URL website = new URL("https://www.billwurtz.com/" + song); Path out = Path.of("songs/" + song); System.out.println(website.toString()); try (InputStream in = website.openStream()) { Files.copy(in, out, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { throw new Exception(e.getMessage()); }
运行输出:
https://www.billwurtz.com/can%20i.mp3 Exception in thread "main" java.lang.Exception: Server returned HTTP response code: 400 for URL: https://billwurtz.com/can i.mp3
- 已检索过同类问题,多数为开发者构造URL时未对空格做转义导致报错,但当前代码中传入的URL字符串已明确包含
%20转义内容,仍触发异常。
问题根源
Java原生URL类在通过字符串构造实例时,会自动对传入内容做语法解析,过程中会把预编码的%20解码为普通空格存入内部路径字段。即使调用toString()方法时会把路径重新序列化显示为带%20的形式,实际发起网络请求时,使用的是解码后带原始空格的路径,未重新做编码处理,因此触发服务器的400错误。
这是Java URL类的历史遗留行为:默认会自动解析、解码传入的URL内容,不会保留传入时的原始编码状态。
解决方案
不要依赖字符串直接拼接构造URL,改用URI类做带编码的构造,从根源避免解码-未重编码的问题:
// 对资源路径部分单独做编码处理,适配包含空格、特殊字符的资源名 URI resourceUri = new URI( "https", // 协议 null, // 无用户认证信息 "www.billwurtz.com", // 主机名 -1, // 使用默认HTTPS端口 "/" + song, // 资源路径 null, // 无查询参数 null // 无页面片段 ); URL website = resourceUri.toURL(); // 本地文件路径将%20还原为普通空格,避免本地存储的文件名带转义字符 Path out = Path.of("songs/" + song.replace("%20", " "));
如果需要兼容现有代码结构,可在拿到原始URL实例后,手动对路径部分的未转义空格重新编码:
URL rawUrl = new URL("https://www.billwurtz.com/" + song); // 重新构造URL,强制对路径中的空格做编码 URL website = new URL( rawUrl.getProtocol(), rawUrl.getHost(), rawUrl.getPort(), rawUrl.getPath().replace(" ", "%20") );
额外注意:从页面提取的song变量,需要确认是否已被提取逻辑自动解码——如果提取阶段已经把%20转成了普通空格,无论哪种构造方式,都需要对路径段的特殊字符统一做编码处理,不要依赖预转义的字符串内容。
内容的提问来源于stack exchange,提问作者George Sanger
相关产品推荐
相关产品推荐

