当前位置:首页 > 文章列表 > 文章 > java教程 > Java HttpRequest 怎样设置请求体发布器

Java HttpRequest 怎样设置请求体发布器

来源:17golang原创 2026-10-26 23:44:10 0浏览 收藏

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 工厂封装。

HttpRequest Builder、BodyPublisher 工厂、HTTP 方法和 HttpClient 发送之间的静态职责结构图
图1:请求体发布器职责结构图,展示请求定义、发布器和发送接口的静态边界;这是说明图,不是运行截图。

因此,设置请求体的核心不是寻找一个“请求体字段”,而是先得到一个 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 是否有效、字节数组是否采用正确编码、文件内容是否匹配接口,都属于调用方自己的契约;发布器只负责把已经确定的数据来源接到请求构建器上。

字符串、字节数组、文件、输入流和 Flow.Publisher 适配到 BodyPublisher 的静态关系图
图2:数据来源与长度契约结构图,展示不同 BodyPublishers 工厂的输入边界和复用约束;这是说明图,不是运行截图。

把发布器绑定到 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.Publisher publisher = 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 方法;最后核对字符集、字节长度和流的可重复性。这样请求构建代码表达的是协议意图,数据读取代码则保留自己的资源边界。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java Files.move 怎样选择 ATOMIC_MOVE 与 REPLACE_EXISTINGJava Files.move 怎样选择 ATOMIC_MOVE 与 REPLACE_EXISTING
上一篇
Java Files.move 怎样选择 ATOMIC_MOVE 与 REPLACE_EXISTING
Java HttpResponse.BodyHandlers 怎样流式消费响应
下一篇
Java HttpResponse.BodyHandlers 怎样流式消费响应
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    720次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    726次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    802次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    763次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    599次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码