1. 概述

在本文中,我们将通过 HttpClient 5 演示如何发起 POST 请求,包括带认证的请求、JSON 数据提交、使用 Fluent API、上传文件以及监控上传进度等内容。

HttpClient 是 Java 中非常流行的 HTTP 客户端库,广泛用于构建 REST 客户端、调用远程服务等场景。我们将从基础用法讲起,逐步深入到文件上传及进度监控等高级用法。


2. 基础 POST 请求

我们先来看一个最简单的 POST 请求示例。使用 NameValuePair 来构造表单参数,然后通过 UrlEncodedFormEntity 设置请求体。

@Test
void whenSendPostRequestUsingHttpClient_thenCorrect() throws IOException {
    final HttpPost httpPost = new HttpPost(SAMPLE_URL);
    final List<NameValuePair> params = new ArrayList<>();
    params.add(new BasicNameValuePair("username", DEFAULT_USER));
    params.add(new BasicNameValuePair("password", DEFAULT_PASS));
    httpPost.setEntity(new UrlEncodedFormEntity(params));

    try (CloseableHttpClient client = HttpClients.createDefault();
         CloseableHttpResponse response = (CloseableHttpResponse) client
             .execute(httpPost, new CustomHttpClientResponseHandler())) {

        final int statusCode = response.getCode();
        assertThat(statusCode, equalTo(HttpStatus.SC_OK));
    }
}

关键点:

  • 使用 NameValuePair 构建参数列表
  • 使用 UrlEncodedFormEntity 设置请求体
  • 推荐使用 try-with-resources 管理资源

3. 带认证的 POST 请求

接下来演示如何向需要 Basic 认证的接口发起 POST 请求。

我们通过 BasicCredentialsProvider 设置用户名和密码,然后将其绑定到 HttpClient 实例上:

@Test
void whenSendPostRequestWithAuthorizationUsingHttpClient_thenCorrect() throws IOException {
    final HttpPost httpPost = new HttpPost(URL_SECURED_BY_BASIC_AUTHENTICATION);
    httpPost.setEntity(new StringEntity("test post"));

    final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
    final UsernamePasswordCredentials credentials = 
        new UsernamePasswordCredentials(DEFAULT_USER, DEFAULT_PASS.toCharArray());

    credsProvider.setCredentials(new AuthScope(URL_SECURED_BY_BASIC_AUTHENTICATION, 80), credentials);

    try (CloseableHttpClient client = HttpClients.custom()
        .setDefaultCredentialsProvider(credsProvider)
        .build();

        CloseableHttpResponse response = (CloseableHttpResponse) client
            .execute(httpPost, new CustomHttpClientResponseHandler())) {

        final int statusCode = response.getCode();
        assertThat(statusCode, equalTo(HttpStatus.SC_OK));
    }
}

关键点:

  • 使用 UsernamePasswordCredentials 设置账号密码
  • 使用 AuthScope 指定认证作用域
  • 使用 BasicCredentialsProvider 注入认证信息

4. 发送 JSON 数据的 POST 请求

在 RESTful 接口中,我们通常需要发送 JSON 格式的请求体。HttpClient 也支持直接发送 JSON。

我们使用 StringEntity 构造 JSON 字符串,并设置 Content-Typeapplication/json

@Test
void whenPostJsonUsingHttpClient_thenCorrect() throws IOException {
    final HttpPost httpPost = new HttpPost(SAMPLE_URL);

    final String json = "{\"id\":1,\"name\":\"John\"}";
    final StringEntity entity = new StringEntity(json);
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    httpPost.setHeader("Content-type", "application/json");

    try (CloseableHttpClient client = HttpClients.createDefault();
         CloseableHttpResponse response = (CloseableHttpResponse) client
             .execute(httpPost, new CustomHttpClientResponseHandler())) {

        final int statusCode = response.getCode();
        assertThat(statusCode, equalTo(HttpStatus.SC_OK));
    }
}

关键点:

  • 使用 StringEntity 构建 JSON 请求体
  • 显式设置 Content-TypeAccept 请求头
  • 注意 JSON 字符串格式正确性,避免语法错误

5. 使用 HttpClient Fluent API 发起 POST 请求

HttpClient 提供了一个 Fluent API,语法更简洁,适合快速构建请求。

以下是一个使用 Fluent API 提交表单的示例:

@Test
void whenPostFormUsingHttpClientFluentAPI_thenCorrect() throws IOException {
    Request request = Request.post(SAMPLE_URL)
        .bodyForm(Form.form()
            .add("username", DEFAULT_USER)
            .add("password", DEFAULT_PASS)
            .build());

    HttpResponse response = request.execute()
        .returnResponse();
    assertThat(response.getCode(), equalTo(HttpStatus.SC_OK));
}

关键点:

  • 使用 Request.post() 快速构建 POST 请求
  • 使用 Form.form() 构建表单数据
  • 链式调用更直观,适合简单场景

6. 发起 Multipart POST 请求

上传文件通常使用 multipart/form-data 格式。HttpClient 提供了 MultipartEntityBuilder 来构造这类请求。

下面演示如何上传一个文件,并携带用户名和密码字段:

@Test
void whenSendMultipartRequestUsingHttpClient_thenCorrect() throws IOException {
    final HttpPost httpPost = new HttpPost(SAMPLE_URL);

    final MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    builder.addTextBody("username", DEFAULT_USER);
    builder.addTextBody("password", DEFAULT_PASS);
    builder.addBinaryBody(
        "file", new File("src/test/resources/test.in"), ContentType.APPLICATION_OCTET_STREAM, "file.ext");

    final HttpEntity multipart = builder.build();
    httpPost.setEntity(multipart);

    try (CloseableHttpClient client = HttpClients.createDefault();
         CloseableHttpResponse response = (CloseableHttpResponse) client
             .execute(httpPost, new CustomHttpClientResponseHandler())) {

        final int statusCode = response.getCode();
        assertThat(statusCode, equalTo(HttpStatus.SC_OK));
    }
}

关键点:

  • 使用 MultipartEntityBuilder 构建 multipart 请求体
  • addTextBody() 添加文本字段
  • addBinaryBody() 添加二进制文件

7. 使用 HttpClient 上传文件

如果我们只想上传一个文件,可以省略其他字段,仅添加文件部分:

@Test
void whenUploadFileUsingHttpClient_thenCorrect() throws IOException {
    final HttpPost httpPost = new HttpPost(SAMPLE_URL);

    final MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    builder.addBinaryBody(
        "file", new File("src/test/resources/test.in"), ContentType.APPLICATION_OCTET_STREAM, "file.ext");
    final HttpEntity multipart = builder.build();

    httpPost.setEntity(multipart);

    try (CloseableHttpClient client = HttpClients.createDefault();
         CloseableHttpResponse response = (CloseableHttpResponse) client
             .execute(httpPost, new CustomHttpClientResponseHandler())) {

        final int statusCode = response.getCode();
        assertThat(statusCode, equalTo(HttpStatus.SC_OK));
    }
}

关键点:

  • 只上传文件时,可以不添加其他文本字段
  • 文件名和内容类型需明确指定

8. 监控文件上传进度

有时候我们需要知道上传的进度,例如在上传大文件时显示进度条。HttpClient 本身不直接支持进度监控,但可以通过扩展 HttpEntityWrapper 实现。

以下是完整的进度监控实现:

8.1 定义监听器接口

public static interface ProgressListener {
    void progress(float percentage);
}

8.2 扩展 HttpEntityWrapper

public class ProgressEntityWrapper extends HttpEntityWrapper {
    private ProgressListener listener;

    public ProgressEntityWrapper(HttpEntity entity, ProgressListener listener) {
        super(entity);
        this.listener = listener;
    }

    @Override
    public void writeTo(OutputStream outstream) throws IOException {
        super.writeTo(new CountingOutputStream(outstream, listener, getContentLength()));
    }
}

8.3 扩展 FilterOutputStream 实现计数

public static class CountingOutputStream extends FilterOutputStream {
    private ProgressListener listener;
    private long transferred;
    private long totalBytes;

    public CountingOutputStream(OutputStream out, ProgressListener listener, long totalBytes) {
        super(out);
        this.listener = listener;
        transferred = 0;
        this.totalBytes = totalBytes;
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        out.write(b, off, len);
        transferred += len;
        listener.progress(getCurrentProgress());
    }

    @Override
    public void write(int b) throws IOException {
        out.write(b);
        transferred++;
        listener.progress(getCurrentProgress());
    }

    private float getCurrentProgress() {
        return ((float) transferred / totalBytes) * 100;
    }
}

8.4 使用示例

@Test
void whenGetUploadFileProgressUsingHttpClient_thenCorrect() throws IOException {
    final HttpPost httpPost = new HttpPost(SAMPLE_URL);

    final MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    builder.addBinaryBody(
        "file", new File("src/test/resources/test.in"), ContentType.APPLICATION_OCTET_STREAM, "file.ext");
    final HttpEntity multipart = builder.build();

    final ProgressEntityWrapper.ProgressListener pListener = 
        percentage -> assertFalse(Float.compare(percentage, 100) > 0);

    httpPost.setEntity(new ProgressEntityWrapper(multipart, pListener));

    try (CloseableHttpClient client = HttpClients.createDefault();
         CloseableHttpResponse response = (CloseableHttpResponse) client
             .execute(httpPost, new CustomHttpClientResponseHandler())) {

        final int statusCode = response.getCode();
        assertThat(statusCode, equalTo(HttpStatus.SC_OK));
    }
}

关键点:

  • 通过包装 HttpEntity 实现上传进度监听
  • 继承 FilterOutputStream 跟踪已写入字节数
  • 使用回调接口 ProgressListener 返回上传进度

9. 小结

本文通过多个示例详细讲解了如何使用 Apache HttpClient 5 发起 POST 请求,包括:

功能 示例说明
基础 POST 使用 NameValuePair 提交表单
带认证的 POST 使用 BasicCredentialsProvider 设置认证信息
JSON 请求 使用 StringEntity 构造 JSON 请求体
Fluent API 使用链式调用简化请求构建
Multipart 请求 使用 MultipartEntityBuilder 提交文件和文本字段
文件上传 仅上传文件的简化方式
上传进度监控 扩展 HttpEntityWrapperFilterOutputStream 实现实时监控

所有示例代码均可在 GitHub 项目 中找到。

踩坑提醒:

  • JSON 请求中注意 Content-Type 设置
  • 文件上传时要指定文件名和 MIME 类型
  • 使用 try-with-resources 管理资源,避免内存泄漏
  • 上传大文件时建议使用进度监控,提升用户体验

如需进一步封装,可结合 Spring 或其他框架简化调用逻辑。


原始标题:Posting with Apache HttpClient | Baeldung