杰瑞科技汇

HttpClient教程怎么学?从入门到实践有哪些步骤?

Apache HttpClient 5.x 完整教程

HttpClient 是一个流行的、功能强大的 Java HTTP 客户端库,用于发送 HTTP 请求和处理 HTTP 响应,它被广泛应用于需要与 Web 服务、REST API 进行交互的 Java 应用程序中。

HttpClient教程怎么学?从入门到实践有哪些步骤?-图1
(图片来源网络,侵删)

重要提示: 本教程基于 HttpClient 5.x 版本,这是当前的主流版本,它与 4.x 版本在 API 设计上有显著不同,尤其是在异步请求和资源管理方面。


目录

  1. 为什么选择 HttpClient?
  2. 环境准备
  3. 核心概念
  4. 发送第一个 GET 请求
  5. 发送 POST 请求 (提交表单/JSON)
  6. 处理响应
  7. 高级主题
    • 1 设置请求头
    • 2 处理重定向
    • 3 处理 Cookie
    • 4 连接池
    • 5 超时设置
  8. 异步请求
  9. 最佳实践与资源管理

为什么选择 HttpClient?

相比于 Java 内置的 HttpURLConnection,HttpClient 提供了以下优势:

  • 更丰富的 API:API 设计更现代化,易于使用。
  • 更强大的功能:内置了对连接池、重定向、Cookie、压缩等高级特性的支持。
  • 更灵活的请求构建:可以轻松地构建复杂的 HTTP 请求。
  • 更好的性能:通过连接池可以显著提高高并发场景下的性能。
  • 同步和异步支持:既支持传统的同步阻塞式调用,也支持基于回调的非阻塞式异步调用。

环境准备

你需要在你的项目中添加 HttpClient 5 的依赖。

Maven (pom.xml)

<dependencies>
    <!-- HttpClient 核心库 -->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <version>5.3.1</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- 用于处理 JSON 的常用库 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- 如果需要异步支持,需要添加这个 -->
    <dependency>
        <groupId>org.apache.httpcomponents.core5</groupId>
        <artifactId>httpcore5-reactive</artifactId>
        <version>5.2</version> <!-- 请使用最新版本 -->
    </dependency>
</dependencies>

Gradle (build.gradle)

dependencies {
    // HttpClient 核心库
    implementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1'
    // 用于处理 JSON 的常用库
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
    // 如果需要异步支持,需要添加这个
    implementation 'org.apache.httpcomponents.core5:httpcore5-reactive:5.2'
}

核心概念

在学习代码之前,了解几个核心类非常重要:

HttpClient教程怎么学?从入门到实践有哪些步骤?-图2
(图片来源网络,侵删)
  • HttpClient: 执行 HTTP 请求的入口点,它管理着连接池、执行上下文等,通常建议为整个应用创建一个单例的 HttpClient 实例。
  • HttpRequest: 代表一个 HTTP 请求的接口,最常用的实现是 HttpRequestBase 的子类,如 HttpGet, HttpPost, HttpPut, HttpDelete 等。
  • HttpResponse: 代表一个 HTTP 响应,它包含状态码、响应头和响应实体。
  • HttpEntity: 代表 HTTP 消息的正文(请求体或响应体),可以看作是数据的容器。
  • CloseableHttpResponse: HttpResponse 的一个可关闭版本,使用后必须关闭以释放资源。
  • ResponseHandler: 一个非常有用的接口,可以让你将 HttpResponse 直接转换为特定类型的 Java 对象,从而简化代码。

发送第一个 GET 请求

