Java HttpRequest 怎样设置请求体发布器
来源:17golang原创
时间:2026-10-26 23:44:10 386浏览 收藏
Java HttpRequest 的请求体不是直接挂一个字符串,而是交给 HttpRequest.BodyPublisher 持续提供要发送的字节。最常用的写法是先根据数据来源选择 BodyPublishers 工厂,再把返回值传给 POST、PUT 或 method:
import java.net.URI;
import java.net.http.HttpRequest;
import java.nio.charset.StandardCharsets;
// 字符串请求体按 UTF-8 转成 BodyPublisher,再绑定到 POST
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.test/users"))
.header("Content-Type", "application/json; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"name\":\"Ada\"}", StandardCharsets.UTF_8))
.build();
官方 API 地址:https://docs.oracle.com/en/java/javase/25/docs/api/java.net.http/java/net/http/HttpRequest.BodyPublishers.html
选择请求体发布器时,先判断数据是文本、字节、文件还是流,再检查字符集、长度和是否可能重复发布;不要把所有输入都先拼成一个大字符串。
把请求体来源抽象成 BodyPublisher
HttpRequest.Builder 负责 URI、请求头和方法等请求定义,BodyPublisher 负责把高层数据转成适合发送的字节流,HttpClient 才负责实际发送。这个分工很像一个发布器策略:请求构建代码只依赖发布器接口,数据来源的差异交给 BodyPublishers 工厂封装。

因此,设置请求体的核心不是寻找一个“请求体字段”,而是先得到一个 BodyPublisher。POST、PUT 和通用的 method 方法都接受它;请求头中的 Content-Type 仍然需要由调用方按接口契约设置,两者解决的是不同问题。
按数据来源选择 BodyPublishers 工厂
常见工厂可以按输入形态分组。文本优先考虑 ofString,已经有完整字节时使用 ofByteArray,文件内容使用 ofFile。如果数据来自响应式发布器或需要拼接多个片段,再考虑 fromPublisher 与 concat。
| 数据来源 | 工厂 | 关键边界 |
|---|---|---|
| Java 字符串 | ofString(body) 或指定 Charset | 不指定时按 UTF-8 转换 |
| 完整字节数组 | ofByteArray(bytes) | 可用 offset 和 length 选取子范围 |
| 本地文件 | ofFile(path) | 路径不存在时会抛出文件未找到异常 |
| 输入流供应器 | ofInputStream(supplier) | 重复发送时供应器需要能重新提供流 |
| Flow 发布器 | fromPublisher(publisher) | 不传长度时长度未知 |
| 多个发布器 | concat(publishers) | 只有组成部分都已知长度时总长度才已知 |
import java.net.http.HttpRequest;
import java.nio.file.Path;
// 文件内容由发布器按路径提供,不需要先把整个文件读进字符串
HttpRequest.BodyPublisher fileBody = HttpRequest.BodyPublishers.ofFile(
Path.of("payload.json"));
// 二进制协议已经准备好字节时,直接发布指定数组
HttpRequest.BodyPublisher binaryBody = HttpRequest.BodyPublishers.ofByteArray(
new byte[] {0x01, 0x02, 0x03});
// 没有请求体时使用空发布器,适合需要显式传入 BodyPublisher 的自定义方法
HttpRequest.BodyPublisher emptyBody = HttpRequest.BodyPublishers.noBody();
这些工厂不会替你决定业务数据格式。JSON 是否有效、字节数组是否采用正确编码、文件内容是否匹配接口,都属于调用方自己的契约;发布器只负责把已经确定的数据来源接到请求构建器上。

