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

Spring Boot大文件JSON请求400/413错误配置方案咨询

大文件Base64 payload通过JSON传输的问题解决

问题描述

实现了一个HTTP客户端,可发送包含Base64文件的JSON请求体,小文件传输正常,但发送20MB以上大文件时出现错误,希望尽量不使用Multipart文件上传方式。

客户端实现代码

ObjectMapper objectMapper = MappersUtils.objectMapper();
String requestBody = objectMapper
        .writerWithDefaultPrettyPrinter()
        .writeValueAsString(request);

HttpRequest httpRequest = HttpRequest.newBuilder()
        .uri(URI.create(url + certificateName))
        .POST(HttpRequest.BodyPublishers.ofString(requestBody))
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .timeout(Duration.ofSeconds(600))
        .build();

HttpClient httpClient = HttpClient.newBuilder()
       .version(HttpClient.Version.HTTP_2)
       .connectTimeout(Duration.ofSeconds(10))
       .build();

CompletableFuture<HttpResponse<String>> response =
        httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString());

String result = response.thenApply(HttpResponse::body).get(120, TimeUnit.SECONDS);
int statusCode = response.thenApply(HttpResponse::statusCode).get(120, TimeUnit.SECONDS);
System.out.println(result);
System.out.println(statusCode);
return objectMapper.readValue(result, PdfConvertAndSignResponse.class);

Spring Boot服务端接收代码

@PostMapping(
        value = "/convert-and-sign/{certificateName}",
        consumes = {MediaType.APPLICATION_JSON_VALUE},
        produces = {MediaType.APPLICATION_JSON_VALUE})
@Operation(summary = "Convert into PDF/A Level (if it's required) and sign it ")
public ResponseEntity<PdfConvertAndSignResponse> convertAndSign(@RequestBody @Length(max = Integer.MAX_VALUE - 1) PdfConvertAndSignRequest request,
                                                                @PathVariable String certificateName) throws PdfException {
    String entryPoint = getEntrypoint(ServletUriComponentsBuilder.fromCurrentRequestUri());
    LocalDateTime startedAt = LocalDateTime.now(GlobalConstants.ZONE_ID);

    LogDto logDto = LogBuilderUtils.logDtoBuilder(
            request.getHeaderData().getService(),
            PdfConstants.WEBSERVICE,
            entryPoint,
            startedAt,
            HttpVerbs.POST.name());

    CertificateEntity certificateEntity = certificateRepository.findByCertificateName(certificateName);
    if (certificateEntity != null) {
        String pwd = b64ToString(certificateEntity.getPassword());
        PdfConvertAndSignRequestData data = MappersUtils.modelMapper().map(certificateEntity, PdfConvertAndSignRequestData.class);
        data.setPassword(pwd);
        List<PdfConvertAndSignRequestData> toProcess = new ArrayList<>();
        List<PdfConvertAndSignRequestData> currentData = request.getData();
        currentData.forEach(pdfSignRequestData -> {
            data.setPdf(pdfSignRequestData.getPdf());
            toProcess.add(data);
        });
        request.setData(toProcess);
        return doConvertAndSign(request, logDto);
    } else {
        throw new PdfSignRequestException("No certificate found", logDto);
    }
}

错误信息

客户端收到响应:

{"type":"about:blank","title":"Bad Request","status":400,"detail":"Failed to read request","instance":"/pdfmanager-ws/v1/convert-and-sign/mycertname"}

状态码400,同时抛出空指针异常:

java.lang.NullPointerException: Cannot invoke "java.util.List.get(int)" because the return value of "fr.dsidiff.pdf.client.model.response.PdfConvertAndSignResponse.getData()" is null

服务器access日志:

192.168.0.1 - - [05/Jul/2023:14:49:32 +0200] "POST /pdfmanager-ws/v1/convert-and-sign/mycertname HTTP/1.1" 400 161
192.168.0.1 - - [05/Jul/2023:14:49:47 +0200] "POST /pdfmanager-ws/v1/convert-and-sign/mycertname HTTP/1.1" 400 161

开启HTTP/2后出现413 – Payload Too Large错误。

现有配置

Tomcat server.xml配置

<Connector  port="8090" 
            protocol="HTTP/1.1"
            maxThreads="150" 
            connectionTimeout="-1"
            maxPostSize="109951162777600"
            maxSwallowSize="109951162777600"
            redirectPort="8443" />

Spring Boot application.properties配置

# maximum size of the request body/payload
server.tomcat.max-swallow-size=200MB
spring.servlet.multipart.max-file-size=200MB
# maximum size of entire POST request
server.tomcat.max-http-form-post-size=200MB
spring.servlet.multipart.max-request-size=200MB

解决方案

1. 调整Spring Boot核心请求体限制

spring.servlet.multipart系列配置仅针对Multipart请求,JSON请求体需要单独配置:

# 全局HTTP请求体大小限制(覆盖所有POST请求类型)
server.max-http-request-size=200MB
# Jackson解析器最大字符串长度(适配Base64大文件)
spring.jackson.parser.max-string-length=273066667 # 对应200MB原文件的Base64编码长度(原文件*4/3)

2. 适配HTTP/2的Tomcat配置

开启HTTP/2后需要调整帧大小和并发流限制:

server.http2.enabled=true
# 调大HTTP/2帧大小(默认16KB,建议设为64KB)
server.tomcat.http2.max-frame-size=65536
# 增加并发流数量
server.tomcat.http2.max-concurrent-streams=100

3. 客户端代码优化(避免大内存占用)

不要一次性将Base64大文件转为字符串,改用流式传输减少内存压力:

ObjectMapper objectMapper = MappersUtils.objectMapper();

HttpRequest httpRequest = HttpRequest.newBuilder()
        .uri(URI.create(url + certificateName))
        // 用流式生成请求体,避免大字符串占用内存
        .POST(HttpRequest.BodyPublishers.ofInputStream(() -> {
            try {
                return new ByteArrayInputStream(objectMapper.writeValueAsBytes(request));
            } catch (JsonProcessingException e) {
                throw new UncheckedIOException(e);
            }
        }))
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .timeout(Duration.ofSeconds(600))
        .build();

HttpClient httpClient = HttpClient.newBuilder()
       .version(HttpClient.Version.HTTP_2)
       .connectTimeout(Duration.ofSeconds(10))
       .build();

// 流式读取响应,避免大字符串加载到内存
CompletableFuture<HttpResponse<InputStream>> response =
        httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofInputStream());

try (InputStream in = response.thenApply(HttpResponse::body).get(120, TimeUnit.SECONDS)) {
    PdfConvertAndSignResponse result = objectMapper.readValue(in, PdfConvertAndSignResponse.class);
    int statusCode = response.thenApply(HttpResponse::statusCode).get(120, TimeUnit.SECONDS);
    System.out.println(statusCode);
    return result;
}

4. 服务端代码健壮性优化

添加请求体非空校验,避免解析失败时触发空指针:

public ResponseEntity<PdfConvertAndSignResponse> convertAndSign(@RequestBody @Length(max = Integer.MAX_VALUE - 1) PdfConvertAndSignRequest request,
                                                               @PathVariable String certificateName) throws PdfException {
    // 新增请求数据校验
    if (request == null || request.getData() == null || request.getData().isEmpty()) {
        LogDto errorLog = LogBuilderUtils.logDtoBuilder(
                null, PdfConstants.WEBSERVICE, 
                getEntrypoint(ServletUriComponentsBuilder.fromCurrentRequestUri()),
                LocalDateTime.now(GlobalConstants.ZONE_ID), HttpVerbs.POST.name());
        throw new PdfSignRequestException("Invalid or empty request data", errorLog);
    }

    // 原有业务代码...
}

内容的提问来源于stack exchange,提问作者davidvera

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 06:57:03