![](/img/trans.png)
[英]400 (Bad Request) Error - Spring-boot File upload Using JQuery AJAX
[英]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.