这是最简单的用例:从指定的 URL 获取数据。

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.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
public class SimpleGetRequest {
    public static void main(String[] args) {
        // 1. 创建 HttpClient 实例
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 2. 创建 HttpGet 请求,可以添加查询参数
            URIBuilder uriBuilder = new URIBuilder("https://jsonplaceholder.typicode.com/posts/1");
            HttpGet httpGet = new HttpGet(uriBuilder.build());
            System.out.println("Executing request: " + httpGet.getMethod() + " " + httpGet.getUri());
            // 3. 执行请求,并获取响应
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                // 4. 获取响应状态码
                System.out.println("Response status: " + response.getCode());
                // 5. 获取响应实体
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response body: " + responseBody);
                // 6. 确保响应实体被完全消费并释放连接
                EntityUtils.consume(response.getEntity());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码解释:

  1. try-with-resources 语句用于创建 CloseableHttpClientCloseableHttpResponse,确保它们在使用后能被自动关闭,避免资源泄漏。
  2. HttpGet 是 GET 请求的具体实现类。
  3. URIBuilder 是一个方便的工具类,用于构建带有查询参数的 URL,可以避免手动拼接字符串带来的问题。
  4. httpClient.execute(httpGet) 会阻塞当前线程,直到服务器返回响应。
  5. response.getCode() 获取 HTTP 状态码,如 200, 404, 500 等。
  6. EntityUtils.toString(response.getEntity()) 将响应体(通常是 JSON 或 HTML 字符串)读取为 String这是一个非常重要的步骤,如果读取后不消费掉实体,连接可能无法被正确释放。

发送 POST 请求

POST 请求通常用于向服务器提交数据,例如表单数据或 JSON 对象。

1 提交 JSON 数据

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpPost;
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.ContentType;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.net.URIBuilder;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
public class PostJsonRequest {
    public static void main(String[] args) {
        String apiUrl = "https://jsonplaceholder.typicode.com/posts";
        // 创建一个 ObjectMapper 用于处理 JSON
        ObjectMapper objectMapper = new ObjectMapper();
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 1. 创建 HttpPost 请求
            HttpPost httpPost = new HttpPost(apiUrl);
            // 2. 创建要发送的 JSON 对象
            // 这里用一个简单的 Map 代替 Java 对象,更直观
            // 实际项目中,你会创建一个对应的 Java 类(如 Post.java)
            String jsonPayload = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
            // 3. 设置请求体
            StringEntity entity = new StringEntity(jsonPayload, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            // 4. 设置请求头
            httpPost.setHeader("Accept", "application/json");
            httpPost.setHeader("Content-type", "application/json");
            System.out.println("Executing request: " + httpPost.getMethod() + " " + httpPost.getUri());
            System.out.println("Request payload: " + jsonPayload);
            // 5. 执行请求
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                System.out.println("Response status: " + response.getCode());
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response body: " + responseBody);
                EntityUtils.consume(response.getEntity());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2 提交表单数据

import org.apache.hc.client5.http.classic.methods.HttpPost;
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.ContentType;
import org.apache.hc.core5.http.io.entity.UrlEncodedFormEntity;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PostFormRequest {
    public static void main(String[] args) {
        String apiUrl = "https://example.com/api/login"; // 这是一个示例 URL
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(apiUrl);
            // 1. 创建表单参数列表
            List<BasicNameValuePair> formParams = new ArrayList<>();
            formParams.add(new BasicNameValuePair("username", "john.doe"));
            formParams.add(new BasicNameValuePair("password", "secret123"));
            // 2. 设置请求体
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams);
            httpPost.setEntity(entity);
            System.out.println("Executing request: " + httpPost.getMethod() + " " + httpPost.getUri());
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                System.out.println("Response status: " + response.getCode());
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response body: " + responseBody);
                EntityUtils.consume(response.getEntity());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

处理响应

手动解析状态码和响应体有些繁琐。ResponseHandler 可以让这个过程更优雅。

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.client5.http.impl.classic.BasicResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
public class ResponseHandlerExample {
    public static void main(String[] args) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet httpGet = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
            // 创建一个 ResponseHandler,它会自动处理响应和资源释放
            BasicResponseHandler<String> responseHandler = new BasicResponseHandler<>();
            // execute 方法现在直接返回响应体的字符串
            String responseBody = httpClient.execute(httpGet, responseHandler);
            System.out.println("Response body (via ResponseHandler):");
            System.out.println(responseBody);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

优点:

  • 代码更简洁,无需手动 try-with-resources 来管理 CloseableHttpResponse
  • ResponseHandler 内部会处理 EntityUtils.consume(),确保资源被释放。

高级主题

1 设置请求头

在之前的 POST 请求示例中已经展示了如何设置请求头,使用 httpGet.setHeader("Header-Name", "Header-Value") 即可。

HttpGet httpGet = new Get("http://example.com");
httpGet.setHeader("User-Agent", "My-Cool-App/1.0");
httpGet.setHeader("Accept-Language", "en-US,en;q=0.9");

2 处理重定向

HttpClient 默认会自动处理重定向(从 HTTP 跳转到 HTTPS,或者 301/302 跳转),你可以通过 HttpClientBuilder 进行配置。

import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpHost;
RequestConfig config = RequestConfig.custom()
        .setRedirectsEnabled(false) // 禁用自动重定向
        .build();
try (CloseableHttpClient httpClient = HttpClients.custom()
        .setDefaultRequestConfig(config)
        .build()) {
    // ...
}

3 处理 Cookie

HttpClient 内置了 Cookie 管理功能。

import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.cookie.BasicCookieStore;
import org.apache.hc.client5.http.impl.cookie.PublicSuffixMatcher;
import org.apache.hc.client5.http.impl.cookie.PublicSuffixMatcherLoader;
import org.apache.hc.client5.http.impl.cookie.StandardCookieSpec;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.cookie.CookieStore;
// 创建一个自定义的 CookieStore
CookieStore cookieStore = new BasicCookieStore();
PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.getDefault();
StandardCookieSpec cookieSpec = StandardCookieSpec.builder().setPublicSuffixMatcher(publicSuffixMatcher).build();
HttpClientContext context = HttpClientContext.create();
context.setCookieStore(cookieStore);
context.setCookieSpecRegistry(cookieSpec);
try (CloseableHttpClient httpClient = HttpClients.custom()
        .setDefaultCookieStore(cookieStore)
        .build()) {
    // 第一次请求,服务器会设置 Cookie
    HttpGet get1 = new HttpGet("http://example.com/login");
    httpClient.execute(get1, context);
    // 第二次请求,HttpClient 会自动带上之前设置的 Cookie
    HttpGet get2 = new HttpGet("http://example.com/profile");
    try (CloseableHttpResponse response = httpClient.execute(get2, context)) {
        // ...
    }
}

4 连接池

在高并发场景下,为每个请求创建新连接是非常低效的,连接池可以复用 TCP 连接,大幅提升性能。

import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.pool.ConnPoolControl;
import org.apache.hc.core5.reactor.ConnectingIOReactor;
import org.apache.hc.core5.reactor.ConnectingIOReactorConfig;
import org.apache.hc.core5.reactor.IOReactorConfig;
import org.apache.hc.core5.reactor.ssl.SSLContexts;
import javax.net.ssl.SSLContext;
import java.util.concurrent.TimeUnit;
public class ConnectionPoolExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建连接池配置
        ConnectingIOReactorConfig ioReactorConfig = ConnectingIOReactorConfig.custom()
                .setIoThreadCount(Runtime.getRuntime().availableProcessors()) // 通常设置为 CPU 核心数
                .setConnectTimeout(5, TimeUnit.SECONDS)
                .setSocketTimeout(5, TimeUnit.SECONDS)
                .build();
        // 2. 创建 IOReactor
        try (ConnectingIOReactor ioReactor = new ConnectingIOReactor(ioReactorConfig)) {
            // 3. 创建 HttpClient 配置
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(5, TimeUnit.SECONDS)
                    .setConnectionRequestTimeout(5, TimeUnit.SECONDS)
                    .build();
            // 4. 使用 IOReactor 创建 HttpClient
            try (CloseableHttpClient httpClient = HttpClients.custom()
                    .setConnectionManager(new PoolingHttpClientConnectionManager(ioReactor))
                    .setDefaultRequestConfig(requestConfig)
                    .build()) {
                // ... 在这里执行多个 HTTP 请求 ...
                // 连接池会自动管理连接的获取和释放
                System.out.println("Connection pool created. Ready to make requests.");
            }
        }
    }
}

注意: 在生产环境中,你应该创建一个全局的 HttpClient 单例,而不是每次请求都创建新的。

5 超时设置

超时设置对于构建健壮的客户端至关重要。

  • 连接超时: 客户端与服务器建立 TCP 连接的最长时间。
  • Socket 超时 (读取超时): 客户端从服务器读取数据的最长时间。
  • 请求获取超时: 从连接池中获取一个连接的最长时间。
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
RequestConfig config = RequestConfig.custom()
        .setConnectTimeout(10, TimeUnit.SECONDS)    // 连接超时
        .setConnectionRequestTimeout(5, TimeUnit.SECONDS) // 从连接池获取连接超时
        .setResponseTimeout(30, TimeUnit.SECONDS)   // 等待响应超时 (HttpClient 5.x 新增)
        .build();
try (CloseableHttpClient httpClient = HttpClients.custom()
        .setDefaultRequestConfig(config)
        .build()) {
    // ...
}

异步请求

HttpClient 5.x 提供了基于 CompletableFuture 的异步 API,非常适合在非阻塞应用(如 Spring WebFlux)或需要高吞吐量的场景中使用。

import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.nio.entity.StringEntity;
import java.util.concurrent.CompletableFuture;
public class AsyncRequestExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建异步 HttpClient
        try (CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault()) {
            // 2. 启动客户端
            httpClient.start();
            // 3. 构建异步请求
            SimpleHttpRequest request = SimpleRequestBuilder.get("https://jsonplaceholder.typicode.com/posts/1")
                    .build();
            System.out.println("Executing async request...");
            // 4. 执行请求并使用回调处理结果
            CompletableFuture<SimpleHttpResponse> future = new CompletableFuture<>();
            httpClient.execute(request, new FutureCallback<SimpleHttpResponse>() {
                @Override
                public void completed(SimpleHttpResponse response) {
                    System.out.println("Async request completed. Status: " + response.getCode());
                    future.complete(response);
                }
                @Override
                public void failed(Exception ex) {
                    System.err.println("Async request failed: " + ex.getMessage());
                    future.completeExceptionally(ex);
                }
                @Override
                public void cancelled() {
                    System.err.println("Async request cancelled.");
                    future.cancel(true);
                }
            });
            // 5. 可以在这里做其他事情,或者等待结果
            future.thenAccept(response -> {
                try {
                    System.out.println("Async Response Body: " + response.getBodyText());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).exceptionally(ex -> {
                System.err.println("Error in future: " + ex.getMessage());
                return null;
            });
            // 等待异步操作完成(在实际应用中,通常不需要这样做)
            future.join();
            System.out.println("Async execution finished.");
        }
    }
}

最佳实践与资源管理

  1. 重用 HttpClient 实例HttpClient 的设计是线程安全的,并且内部管理着昂贵的资源(如连接池),你应该为你的整个应用程序创建一个 HttpClient 单例,而不是为每个请求都创建一个新的。
  2. 总是使用 try-with-resources:对于所有实现了 CloseableAutoCloseable 的资源(如 CloseableHttpClient, CloseableHttpResponse),都应该使用 try-with-resources 语句来确保它们被正确关闭。
  3. 消费响应实体:在读取完响应体后,务必调用 EntityUtils.consume(response.getEntity()) 或使用 ResponseHandler,否则连接可能无法被放回连接池,最终导致资源耗尽。
  4. 配置连接池和超时:在生产环境中,务必根据你的应用需求合理配置连接池大小、超时时间等参数。
  5. 处理异常:网络请求是不可靠的,要妥善处理可能发生的 IOExceptionTimeoutException 等异常。

本教程涵盖了 Apache HttpClient 5.x 的核心用法,包括:

  • 同步请求GET, POST 的基本用法。
  • 请求与响应:如何构建请求、设置请求头、处理响应体。
  • 高级功能:连接池、Cookie、重定向、超时配置。
  • 异步请求:使用 CompletableFuture 进行非阻塞调用。
  • 最佳实践:强调资源管理和性能优化。

HttpClient 是一个功能强大的工具,掌握它将使你在处理 HTTP 通信时更加得心应手,建议你结合官方文档和本教程,通过实际项目来练习和巩固这些知识。

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