繁体   English   中英

在 Spring Boot 2.1 MultipartFile - 启用 SSL(https)时上传文件时,几乎 50% http 400 bad request error

[英]Got almost 50% http 400 bad request error on Spring Boot 2.1 MultipartFile - file upload when enable SSL(https)

我有一个 API 服务器,它在公共域上使用 Spring Boot 2.1 构建,它也提供 API 和文件上传。

最近几天,我们想升级这个 Spring 引导服务器以使用 SSL (https)。 在我们在 Spring Boot 中设置 SSL 设置之前。 用于文件上传的 API 非常好用(100% 上传成功)。

在我们在 Spring 引导中设置 SSL 设置之后。 用于文件上传的 API 有效,但只有 50% 上传成功,其他 50% 得到 http 400 错误请求。 (我们确定问题与前端 web 无关,因为我们使用 Swagger 与 Spring 捆绑在一起启动测试可以得到相同的结果)

我们查找 Spring Boot 的服务器日志。 当发生 http 400 bad request 时,没有任何关于 http 400 bad request 的日志。 我们研究了很多天并在互联网上进行了调查,但仍然无法解决这个问题。 请给予帮助。

我们已经尝试禁用 csrf(在属性文件中或通过配置类)和许多其他在互联网上提供但仍然无法正常工作的解决方案。

环境:Spring Boot 2.1.13(这是Spring Boot 2.1的最新版本)

属性文件中的设置:(仅在属性文件中添加了SSL设置部分,并且SSL(https)已成功打开)

# SSL setup
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=abcdef
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat
server.ssl.enabled-protocols=TLSv1.1,TLSv1.2
server.http2.enabled=true
security.basic.enabled=false
security.enable-csrf=false

## MULTIPART (MultipartProperties)
# Enable multipart uploads
spring.servlet.multipart.enabled = true
# Threshold after which files are written to disk.
spring.servlet.multipart.file-size-threshold=2KB
# Max file size.
spring.servlet.multipart.max-file-size=100MB
# Max Request Size
spring.servlet.multipart.max-request-size=115MB

我的 controller 用于文件上传:

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import java.util.Collections;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@Slf4j
@RestController
@RequestMapping(value = "/v1/fileupload")
@Api(tags = {"fileupload api"}, value = "fileupload")
@SwaggerDefinition(tags = {
    @Tag(name = "fileupload api", description = "apis for file upload")
})
public class FileUploadController {

    @Autowired
    private FileUploadService fileUploadService;

    private ApiUtilHelper helper = new ApiUtilHelper();

    @ApiOperation(value = "upload single data import file")
    @RequestMapping(
        value = "/dataimport",
        method = RequestMethod.POST,,
        consumes = { MediaType.MULTIPART_FORM_DATA_VALUE },
        produces = { MediaType.APPLICATION_JSON_UTF8_VALUE }
    )
    public ResponseEntity<?> uploadSingleFileForDataImport(@RequestParam("file") MultipartFile file) throws FileStorageException {
        log.info("Enter into uploadSingleFileForDataImport");
        FileUploadResponse fileUploadResponse = fileUploadService.storeFile(file, "dataImport");
        Map<String, Object> additionals = Collections.singletonMap("filupload", fileUploadResponse);
        BasicResponse br = helper.createSuccessBaseResponse(ApiSuccessCode.CreateSuccess, additionals);
        return new ResponseEntity<BasicResponse>(br, ApiSuccessCode.CreateSuccess.getHttpStatus());
    }

Swagger 测试结果:

Request URL: https://example.com:8443/v1/fileupload/dataimport
Request Method: POST
Status Code: 400 
Remote Address: 111.222.111.222:8443
Referrer Policy: no-referrer-when-downgrade

**Response http header from Spring Boot**
Connection: close
Content-Length: 0
Date: Mon, 20 Apr 2020 13:13:02 GMT

**Request http header from Swagger**
accept: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,ja;q=0.5
Connection: keep-alive
Content-Length: 484098
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryiXmuHnaNthhXowmb
Cookie: _ga=GA1.2.1299976434.1580821082; JSESSIONID=2C157019D6560405CC75A5F5083DE0AE
Host: example.com:8443
Origin: https://example.com:8443
Referer: https://example.com:8443/swagger-ui.html
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36

2020.04.20 13:44(UTC 时间)补充信息如下:谢谢@nbalodi,当我设置 logging.level.org.springframework.web=DEBUG。 我现在得到了错误日志。 附上的日志如下:

HttpEntityMethodProcessor : No match for [application/json;charset=UTF-8], supported: []
ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present]
DispatcherServlet : Completed 400 BAD_REQUEST

奇怪的是,这种错误情况只发生在我们使用如上所述的 ssl 设置时。

谢谢大家的回复。 我认为这是 SpringBoot 的错误或其在 SpringBoot v2.1.x 版本中嵌入的 Tomcat 错误。 SpringBoot v2.3.0 正式版发布时。 我使用相同的代码升级到 v2.3.0 现在一切正常。 我在文件上传中使用批量测试或称为压力测试。 现在已经100%成功了。

Update: The root cause is Tomcat component is not stable with Spring framework - Multipart under the https and/or http2 or Spring security (such like OAuth2) enabled.

在 Spring Boot 2.3.0 - 2.3.2 大部分情况下,文件上传失败率为 4 ~ 14 ~ 50%。 Spring Boot 2.3.5到2.4.0版本后,文件上传失败率接近50%。 关键的不同是 Tomcat 版本和 Spring 安全版本在这些 Spring 引导版本之间是不同的。

另一个问题是Tomcat支持的http2协议不稳定。 如果启用 http2(例如在属性文件中设置 server.http2.enabled=true)并使用 Multipart 或 HttpServletRequest 进行文件上传,则失败率上升到接近 90% 并获得连接。

结论:对于任何面临文件上传不稳定问题并获得 ERR_CONNECTION_CLOSED 并找到错误日志的人

org.apache.catalina.connector.ClientAbortException: org.apache.coyote.CloseNowException: Connection [{0}], Stream [{1}], This stream is not writable

原因:org.apache.coyote.CloseNowException:连接 [{0}],Stream [{1}],此 ZF7B44CFAFD5C52223D5498196C8A2E7B 不可写

引起:org.apache.coyote.http2.StreamException: Connection [{0}], Stream [{1}], 这个 ZF7B44CFAFD5C52223D5498196C8A2EB 不可写

您可以尝试升级到 Spring Boot 2.4.0 并通过在属性中设置 server.http2.enabled=false 来禁用 Spring Boot 中 Tomcat 的 http2 支持

您可能会发现稳定性甚至提高到 100% 的成功率。 (我尝试并实施给我所有的客户)

如果禁用 http2 支持对您不适用。 您可以尝试降级到 Spring Boot 2.3.0 - 2.3.2 版本或通过 HttpServletRequest 实现文件上传(记得先禁用 Spring Boot 中的 Multipart)

你可以在这里那里找到相关的开发文档

是否可以将您的日志级别更改为调试? 即 logging.level.org.springframework.web=DEBUG。

试试这个可能会有所帮助。 看不到您的代码有任何其他问题。

@RequestBody MultipartFile[] submissions

应该

@RequestParam("file") MultipartFile[] submissions

这些文件不是请求正文,它们是它的一部分,并且没有内置的HttpMessageConverter可以将请求转换为MultiPartFile数组。

您还可以使用MultipartHttpServletRequest ,它使您可以访问各个部分的标头。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM