DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
腾讯云;让你的想法跑起来;轻量服务器 · 建站 / Docker / API;查看产品 →

Quarkus REST Client 声明 Latin-1,却仍发出 UTF-8 请求体

Quarkus 3.40.1 的 String 写出器忽略声明的 charset。通过原始字节确认差异,用严格编码的 byte[] 请求体保持 Latin-1 协议。

接口已经标注 @Consumes("text/plain; charset=ISO-8859-1"),服务端收到的 é 却变成了 é。若使用 Quarkus REST Client 3.40.1,先查看请求体的原始字节:这个版本的 String 写出器固定使用 UTF-8,Content-Type 中的 charset 不会改变它的编码。

以 é 为例,ISO-8859-1 应发送单字节 e9,UTF-8 则发送 c3 a9。请求头写着前者、字节采用后者,服务端按声明解码就会得到错误字符。只检查请求头,或者在日志里直接打印 Java String,都看不出这个差异。

先确认写出的是哪一种实体

以下接口形态会走 String 请求体序列化:

@POST
@Path("/raw")
@Consumes("text/plain; charset=ISO-8859-1")
String sendLatin1(String body);

这里假设客户端已经按项目现有方式注册,并且对端确实约定 Latin-1。3.40.1 的 StringMessageBodyHandler.writeTo 使用 StandardCharsets.UTF_8,没有根据传入的媒体类型选择字符集。这个结论针对该版本、该写出器;自定义 provider、其他客户端实现和 JSON 序列化需要分别检查。

用受控接收端查看原始 byte[]、长度和 Content-Type,避免先经过服务端文本解码。只记录测试内容,不把真实业务正文打进共享日志。若原始字节已经是 e9,故障就在其他环节,不能再套用这个原因。

对必须使用 Latin-1 的接口,明确传入字节

无法同步调整协议时,可以先在客户端边界编码,并让接口接收 byte[]:

@POST
@Path("/raw")
@Consumes("text/plain; charset=ISO-8859-1")
String sendLatin1(byte[] body);

3.40.1 的字节数组写出器直接向实体流写入数组内容,不再次做字符集转换。编码时保留严格失败行为,尤其不要让 Latin-1 无法表示的中文悄悄变成问号:

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;

static byte[] encodeLatin1(String text) throws CharacterCodingException {
    ByteBuffer encoded = StandardCharsets.ISO_8859_1.newEncoder()
            .onMalformedInput(CodingErrorAction.REPORT)
            .onUnmappableCharacter(CodingErrorAction.REPORT)
            .encode(CharBuffer.wrap(text));
    byte[] body = new byte[encoded.remaining()];
    encoded.get(body);
    return body;
}

调用处把这段函数的返回值交给原客户端,例如 client.sendLatin1(encodeLatin1("é"))。捕获编码异常后,应按业务规则拒绝或报告不兼容内容,不能改用替换字符后继续发送。这里从 ByteBuffer 的有效区间复制,不直接把可能带有多余容量的底层数组作为请求体。

如果双方都能迁移到 UTF-8,也可以协调修改协议与声明,继续传 String;不能只把请求头改成 UTF-8,而让仍按 Latin-1 工作的服务端独自承担变化。已经是其他编码的字节也不能重新按 Latin-1 编码一次。

验收要比较字节和解码结果

在同一个原调用入口检查 é 的原始字节为 e9,Content-Type 仍符合约定,服务端还原后字符正确。再覆盖空字符串、ASCII,以及一个 Latin-1 不能表示的字符:前两者应按协议发送,最后一项应明确失败,不应出现替换字节。

这项调整会改变客户端方法参数类型,需同步所有调用点;发布前保留原接口与编码代码的版本差异。回退时恢复配套调用,不单独留下 byte[] 接口或新的 charset 声明。后续采用框架修复时,先确认目标正式版本包含按媒体类型编码的实现,再用相同字节用例复核;问题被登记或主分支改变,都不等于当前部署版本已修复。