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

MultiPartEntityBuilder单元测试抛出ContentTooLongException问题

问题描述

基于javax.servlet开发的应用中实现了一个代理类,负责为POST请求构造multipart form data格式的请求体。实际调用servlet接口开展回归测试时,应用运行完全符合预期,但对应的单元测试始终执行失败。

  • 异常抛出代码行:String httpPostBody = IOUtils.toString(httpPost.getEntity().getContent());
  • 具体异常触发点:httpPost.getEntity().getContent()调用
  • 完整异常信息:
org.apache.http.ContentTooLongException: Content length is unknown

at org.apache.http.entity.mime.MultipartFormEntity.getContent(MultipartFormEntity.java:101)
at gov.va.cds.test.CDSRequestBuilderTest.testCreatePostRequest(CDSRequestBuilderTest.java:117)
关联代码

单元测试代码

@Test
public void testCreatePostRequest() throws IOException, ServletException, URISyntaxException, NullPointerException {
    URI newTargetUri = new URI(targetUriPost);
    cdsRequestBuilder = new CDSRequestBuilder();
    HttpPost httpPost = cdsRequestBuilder.createPostRequest(mockClientRequest, newTargetUri);

    // Parse the entity
    String httpPostBody = IOUtils.toString(httpPost.getEntity().getContent());

    // Perform evaluation
    Assert.assertNotNull(httpPost);
    Assert.assertEquals(httpPost.getHeaders(HttpHeaders.HOST)[0].toString(), ("Host: " + newTargetUri.getHost()));
    Assert.assertTrue(httpPost.getHeaders(HttpHeaders.CONTENT_LENGTH).length == 0);
    Assert.assertTrue(httpPostBody.contains("payload"));
    Assert.assertTrue(httpPostBody.contains("file"));
    Assert.assertTrue(httpPostBody.contains(testFileBinaryData));
}

被测方法核心逻辑

private HttpEntity convertInputStreamToMultiPart(Collection<Part> clientParts, String boundary) throws IOException {
    Part payload = null;
    Part file = null;

    for (Part part : clientParts) {
        if (part.getName().equalsIgnoreCase("payload")) {
            payload = part;
        } else if (part.getName().equalsIgnoreCase("file")) {
            file = part;
        }
    }

    if(payload == null) {
        logIt.error("Failed to extract payload - no payload found");
        throw new IllegalStateException("Payload is missing from client request.");
    }

    if(file == null || file.getSubmittedFileName().length() == 0) {
        logIt.error("Failed to extract file - no file found");
        throw new IllegalStateException("File is missing from client request.");
    }

    logIt.info("Payload is parsed from client: {}", IOUtils.toString(payload.getInputStream()));

    return MultipartEntityBuilder.create()
            .addTextBody("payload", IOUtils.toString(payload.getInputStream()))
            .addBinaryBody("file", file.getInputStream(), ContentType.parse("application/pdf"), file.getSubmittedFileName())
            .setContentType(ContentType.parse(MediaType.APPLICATION_FORM_URLENCODED))
            .setBoundary(boundary)
            .build();
}

单元测试Mock逻辑

Part mockFilePart = mock(Part.class);
when(mockFilePart.getName()).thenReturn("file");
when(mockFilePart.getSubmittedFileName()).thenReturn(testFileName);
when(mockFilePart.getInputStream()).thenReturn(IOUtils.toInputStream(testFileBinaryData));

补充背景:此前将文件输入流临时保存为File对象,再传入addBinaryBody构造二进制请求体时,单元测试可正常通过;基于性能测试结果优化代码后,改为直接传入文件输入流对象构造请求体,随即出现上述测试失败问题。

问题根因
  1. 流式实体的接口限制:直接传入InputStream给MultipartEntityBuilder.addBinaryBody()时,构造出的MultipartFormEntity属于非缓冲流式实体,这类实体不会预先加载流内容计算总长度,其getContent()方法的源码逻辑明确要求必须已知内容长度,否则直接抛出ContentTooLongException。之前传入File对象时,构造的是可计算长度的非流式实体,因此不会触发该异常。
  2. 测试逻辑与实际运行路径不一致:实际业务运行发送HTTP请求时,HttpClient不会调用getContent()方法读取实体内容,而是通过writeTo(OutputStream)方法直接将流内容写出到网络通道,因此回归测试完全正常;只有单元测试中主动调用getContent()读取内容时才会触发异常。
  3. 隐藏的业务代码bug:当前代码给multipart实体设置的Content-Type为application/x-www-form-urlencoded,不符合multipart请求的格式规范,正确值应为multipart/form-data,该问题会导致下游服务无法正常解析请求体。
修复方案
  • 修正单元测试的内容读取逻辑:不要调用getContent()方法读取实体,改用实体的writeTo()方法将内容输出到内存流后再解析,该路径和实际发请求的逻辑完全一致,不会触发长度校验。示例代码:
    // 替换原IOUtils.toString(httpPost.getEntity().getContent())逻辑
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    httpPost.getEntity().writeTo(baos);
    String httpPostBody = baos.toString(StandardCharsets.UTF_8);
    
  • 修正业务代码的Content-Type配置错误:将.setContentType(ContentType.parse(MediaType.APPLICATION_FORM_URLENCODED))修改为.setContentType(ContentType.MULTIPART_FORM_DATA),避免请求格式不符合规范。
  • 非必要不推荐的可选方案:如果确实需要调用getContent()读取实体,可以在构造MultipartEntity时调用setBufferContent(true)开启内容缓冲,构造阶段会将流内容全部读入内存计算长度,但会增加内存开销,抵消直接传流的性能优化收益。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 22:54:17