We are currently developing a Spring MVC environment using functional routing like this:
router {
POST("/user-photo/", userImageHandler::create)
}
@Component("user.Image")
class UserImageHandler : PassHandler() {
fun create(serverRequest: ServerRequest) = success {
val multipartRequest = (serverRequest.servletRequest() as? MultipartHttpServletRequest) ?:
throw ResponseStatusException(
HttpStatus.NOT_ACCEPTABLE,
"Request is not a multipart request"
)
val file: MultipartFile = multipartRequest.getFile("file") ?: throw ResponseStatusException(
HttpStatus.NOT_ACCEPTABLE,
"Missing file with name: file"
)
....
}
}
The ServerRequest is the https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/function/ServerRequest.html interface. The only way to access files seem to be the casting method we do right now - serverRequest.servletRequest().parts is empty.
This is the integration test:
val file = MockMultipartFile(
"blank.jpg",
"blank.jpg",
"image/jpg",
getImageBytes()
)
mockMvc.perform(
MockMvcRequestBuilders.multipart("/user-photo/")
.file(file)
.accept(MediaType.APPLICATION_JSON)
).andExpect(
MockMvcResultMatchers.status().isOk
)
In the integration test the serverRequest.servletRequest part is of type SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@71f08480] and thus not a decendant of MultipartHttpServletRequest.
Is there a smarter way to access multipart files with the ServerRequest interface? Otherwise I would think that HttpServletRequest is missing a getFiles() method.
I found an way to access multipart files but it's a few levels deep and would require a lot of casting:
Comment From: poutsma
We're going to add a method that exposes the parts in 5.3, but in the mean time you should be able to get them via HttpServletRequest::getParts, which was introduced in Servlet 3.0.
Comment From: jonasbark
@poutsma thank you, sounds good! Regarding your getParts reference - I already checked this and mentioned it in the initial post as well:
serverRequest.servletRequest().parts is empty.
So no luck with getParts as well it seems.
If it helps I can build a minimal project.
Comment From: poutsma
That's strange. Both SecurityContextHolderAwareRequestWrapper and HttpWriterFilter.HeaderWriterRequest extend Servlet's HttpServletRequestWrapper, so they should just pass the parts through.
I am not able to reproduce this behavior with this simple route:
@Bean
public RouterFunction<ServerResponse> route() {
return RouterFunctions.route()
.POST("/", request -> {
for (Part part : request.servletRequest().getParts()) {
System.out.println(part.getName() + " " + part.getSize());
}
return ServerResponse.ok().build();
})
.build();
}
when invoking the above with curl -F, the part names and sizes are printed as expected.
So please, do provide a minimal project that reproduces this behavior.
Comment From: jonasbark
I tested it with a real API request as well and it works. But what I'm really trying to achieve here is to get the integration test to run with MockMvc. I posted the integration test content in the initial post.
@Test
fun `test create`(): Unit = runBlocking {
val file = MockMultipartFile(
"blank.jpg",
"blank.jpg",
"image/jpg",
getImageBytes()
)
mockMvc.perform(
MockMvcRequestBuilders.multipart("/user-photo/")
.file(file)
.accept(MediaType.APPLICATION_JSON)
).andExpect(
MockMvcResultMatchers.status().isOk
)
Unit
}
I created a fairly minimal repo here: https://github.com/jonasbark/spring-framework-24909
Comment From: poutsma
As it turned out, the solution was to use part (and MockPart) on the builder, instead of file. MultipartFile is a Spring abstraction introduced before Servlet offered Part. As such, the HttpServletRequest does not "know" about MultipartFile objects, only Part.
This test works for me:
@Test
fun `test create`(): Unit = runBlocking {
val part = MockPart(
"blank.jpg",
"blank.jpg",
getImageBytes()
)
mockMvc.perform(
MockMvcRequestBuilders.multipart("/user-photo/")
.part(part)
.accept(MediaType.APPLICATION_JSON)
).andExpect(
MockMvcResultMatchers.status().isOk
)
Unit
}
Though your initial problem is fixed, I am going to keep this issue around as a feature request to expose multipart data on WebMvn.fn's ServerRequest directly, without resorting to HttpServletRequest.