Java NIO下载Windows正常macOS返回403 Forbidden问题排查
问题现象
编写的批量从服务器重新下载文件的小型命令行程序,在Windows系统下运行无异常,在macOS系统下无法正常工作,运行时抛出如下403报错:
Download failed: java.io.IOException: Server returned HTTP response code: 403 for URL: https://cdn.modrinth.com/data/P7dR8mSH/versions/0.56.3+1.19/fabric-api-0.56.3%2B1.19.jar at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1914) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1512) at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:268) at java.net.URL.openStream(URL.java:1092) at main.downloadUsingNIO(main.java:37) at main.main(main.java:20)
原始实现代码
private static void downloadUsingNIO(String urlStr, String file) throws IOException { URL url = new URL(urlStr); ReadableByteChannel rbc = Channels.newChannel(url.openStream()); FileOutputStream fos = new FileOutputStream(file); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); rbc.close(); }
调用方式:
downloadUsingNIO(URLOFFILE, WHERETOSAVEANDTOWHATFILE);
已排查确认的信息
- Windows与macOS端运行程序的账户均为系统主管理员账户
- 执行
sudo java -jar命令提权运行jar包无法解决问题 - 同一下载链接在Safari浏览器中可正常下载文件
- 执行
curl -o thing.jar命令可正常完成下载 - 曾尝试使用Apache Commons.IO实现下载逻辑,因底层调用逻辑与原实现一致未生效,报错栈完全相同
根因分析
报错核心原因是目标CDN拦截了Java默认请求的User-Agent标识。
Java原生URL.openStream()发起HTTP请求时,默认携带的User-Agent会携带Java版本特征,和浏览器、curl等常规客户端的UA特征差异明显,触发了Modrinth CDN的拦截规则返回403;Safari、curl的默认UA在CDN的放行规则内,因此可以正常下载。不同系统下安装的JDK版本默认UA存在细微差异,导致Windows端未触发拦截、macOS端触发拦截。
修复方案
手动为请求配置合规的浏览器User-Agent即可解决问题,修复后的代码同时补充了本地文件完整性校验、存储目录自动创建的逻辑,健壮性更强:
private static void downloadUsingNIO(String Strurl, String path) throws IOException { final URLConnection connection = new URL(Strurl).openConnection(); connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0"); final int contentLength = connection.getContentLength(); final File end = new File(path); if (end.exists()) { final URLConnection savedFileConnection = end.toURI().toURL().openConnection(); if (savedFileConnection.getContentLength() == contentLength) { return; } } else { final File dir = end.getParentFile(); if (!dir.exists()) dir.mkdirs(); } final ReadableByteChannel rbc = Channels.newChannel(connection.getInputStream()); final FileOutputStream fos = new FileOutputStream(end); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); return; }
内容的提问来源于stack exchange,提问作者NoThrottle
相关产品推荐
相关产品推荐

