Spring 引导多部分文件始终为空
Spring Boot multipartfile always null
我正在使用 Spring 引导版本 = '1.4.0.RC1' 和 Spring Boot Stormpath 1.0.2.
我正在尝试使用分段文件上传,但控制器中的 MultipartFile 始终为 null。
当我使用@RequestPart("file") 时,信息:"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present"
当我使用@RequestPart(name = "file", required = false) 时,该部分始终为空。
但是,如果我向控制器添加一个 HttpServletRequest 参数,我可以直接从请求中获取文件部分,所以我知道它确实存在。
这是控制器,在下面的代码中 checkNotNull(part)
总是成功而 checkNotNull(imageFile)
总是失败:
@PostMapping("{username}/profilePhoto")
public ResponseEntity<?> saveProfilePhoto(@PathVariable("username") String username,
@RequestPart(name = "file", required = false) MultipartFile imageFile,
HttpServletRequest request) {
try {
Part part = request.getPart("file");
checkNotNull(part);
checkNotNull(imageFile);
} catch (IOException | ServletException ex) {
throw InternalServerErrorException.create();
}
// Transfer the multipart file to a temp file
File tmpFile;
try {
tmpFile = File.createTempFile(TMP_FILE_PREFIX, null);
imageFile.transferTo(tmpFile);
} catch (IOException ex) {
log.error("Failed to create temp file", ex);
throw InternalServerErrorException.create();
}
// Execute the use case
updateUserProfilePhoto.execute(username, tmpFile);
// Delete the temp file
FileUtils.deleteQuietly(tmpFile);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
我的集成测试使用改造:
@Multipart
@POST("users/{username}/profilePhoto")
Call<Void> uploadProfilePhoto(@Path("username") String username,
@Part("file") RequestBody profilePhoto);
...
@Test
public void saveProfilePhoto_shouldSavePhoto() throws IOException {
// Given
String usernamme = usernames[0];
Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
File imageFile = testImageResource.getFile();
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("image/*"), imageFile);
// When
Response<Void> response = getTestApi().uploadProfilePhoto(usernamme, body).execute();
// Then
assertThat(response.code()).isEqualTo(201);
}
我正在使用自动配置,所以我唯一的自定义配置 class 配置 Stormpath:
@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.apply(stormpath());
}
}
更新:
这是传出请求。我不确定如何在多部分解析器本身中启用日志记录。
2016-08-18 14:44:14.714 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : --> POST http://localhost:8080/users/user1/profilePhoto http/1.1
2016-08-18 14:44:14.714 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Content-Type: multipart/form-data; boundary=fe23ef21-3413-404c-a260-791c6921b2c6
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Content-Length: 181212
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Accept: application/json
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Authorization: Bearer [token]
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig :
2016-08-18 14:44:14.735 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : --fe23ef21-3413-404c-a260-791c6921b2c6
Content-Disposition: form-data; name="file"
Content-Transfer-Encoding: binary
Content-Type: image/*
Content-Length: 180999
file data
--fe23ef21-3413-404c-a260-791c6921b2c6--
2016-08-18 14:44:14.762 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : --> END POST (181212-byte body)
对正在发生的事情有什么想法吗?
您必须启用 Spring Multipart Resolver,因为默认情况下 Spring 不启用多部分功能。
By default, Spring does no multipart handling, because some developers
want to handle multiparts themselves. You enable Spring multipart
handling by adding a multipart resolver to the web application’s
context.
在您的配置中 class 您需要添加以下 bean:
@Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
*** 更新 ***
因为我之前的回答根据评论是不正确的。这是我能够 运行 成功的更新示例。
@SpringBootApplication
public class WhosebugWebmvcSandboxApplication {
public static void main(String[] args) {
SpringApplication.run(WhosebugWebmvcSandboxApplication.class, args);
}
@Controller
public class UploadPhoto {
@PostMapping("{username}/profilePhoto")
public ResponseEntity<String> saveProfilePhoto(@PathVariable("username") String username,
@RequestPart(name = "file", required = false) MultipartFile imageFile, HttpServletRequest request) {
String body = "MultipartFile";
if (imageFile == null) {
body = "Null MultipartFile";
}
return ResponseEntity.status(HttpStatus.CREATED).body(body);
}
}
}
这是一个非常基本的测试,没有什么特别的东西。然后我创建了一个邮递员请求,这里是示例 curl 调用:
curl -X POST -H "Cache-Control: no-cache" -H "Postman-Token: 17e5e6ac-3762-7d45-bc99-8cfcb6dc8cb5" -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "file=@" "http://localhost:8080/test/profilePhoto"
响应是 MultipartFile
,这意味着它不是空的,并且在该行上进行调试显示变量中填充了我正在上传的图像。
我发现问题出在我使用 Retrofit 构建请求的方式上。
Spring 的多部分解析器要求文件的文件名出现在该部分的内容配置字段中。没有这个,它不会将文件添加到多部分请求中。
根据此处找到的信息:https://futurestud.io/blog/retrofit-2-how-to-upload-files-to-server,我的 API 界面应该是:
@Multipart
@POST("users/{username}/profilePhoto")
Call<Void> uploadProfilePhoto(@Path("username") String username,
@Part MultipartBody.Part profilePhoto);
然后在我的测试中拨打电话时:
// Given
String usernamme = usernames[0];
Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
File imageFile = testImageResource.getFile();
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", imageFile.getName(), requestFile);
// When
Response<Void> response = testApi.uploadProfilePhoto(usernamme, filePart).execute();
您必须启用 Spring Multipart Resolver,因为默认情况下 Spring 不会启用多部分功能。
By default, Spring does no multipart handling, because some developers
want to handle multiparts themselves. You enable Spring multipart
handling by adding a multipart resolver to the web application’s
context.
在您的配置中 class 您需要添加以下 bean:
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
因为 spring boot 1.4+ 使用 servlet 3.0+ 我们可以利用 StandardServletMultipartResolver
而不是 classic CommonsMultipartResolver
请参考我在此URL的回答:
Spring Boot multipart upload getting null file object
基本上,您必须避免 multipart 的默认 Spring 配置,并避免在请求中设置 Content-type 或边界 header。
检查您的 application.properties 中是否存在以下行:
spring.http.multipart.enabled = true
我正在使用 Spring 引导版本 = '1.4.0.RC1' 和 Spring Boot Stormpath 1.0.2.
我正在尝试使用分段文件上传,但控制器中的 MultipartFile 始终为 null。
当我使用@RequestPart("file") 时,信息:"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present"
当我使用@RequestPart(name = "file", required = false) 时,该部分始终为空。
但是,如果我向控制器添加一个 HttpServletRequest 参数,我可以直接从请求中获取文件部分,所以我知道它确实存在。
这是控制器,在下面的代码中 checkNotNull(part)
总是成功而 checkNotNull(imageFile)
总是失败:
@PostMapping("{username}/profilePhoto")
public ResponseEntity<?> saveProfilePhoto(@PathVariable("username") String username,
@RequestPart(name = "file", required = false) MultipartFile imageFile,
HttpServletRequest request) {
try {
Part part = request.getPart("file");
checkNotNull(part);
checkNotNull(imageFile);
} catch (IOException | ServletException ex) {
throw InternalServerErrorException.create();
}
// Transfer the multipart file to a temp file
File tmpFile;
try {
tmpFile = File.createTempFile(TMP_FILE_PREFIX, null);
imageFile.transferTo(tmpFile);
} catch (IOException ex) {
log.error("Failed to create temp file", ex);
throw InternalServerErrorException.create();
}
// Execute the use case
updateUserProfilePhoto.execute(username, tmpFile);
// Delete the temp file
FileUtils.deleteQuietly(tmpFile);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
我的集成测试使用改造:
@Multipart
@POST("users/{username}/profilePhoto")
Call<Void> uploadProfilePhoto(@Path("username") String username,
@Part("file") RequestBody profilePhoto);
...
@Test
public void saveProfilePhoto_shouldSavePhoto() throws IOException {
// Given
String usernamme = usernames[0];
Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
File imageFile = testImageResource.getFile();
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("image/*"), imageFile);
// When
Response<Void> response = getTestApi().uploadProfilePhoto(usernamme, body).execute();
// Then
assertThat(response.code()).isEqualTo(201);
}
我正在使用自动配置,所以我唯一的自定义配置 class 配置 Stormpath:
@Configuration
public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.apply(stormpath());
}
}
更新: 这是传出请求。我不确定如何在多部分解析器本身中启用日志记录。
2016-08-18 14:44:14.714 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : --> POST http://localhost:8080/users/user1/profilePhoto http/1.1
2016-08-18 14:44:14.714 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Content-Type: multipart/form-data; boundary=fe23ef21-3413-404c-a260-791c6921b2c6
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Content-Length: 181212
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Accept: application/json
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : Authorization: Bearer [token]
2016-08-18 14:44:14.715 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig :
2016-08-18 14:44:14.735 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : --fe23ef21-3413-404c-a260-791c6921b2c6
Content-Disposition: form-data; name="file"
Content-Transfer-Encoding: binary
Content-Type: image/*
Content-Length: 180999
file data
--fe23ef21-3413-404c-a260-791c6921b2c6--
2016-08-18 14:44:14.762 DEBUG 13088 --- [ main] c.t.server.web.testutil.TestConfig : --> END POST (181212-byte body)
对正在发生的事情有什么想法吗?
您必须启用 Spring Multipart Resolver,因为默认情况下 Spring 不启用多部分功能。
By default, Spring does no multipart handling, because some developers want to handle multiparts themselves. You enable Spring multipart handling by adding a multipart resolver to the web application’s context.
在您的配置中 class 您需要添加以下 bean:
@Bean
public MultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
*** 更新 *** 因为我之前的回答根据评论是不正确的。这是我能够 运行 成功的更新示例。
@SpringBootApplication
public class WhosebugWebmvcSandboxApplication {
public static void main(String[] args) {
SpringApplication.run(WhosebugWebmvcSandboxApplication.class, args);
}
@Controller
public class UploadPhoto {
@PostMapping("{username}/profilePhoto")
public ResponseEntity<String> saveProfilePhoto(@PathVariable("username") String username,
@RequestPart(name = "file", required = false) MultipartFile imageFile, HttpServletRequest request) {
String body = "MultipartFile";
if (imageFile == null) {
body = "Null MultipartFile";
}
return ResponseEntity.status(HttpStatus.CREATED).body(body);
}
}
}
这是一个非常基本的测试,没有什么特别的东西。然后我创建了一个邮递员请求,这里是示例 curl 调用:
curl -X POST -H "Cache-Control: no-cache" -H "Postman-Token: 17e5e6ac-3762-7d45-bc99-8cfcb6dc8cb5" -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "file=@" "http://localhost:8080/test/profilePhoto"
响应是 MultipartFile
,这意味着它不是空的,并且在该行上进行调试显示变量中填充了我正在上传的图像。
我发现问题出在我使用 Retrofit 构建请求的方式上。
Spring 的多部分解析器要求文件的文件名出现在该部分的内容配置字段中。没有这个,它不会将文件添加到多部分请求中。
根据此处找到的信息:https://futurestud.io/blog/retrofit-2-how-to-upload-files-to-server,我的 API 界面应该是:
@Multipart
@POST("users/{username}/profilePhoto")
Call<Void> uploadProfilePhoto(@Path("username") String username,
@Part MultipartBody.Part profilePhoto);
然后在我的测试中拨打电话时:
// Given
String usernamme = usernames[0];
Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
File imageFile = testImageResource.getFile();
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", imageFile.getName(), requestFile);
// When
Response<Void> response = testApi.uploadProfilePhoto(usernamme, filePart).execute();
您必须启用 Spring Multipart Resolver,因为默认情况下 Spring 不会启用多部分功能。
By default, Spring does no multipart handling, because some developers want to handle multiparts themselves. You enable Spring multipart handling by adding a multipart resolver to the web application’s context.
在您的配置中 class 您需要添加以下 bean:
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
因为 spring boot 1.4+ 使用 servlet 3.0+ 我们可以利用 StandardServletMultipartResolver
而不是 classic CommonsMultipartResolver
请参考我在此URL的回答: Spring Boot multipart upload getting null file object
基本上,您必须避免 multipart 的默认 Spring 配置,并避免在请求中设置 Content-type 或边界 header。
检查您的 application.properties 中是否存在以下行:
spring.http.multipart.enabled = true