使用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转义的终端上可以显示动态更新效果。

相关推荐
梦想画家1 小时前
Scrapy爬虫实战:如何用Rules实现高效数据采集
爬虫·scrapy
Enti7c1 小时前
JavaScript 实现输入框的撤销功能
开发语言·javascript·ecmascript
武昌库里写JAVA1 小时前
Java 设计模式
java·vue.js·spring boot·课程设计·宠物管理
钢铁男儿1 小时前
Python 函数装饰器和闭包(闭包)
java·网络·python
Clf丶忆笙1 小时前
从零开始搭建第一个Spring Boot应用:从入门到精通
java·spring boot
东坡大表哥1 小时前
【Android】Android签名解析
android·java
Miracle&1 小时前
Qt 显示QRegExp 和 QtXml 不存在问题
开发语言·qt
杨不易呀2 小时前
Java面试:微服务与大数据场景下的技术挑战
java·大数据·微服务·面试·技术栈
magic 2452 小时前
SpringMVC——第三章:获取请求数据
java·数据库·springmvc
秋风&萧瑟3 小时前
【QT】QT中的事件
开发语言·qt