杰瑞科技汇

java httpclient 下载

下面我将为你提供一个详细的指南,包括:

java httpclient 下载-图1
(图片来源网络,侵删)
  1. 同步下载(最简单直接的方式)
  2. 异步下载(更高效,不阻塞主线程)
  3. 带有进度显示的下载(用户体验更好)
  4. 使用 Apache HttpClient 4.x/5.x(如果你仍在使用或更偏爱它)

核心步骤(通用)

无论使用哪种方式,下载文件的基本步骤都类似:

  1. 创建 HttpClient 实例:这是发送请求的客户端。
  2. 创建 HttpRequest 实例:指定要下载的 URL、HTTP 方法(通常是 GET)等。
  3. 发送请求并获取 HttpResponse:客户端执行请求,服务器返回响应。
  4. 检查响应状态码:确保请求成功(通常是 200 OK)。
  5. 获取响应体(BodyHandlers:将响应流式地传输到一个文件或字节数组。
  6. 处理流和资源非常重要,确保在操作完成后关闭输入流和输出流,以避免资源泄漏。

同步下载 (Java 11+)

这是最简单的方式,代码直观,适合简单的脚本或不需要高并发的场景。

示例代码

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class SyncDownloader {
    public static void main(String[] args) {
        String fileUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
        String destinationPath = "dummy.pdf";
        // 1. 创建 HttpClient
        HttpClient httpClient = HttpClient.newHttpClient();
        // 2. 创建 HttpRequest
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(fileUrl))
                .build();
        try {
            // 3. 发送请求并获取响应,BodyHandlers.ofFile 会自动处理流和文件写入
            Path destination = Paths.get(destinationPath);
            HttpResponse<Path> response = httpClient.send(request, BodyHandlers.ofFile(destination));
            // 4. 检查响应状态码
            if (response.statusCode() == 200) {
                System.out.println("文件下载成功!");
                System.out.println("文件已保存到: " + destination.toAbsolutePath());
            } else {
                System.err.println("下载失败,状态码: " + response.statusCode());
            }
        } catch (IOException | InterruptedException e) {
            System.err.println("下载过程中发生错误: " + e.getMessage());
            // 如果是中断异常,恢复中断状态
            if (e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

代码解析:

  • HttpClient.newHttpClient(): 创建一个默认配置的 HttpClient
  • HttpRequest.newBuilder().uri(...).build(): 构建一个 GET 请求。
  • httpClient.send(...): 发送请求并阻塞,直到收到响应。
  • BodyHandlers.ofFile(Path): 这是一个非常方便的处理器,它会自动将响应体(输入流)的内容写入到指定的文件路径中,并自动处理流的关闭,大大简化了代码。

异步下载 (Java 11+)

异步下载不会阻塞调用线程,非常适合在 GUI 应用或需要处理多个网络请求的服务端环境中使用,它返回一个 CompletableFuture

java httpclient 下载-图2
(图片来源网络,侵删)

示例代码

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class AsyncDownloader {
    public static void main(String[] args) {
        String fileUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
        String destinationPath = "dummy_async.pdf";
        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(fileUrl))
                .build();
        Path destination = Paths.get(destinationPath);
        // 异步发送请求
        CompletableFuture<HttpResponse<Path>> futureResponse = httpClient.sendAsync(request, BodyHandlers.ofFile(destination));
        // 使用 thenAccept 来处理成功完成的情况
        futureResponse.thenAccept(response -> {
            if (response.statusCode() == 200) {
                System.out.println("文件异步下载成功!");
                System.out.println("文件已保存到: " + destination.toAbsolutePath());
            } else {
                System.err.println("异步下载失败,状态码: " + response.statusCode());
            }
        });
        // 使用 exceptionally 来处理异常
        futureResponse.exceptionally(e -> {
            System.err.println("异步下载过程中发生错误: " + e.getMessage());
            return null;
        });
        System.out.println("请求已发送,主线程继续执行其他任务...");
        // 为了让主线程等待异步任务完成(仅用于演示)
        try {
            futureResponse.get(); // 阻塞,直到 future 完成
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

代码解析:

  • httpClient.sendAsync(...): 发送异步请求,立即返回一个 CompletableFuture
  • futureResponse.thenAccept(...): 当 CompletableFuture 成功完成时,执行这个回调函数。
  • futureResponse.exceptionally(...): 当 CompletableFuture 发生异常时,执行这个回调函数。
  • futureResponse.get(): 在主线程中调用此方法可以等待异步任务完成,在实际应用中,你可能不需要它,你的应用可能会一直运行或由其他事件驱动。

带有进度显示的下载

要显示下载进度,我们需要更精细地控制流,我们不能直接使用 BodyHandlers.ofFile(),而是需要自己处理输入流和输出流,并在写入数据时计算字节数。

示例代码

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class ProgressDownloader {
    public static void main(String[] args) {
        String fileUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
        String destinationPath = "dummy_progress.pdf";
        HttpClient httpClient = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(fileUrl))
                .build();
        Path destination = Paths.get(destinationPath);
        try {
            // 发送请求,但使用 BodyHandlers.ofInputStream() 来获取原始输入流
            HttpResponse<InputStream> response = httpClient.send(request, BodyHandlers.ofInputStream());
            if (response.statusCode() != 200) {
                System.err.println("下载失败,状态码: " + response.statusCode());
                return;
            }
            // 获取输入流
            InputStream inputStream = response.body();
            // 获取输出流
            OutputStream outputStream = Files.newOutputStream(destination, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
            byte[] buffer = new byte[8192]; // 8KB 缓冲区
            int bytesRead;
            long totalBytesRead = 0;
            // 从响应头中获取文件总大小(可选)
            long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1);
            System.out.println("开始下载...");
            // 循环读取并写入
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
                totalBytesRead += bytesRead;
                // 计算并打印进度
                if (contentLength > 0) {
                    double percent = (double) totalBytesRead / contentLength * 100;
                    System.out.printf("下载进度: %.2f%%%n", percent);
                } else {
                    System.out.println("已下载: " + totalBytesRead + " bytes");
                }
            }
            // 确保所有流都被关闭
            inputStream.close();
            outputStream.close();
            System.out.println("文件下载完成!");
        } catch (IOException | InterruptedException e) {
            System.err.println("下载过程中发生错误: " + e.getMessage());
            if (e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

代码解析:

  • BodyHandlers.ofInputStream(): 获取一个 InputStream,让我们可以手动读取数据。
  • Files.newOutputStream(...): 创建一个文件输出流。
  • 我们使用一个循环和缓冲区来从输入流读取数据,并写入到输出流中。
  • 通过检查 Content-Length 响应头,我们可以计算出下载的百分比。
  • 手动关闭流:在这种模式下,我们必须手动 close() 输入流和输出流,通常建议使用 try-with-resources 语句来自动管理。

使用 Apache HttpClient 4.x/5.x

虽然 Java 11+ 的 HttpClient 已经很强大,但很多历史项目仍在使用 Apache HttpClient,它的 API 更加丰富和灵活。

java httpclient 下载-图3
(图片来源网络,侵删)

Maven 依赖 (Apache HttpClient 5.x)

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3.1</version> <!-- 请使用最新版本 -->
</dependency>

示例代码 (Apache HttpClient 5.x)

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.FileEntity;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
import org.apache.hc.core5.http.io.entity.StreamEntity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class ApacheHttpClientDownloader {
    public static void main(String[] args) {
        String fileUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
        String destinationPath = "dummy_apache.pdf";
        // 1. 创建 HttpClient
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 2. 创建 HttpRequest (HttpGet)
            HttpGet httpGet = new HttpGet(fileUrl);
            // 3. 执行请求
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                // 4. 检查响应状态码
                if (response.getCode() == 200) {
                    // 5. 获取响应实体
                    // EntityUtils.toByteArray 会将整个实体读入内存,对于大文件不推荐
                    // byte[] bytes = EntityUtils.toByteArray(response.getEntity());
                    // 推荐方式:流式传输
                    File destinationFile = new File(destinationPath);
                    try (InputStream in = response.getEntity().getContent();
                         FileOutputStream out = new FileOutputStream(destinationFile)) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = in.read(buffer)) != -1) {
                            out.write(buffer, 0, bytesRead);
                        }
                    }
                    System.out.println("文件下载成功!保存到: " + destinationFile.getAbsolutePath());
                } else {
                    System.err.println("下载失败,状态码: " + response.getCode());
                }
            }
        } catch (IOException e) {
            System.err.println("下载过程中发生错误: " + e.getMessage());
        }
    }
}

代码解析:

  • HttpClients.createDefault(): 创建一个默认配置的 CloseableHttpClient
  • new HttpGet(url): 创建一个 GET 请求对象。
  • httpClient.execute(httpGet): 执行请求,返回 CloseableHttpResponse
  • response.getEntity().getContent(): 获取响应体的输入流。
  • try-with-resources: CloseableHttpClientCloseableHttpResponse 都实现了 AutoCloseable,使用 try-with-resources 可以确保它们在使用后被自动关闭,防止资源泄漏。

总结与对比

特性 Java 11+ HttpClient Apache HttpClient 5.x
依赖 无需外部依赖 (JDK 内置) 需要 httpclient5 等库
API 风格 现代,函数式 (CompletableFuture) 经典,面向对象
同步/异步 原生支持 (send vs sendAsync) 主要依赖外部库 (如 CompletableFuture) 或回调
简洁性 BodyHandlers.ofFile() 非常简洁 需要手动管理流和资源
灵活性 非常灵活,尤其是异步处理 非常灵活,插件化程度高
推荐场景 新项目首选,尤其是基于 Java 11+ 的项目 遗留系统维护,或需要 Apache HttpClient 特定功能的项目

对于新项目,强烈推荐使用 Java 11+ 内置的 HttpClient,因为它更现代、更简洁,并且与平台深度集成。

分享:
扫描分享到社交APP
上一篇
下一篇