You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java 17中使用java.net发送POST多部分请求时BodyPublishers未添加Content-Length头导致S3预签名URL上传失败

Java 17中使用java.net发送POST多部分请求时BodyPublishers未添加Content-Length头导致S3预签名URL上传失败

看起来你遇到了Java版本差异带来的HttpClient行为变化问题——Java 12里正常运行的代码到Java 17就因为缺少Content-Length头被S3拒绝了,而且你不想引入第三方库,这个问题我来帮你解决。

问题根源

Java 17的HttpClient实现和Java 12有个关键区别:在处理HttpRequest.BodyPublishers.ofByteArrays()时,Java 12会自动计算并添加Content-Length头,但Java 17不再自动做这件事。而S3的预签名URL上传强制要求请求携带Content-Length头,用来验证请求的完整性,这就是报错的核心原因。

解决方案:手动计算并添加Content-Length头

我们只需要在构建请求前,提前计算整个multipart请求体的总字节数,然后手动把这个值添加到请求头里就行。具体步骤如下:

  1. 修改fileParams方法,让它同时返回请求体的BodyPublisher和计算好的总长度
  2. 在uploadFile方法中,用计算出的总长度手动添加Content-Length头

修改后的代码示例

首先创建一个简单的内部类,用来同时封装请求体和长度:

private static class BodyWithLength {
    private final HttpRequest.BodyPublisher publisher;
    private final long length;

    public BodyWithLength(HttpRequest.BodyPublisher publisher, long length) {
        this.publisher = publisher;
        this.length = length;
    }

    public HttpRequest.BodyPublisher getPublisher() {
        return publisher;
    }

    public long getLength() {
        return length;
    }
}

然后更新fileParams方法,计算总长度并返回BodyWithLength对象:

private BodyWithLength fileParams(LinkedHashMap<String,Object> data, String boundary, String mimeType) throws IOException {
    // Result request body
    List<byte[]> byteArrays = new ArrayList<>();

    // Separator with boundary
    byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=").getBytes(StandardCharsets.UTF_8);

    // Iterating over data parts
    for (Object keyAux : data.keySet()) {
        String key = keyAux.toString();
        if (key.equals("file")) continue;

        // Opening boundary
        byteArrays.add(separator);
        byteArrays.add(("\"" + key + "\"\r\n\r\n" + data.get(key) + "\r\n")
                .getBytes(StandardCharsets.UTF_8));
    }

    if (data.get("file") instanceof Path) {
        var path = (Path) data.get("file");
        byteArrays.add(separator);
        byteArrays.add(("\"" + "file" + "\"; filename=\"" + path.getFileName()
                + "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
        byteArrays.add(Files.readAllBytes(path));
        byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
    }

    // Closing boundary
    byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));

    // 计算请求体总字节数
    long totalLength = byteArrays.stream().mapToLong(byteArray -> byteArray.length).sum();

    return new BodyWithLength(HttpRequest.BodyPublishers.ofByteArrays(byteArrays), totalLength);
}

最后更新uploadFile方法,添加Content-Length头:

public HttpResponse<String> uploadFile(String url, LinkedHashMap<String, Object> params) throws URISyntaxException, IOException {
    String boundary = UUID.randomUUID().toString();
    String fileContentType = getMimeTypeFromData(params);
    BodyWithLength bodyWithLength = fileParams(params, boundary, fileContentType);

    HttpRequest request = HttpRequest.newBuilder()
            .header("Content-Type", "multipart/form-data; boundary=" + boundary)
            // 手动添加Content-Length头
            .header("Content-Length", String.valueOf(bodyWithLength.getLength()))
            .uri(new URI(url))
            .POST(bodyWithLength.getPublisher())
            .build();

    return execute(request);
}

额外提示

如果你的上传文件很大,Files.readAllBytes可能会导致内存溢出问题。如果需要处理大文件,你可以改用流式的方式构建请求体,但这需要更复杂的长度计算(比如提前统计文件大小加上multipart边界等部分的长度),不过当前的方案已经能解决你现在的问题了。

备注:内容来源于stack exchange,提问作者Tiberius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 09:24:31