把发布器绑定到 POST、PUT 和自定义方法
请求方法决定发布器在请求定义中的位置。普通表单或 JSON 提交可以用 POST(publisher),资源整体替换常用 PUT(publisher);需要 PATCH 等其他方法时使用 method(name, publisher)。方法名和请求体内容是两个独立维度,不要通过修改 Content-Type 来代替选择 HTTP 方法。
import java.net.URI;
import java.net.http.HttpRequest;
String json = "{\"enabled\":true}";
HttpRequest.BodyPublisher body = HttpRequest.BodyPublishers.ofString(json);
// POST 使用发布器提交一份资源或命令数据
HttpRequest post = HttpRequest.newBuilder(URI.create("https://api.example.test/jobs"))
.header("Content-Type", "application/json")
.POST(body)
.build();
// method 允许把同一个发布器交给接口约定的其他 HTTP 方法
HttpRequest patch = HttpRequest.newBuilder(URI.create("https://api.example.test/jobs/7"))
.header("Content-Type", "application/json")
.method("PATCH", body)
.build();
同一个发布器是否适合被多个请求复用,要结合它的来源判断。内存中的字符串和字节数组通常更容易重复提供;文件发布器由路径描述内容;输入流发布器则需要特别关注每次发送是否都能拿到新的流。不要因为变量可以复用,就推断底层数据一定可以无限次读取。
处理长度契约与输入流复用
fromPublisher(publisher) 会把长度视为未知;带第二个参数的版本则把长度声明为一个正数,而且这个数必须等于发布器实际发送的字节数。长度不是估算值,少发或多发都会破坏请求体契约。
import java.net.http.HttpRequest; import java.nio.ByteBuffer; import java.util.concurrent.Flow; // publisher 的每次发布必须恰好提供 12 个字节 Flow.Publisherpublisher = createPublisher(); HttpRequest.BodyPublisher fixedLength = HttpRequest.BodyPublishers .fromPublisher(publisher, 12L); // 未知长度版本不声明总字节数,不能把它当成“长度大约为 12” HttpRequest.BodyPublisher unknownLength = HttpRequest.BodyPublishers .fromPublisher(publisher);
上面的 createPublisher() 只是示意一个已有的 Flow.Publisher,本文没有运行这段代码。若长度已知,声明值必须是正数且与实际字节数完全一致;若长度未知,就使用不带长度的适配器,而不是猜一个数字。
ofInputStream 接收的是 Supplier,而不是已经打开的一次性流。官方 API 特别强调请求需要重复发送时会再次调用供应器,因此供应器应按调用重新打开资源;把同一个已经读到末尾的流反复返回,会让重试或重复发布失去正确的数据来源。
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.http.HttpRequest;
import java.nio.file.Files;
import java.nio.file.Path;
Path payload = Path.of("payload.bin");
HttpRequest.BodyPublisher streamBody = HttpRequest.BodyPublishers.ofInputStream(() -> {
try {
// 每次调用都创建新的流,避免把一次性流跨请求复用
return Files.newInputStream(payload);
} catch (IOException error) {
// Supplier 不能直接抛 checked exception,保留原始原因再转为 unchecked
throw new UncheckedIOException("无法打开请求体文件", error);
}
});
用反例和判断清单检查实现
最常见的错误不是不会调用工厂,而是把不同层面的契约混在一起。下面几项可以作为代码评审时的快速检查:
- 文本是否明确指定了接口要求的字符集;不指定时,
ofString按 UTF-8 转换。 - 已知长度是否以字节为单位,而不是 Java 字符数、代码点数量或字符串长度。
fromPublisher的声明长度是否和实际发布字节数相等;不确定时不要虚构长度。- 输入流供应器是否每次都能提供新的流,并在错误路径保留底层异常原因。
- 请求方法是否与接口契约一致;
Content-Type只描述媒体类型,不能代替POST、PUT或PATCH。 - 确实需要无体请求时,是否使用了
noBody()或对应的无体方法,而不是传入一个内容为空但语义不清的字符串。
一个典型反例是把大文件先读成字符串,再调用 ofString。它把文件的字节语义改成了字符语义,增加了内存占用,也可能在编码不匹配时改变原始内容。文件本身就是二进制载荷时,优先使用 ofFile;只有接口明确要求文本转换时,才在入口决定字符集。
另一个反例是把 fromPublisher(publisher, 1024) 当成“最多 1024 字节”。这个重载表达的是精确长度,不是上限;如果只能知道上限,应改用未知长度版本,或者让上游发布器建立真实的长度契约。
HttpRequest 怎样发送 JSON 请求体?
先把 JSON 文本交给 BodyPublishers.ofString,再在 Builder 上设置 Content-Type: application/json,最后传给 POST 或接口要求的其他方法。
ofString 默认使用什么字符集?
无 Charset 参数的 ofString 使用 UTF-8。若服务端要求其他字符集,应显式传入 Charset,并让 Content-Type 的声明与实际编码保持一致。
fromPublisher 的 contentLength 可以填写 0 吗?
不能。带长度的重载要求正数,并且该长度必须等于发布器实际发送的字节数;需要无体语义时应使用 noBody()。
为什么 ofInputStream 要传 Supplier?
请求可能需要重复发送,Supplier 可以在每次需要时重新取得输入流。返回同一个已消费流,不能保证后续发布仍有完整请求体。
归纳起来,Java HttpRequest 设置请求体的稳定模式是:先按数据来源选择 BodyPublishers,再把得到的 BodyPublisher 绑定到 HTTP 方法;最后核对字符集、字节长度和流的可重复性。这样请求构建代码表达的是协议意图,数据读取代码则保留自己的资源边界。
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
288 收藏
-
244 收藏
-
247 收藏
-
220 收藏
-
文章 · java教程 | 1小时前 | 文件处理 · Java · 异常处理 · nio · java 文件替换 Files.move ATOMIC_MOVE REPLACE_EXISTING 原子移动290 收藏
-
497 收藏
-
162 收藏
-
450 收藏
-
249 收藏
-
359 收藏
-
397 收藏
-
293 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习