Spring Boot 2.1 MultipartFile - 启用 SSL(https) 时文件上传几乎有 50% 的 http 400 错误请求
Got almost 50% http 400 bad request error on Spring Boot 2.1 MultipartFile - file upload when enable SSL(https)
我有一个 API 服务器,它在 public 域上使用 Spring Boot 2.1 构建,该域还提供 API 服务和文件上传。
最近几天,我们想升级此 Spring 引导服务器以使用 SSL (https)。
在我们设置 Spring Boot 中的 SSL 设置之前。文件上传的 API 非常好用(100% 上传成功)。
在 Spring Boot 中设置 SSL 设置之后。用于文件上传的 API 可以正常工作,但只有 50% 上传成功,其他 50% 收到 http 400 错误请求。 (我们确定问题与前端web无关,因为我们使用捆绑了Spring的Swagger启动测试可以得到相同的结果)
然后我们查找 Spring Boot 的服务器日志。当发生 http 400 错误请求时,没有任何关于 http 400 错误请求的日志。
我们研究了很多天,在网上进行了调查,但仍然无法解决这个问题。请给予帮助。
我们已经尝试禁用 csrf(在属性文件中或通过配置 class)和互联网上提供的许多其他解决方案,但仍然无效。
环境: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
我的文件上传控制器:
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 设置时才会发生。
是否可以将您的日志级别更改为 DEBUG?即 logging.level.org.springframework.web=DEBUG.
试试这个可能会有帮助。看不到您的代码有任何其他问题。
@RequestBody MultipartFile[] submissions
应该是
@RequestParam("file") MultipartFile[] submissions
文件不是请求 body,它们是请求的一部分,没有 built-in HttpMessageConverter
可以将请求转换为 MultiPartFile
的数组.
您也可以使用 MultipartHttpServletRequest
,这样您就可以访问各个部分的 headers。
谢谢大家的回复。
我认为这是 SpringBoot 的错误或其在 SpringBoot v2.1.x 版本中嵌入 Tomcat 的错误。
SpringBoot v2.3.0正式版发布时。我使用相同的代码升级到 v2.3.0
现在一切都可行。我在文件上传中使用批量测试或称为压力测试。
现在已经100%成功了。
更新:
根本原因是 Tomcat 组件在 Spring 框架下不稳定 - 启用 https and/or http2 或 Spring 安全性(例如 OAuth2)下的多部分。
大多数情况下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(例如在您的属性文件中设置服务器。http2.enabled=true)并使用 Multipart 或 HttpServletRequest 进行文件上传,失败率将上升到接近 90% 并获得连接。
结论:
对于遇到文件上传不稳定问题并得到 ERR_CONNECTION_CLOSED 并找到关于
的错误日志的任何人
org.apache.catalina.connector.ClientAbortException: org.apache.coyote.CloseNowException: 连接 [{0}], 流 [{1}], 此流不可写
原因:org.apache.coyote.CloseNowException:连接 [{0}]、流 [{1}]、此流不可写
原因:org.apache.coyote。http2.StreamException:连接 [{0}],流 [{1}],此流不可写
您可以尝试升级到 Spring Boot 2.4.0 并在 Spring Boot 中禁用 http2 支持 Tomcat 通过设置服务器。http2.enabled=false in您的属性文件
您可能会发现稳定性得到了提高,甚至达到了 100% 的成功率。 (我尝试并为所有客户实施)
如果禁用 http2 支持对您不可行。
您可以尝试降级到 Spring Boot 2.3.0 - 2.3.2 版本或通过 HttpServletRequest 实现文件上传(记得在 Spring Boot first 中禁用 Multipart)
我有一个 API 服务器,它在 public 域上使用 Spring Boot 2.1 构建,该域还提供 API 服务和文件上传。
最近几天,我们想升级此 Spring 引导服务器以使用 SSL (https)。 在我们设置 Spring Boot 中的 SSL 设置之前。文件上传的 API 非常好用(100% 上传成功)。
在 Spring Boot 中设置 SSL 设置之后。用于文件上传的 API 可以正常工作,但只有 50% 上传成功,其他 50% 收到 http 400 错误请求。 (我们确定问题与前端web无关,因为我们使用捆绑了Spring的Swagger启动测试可以得到相同的结果)
然后我们查找 Spring Boot 的服务器日志。当发生 http 400 错误请求时,没有任何关于 http 400 错误请求的日志。 我们研究了很多天,在网上进行了调查,但仍然无法解决这个问题。请给予帮助。
我们已经尝试禁用 csrf(在属性文件中或通过配置 class)和互联网上提供的许多其他解决方案,但仍然无效。
环境: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
我的文件上传控制器:
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 设置时才会发生。
是否可以将您的日志级别更改为 DEBUG?即 logging.level.org.springframework.web=DEBUG.
试试这个可能会有帮助。看不到您的代码有任何其他问题。
@RequestBody MultipartFile[] submissions
应该是
@RequestParam("file") MultipartFile[] submissions
文件不是请求 body,它们是请求的一部分,没有 built-in HttpMessageConverter
可以将请求转换为 MultiPartFile
的数组.
您也可以使用 MultipartHttpServletRequest
,这样您就可以访问各个部分的 headers。
谢谢大家的回复。 我认为这是 SpringBoot 的错误或其在 SpringBoot v2.1.x 版本中嵌入 Tomcat 的错误。 SpringBoot v2.3.0正式版发布时。我使用相同的代码升级到 v2.3.0 现在一切都可行。我在文件上传中使用批量测试或称为压力测试。 现在已经100%成功了。
更新: 根本原因是 Tomcat 组件在 Spring 框架下不稳定 - 启用 https and/or http2 或 Spring 安全性(例如 OAuth2)下的多部分。
大多数情况下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(例如在您的属性文件中设置服务器。http2.enabled=true)并使用 Multipart 或 HttpServletRequest 进行文件上传,失败率将上升到接近 90% 并获得连接。
结论: 对于遇到文件上传不稳定问题并得到 ERR_CONNECTION_CLOSED 并找到关于
的错误日志的任何人org.apache.catalina.connector.ClientAbortException: org.apache.coyote.CloseNowException: 连接 [{0}], 流 [{1}], 此流不可写
原因:org.apache.coyote.CloseNowException:连接 [{0}]、流 [{1}]、此流不可写
原因:org.apache.coyote。http2.StreamException:连接 [{0}],流 [{1}],此流不可写
您可以尝试升级到 Spring Boot 2.4.0 并在 Spring Boot 中禁用 http2 支持 Tomcat 通过设置服务器。http2.enabled=false in您的属性文件
您可能会发现稳定性得到了提高,甚至达到了 100% 的成功率。 (我尝试并为所有客户实施)
如果禁用 http2 支持对您不可行。 您可以尝试降级到 Spring Boot 2.3.0 - 2.3.2 版本或通过 HttpServletRequest 实现文件上传(记得在 Spring Boot first 中禁用 Multipart)