使用Java的HttpClient实现文件下载器

下载器通常需要从指定的URL下载文件,并且保存到本地。可能还需要处理一些异常情况,比如网络问题、文件不存在等等。

首先,HttpClient是在Java 11中引入的,所以用户需要确认他们的Java版本是否至少是11。如果用户用的是旧版本,可能需要使用旧的HttpClient或者其他库,比如Apache HttpClient。但用户明确提到用Java的HttpClient库,所以假设他们用的是Java 11或更高版本。

接下来,我需要考虑如何用HttpClient发送GET请求获取文件。HttpClient的基本步骤是创建客户端实例,构造请求,发送请求,然后处理响应。对于下载文件,可能需要获取输入流,然后读取数据写入本地文件。

然后,保存文件的部分。需要从URL中提取文件名,或者如果URL路径中没有文件名,可能需要处理Content-Disposition头来获取文件名。但这个问题可能比较复杂,用户可能只需要一个简单的例子,所以暂时先假设URL路径中有文件名,这样可以通过解析URL路径来获取文件名。

接下来,需要考虑大文件的下载,可能需要使用流式处理,避免将整个文件加载到内存中。使用InputStream读取响应体,然后通过Files.copy或者手动读取字节并写入文件。

异常处理方面,需要处理IOException,可能还有URISyntaxException,如果URL格式不正确的话。此外,还要检查HTTP响应码是否为200,否则可能文件不存在或服务器错误。

然后,用户可能需要一个示例程序,包含main方法,接受URL参数,调用下载方法。例如,用户运行程序时输入URL,程序下载文件到当前目录。

可能还需要进度显示,比如显示下载的百分比,但用户没有特别提到,可能属于进阶功能。不过如果可能的话,可以简单提一下如何实现进度跟踪,比如通过监听读取的字节数。

还有需要考虑的是设置超时,防止长时间等待。HttpClient可以设置连接超时和请求超时。

另外,可能需要处理重定向,默认情况下HttpClient会自动处理重定向,所以用户可能不需要额外设置,但可以提到这一点。

以下是一个使用Java HttpClient实现的文件下载器示例,包含进度显示和基础错误处理:

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;

public class FileDownloader {
    private static final HttpClient httpClient = HttpClient.newBuilder()
            .followRedirects(HttpClient.Redirect.NORMAL)
            .connectTimeout(Duration.ofSeconds(15))
            .build();

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("请提供下载URL作为参数");
            return;
        }

        try {
            String url = args[0];
            Path outputDir = args.length > 1 ? Path.of(args[1]) : Path.of("");
            downloadFile(url, outputDir);
        } catch (Exception e) {
            System.err.println("下载失败: " + e.getMessage());
        }
    }

    public static void downloadFile(String urlString, Path outputDir) throws Exception {
        // 验证并创建输出目录
        if (!Files.exists(outputDir)) {
            Files.createDirectories(outputDir);
        }

        URI uri;
        try {
            uri = new URI(urlString);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("无效的URL格式");
        }

        HttpRequest request = HttpRequest.newBuilder(uri)
                .GET()
                .header("User-Agent", "Java HttpClient Downloader")
                .build();

        HttpResponse<InputStream> response = httpClient.send(
                request, 
                HttpResponse.BodyHandlers.ofInputStream()
        );

        if (response.statusCode() != 200) {
            throw new IOException("服务器返回错误状态码: " + response.statusCode());
        }

        String fileName = getFileName(uri, response);
        Path outputPath = outputDir.resolve(fileName);
        
        System.out.println("开始下载: " + fileName);
        System.out.println("保存路径: " + outputPath.toAbsolutePath());

        try (InputStream is = response.body()) {
            long contentLength = response.headers()
                    .firstValueAsLong("Content-Length")
                    .orElse(0L);

            if (contentLength > 0) {
                downloadWithProgress(is, outputPath, contentLength);
            } else {
                Files.copy(is, outputPath, StandardCopyOption.REPLACE_EXISTING);
            }
        }

        System.out.println("\n下载完成!");
    }

    private static String getFileName(URI uri, HttpResponse<?> response) {
        // 尝试从Content-Disposition头获取文件名
        return response.headers()
                .firstValue("Content-Disposition")
                .map(header -> {
                    String[] parts = header.split(";");
                    for (String part : parts) {
                        if (part.trim().startsWith("filename=")) {
                            return part.split("=")[1]
                                    .replace("\"", "")
                                    .trim();
                        }
                    }
                    return null;
                })
                .orElseGet(() -> {
                    // 从URL路径获取文件名
                    String path = uri.getPath();
                    return path.substring(path.lastIndexOf('/') + 1);
                });
    }

    private static void downloadWithProgress(InputStream is, Path outputPath, long contentLength) 
            throws IOException {
        try (var os = Files.newOutputStream(outputPath)) {
            byte[] buffer = new byte[4096];
            long bytesRead = 0;
            int read;

            while ((read = is.read(buffer)) != -1) {
                os.write(buffer, 0, read);
                bytesRead += read;
                
                // 更新进度显示
                double progress = (double) bytesRead / contentLength * 100;
                System.out.printf("进度: %.2f%% (%d/%d bytes)\r", 
                        progress, bytesRead, contentLength);
            }
        }
    }
}

功能特点:

  1. 自动重定向处理 :配置了followRedirects(HttpClient.Redirect.NORMAL)
  2. 进度显示:当服务器提供Content-Length时会显示下载进度
  3. 智能文件名检测
    • 优先从Content-Disposition头获取文件名
    • 其次从URL路径解析文件名
  4. 异常处理:处理常见的网络错误和I/O错误
  5. 目录自动创建:自动创建不存在的输出目录
  6. 超时设置:15秒连接超时

使用方式:

bash 复制代码
java FileDownloader.java <下载URL> [输出目录]

示例:

bash 复制代码
java FileDownloader.java https://example.com/largefile.zip /tmp/downloads

扩展建议:

  1. 断点续传 :添加Range头支持
  2. 多线程下载:分割文件范围并行下载
  3. 速度限制:控制下载带宽
  4. 代理支持 :通过.proxy()方法配置
  5. HTTPS验证:自定义SSLContext
  6. 身份验证:添加Authorization头

该实现使用了Java 11+的HttpClient API,具有非阻塞IO特性,适合处理大文件下载。进度显示功能在支持ANSI转义的终端上可以显示动态更新效果。

相关推荐
滴水可藏海8 分钟前
EasyExcel系列:读取空数据行的问题
java
王小二_Leon17 分钟前
JAVA中正则表达式的入门与使用
java·正则表达式
weixin_4932026318 分钟前
R语言网状Meta分析---Meta回归(1)(基于gemtc)
开发语言·回归·r语言
muxue17828 分钟前
go:实现最简单区块链
开发语言·后端·golang
Achou.Wang29 分钟前
go语言内存泄漏的常见形式
开发语言·golang
骑牛小道士30 分钟前
java基础 运算符
java
旅行的橘子汽水32 分钟前
【C语言-全局变量】
c语言·开发语言·数据库
晴天毕设工作室1 小时前
计算机毕业设计指南
java·开发语言·python·计算机网络·课程设计
jhtwn1 小时前
Java NIO之Buffer
java·开发语言
沐墨专攻技术1 小时前
顺序表专题(C语言)
c语言·开发语言·数据结构·顺序表