Reactive Spring: Webflux Multipart File Upload

A clear, simple example of multipart file upload using Reactive Spring

Spring Boot v2.0.3.RELEASE
Coding using reactive functional style (as opposed to annotation-based)
Spring Initializr Dependencies: “Reactive Web”
Language: Kotlin

Notes follow after the code:

package com.example.FileUploadExample
import com.example.FileUploadExample.HomeHandler
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.HandlerFunction
import org.springframework.web.reactive.function.server.RequestPredicates
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerResponse
@Configuration
class DefaultRouter{
@Bean
fun route(homeHandler: HomeHandler): RouterFunction<ServerResponse> {
return RouterFunctions
.route(RequestPredicates.POST("/upload/{id}").and(RequestPredicates.accept(MediaType.MULTIPART_FORM_DATA)),
HandlerFunction<ServerResponse> { homeHandler.upload(it) })
}
}

view raw
DefaultRouter.kt
hosted with ❤ by GitHub

package com.example.FileUploadExample
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class FileUploadExample
fun main(args: Array<String>) {
runApplication<FileUploadExample>(*args)
}

view raw
FileUploadExample.kt
hosted with ❤ by GitHub

package com.example.FileUploadExample
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.http.codec.multipart.FilePart
import org.springframework.http.codec.multipart.Part
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.BodyExtractors
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.bodyToServerSentEvents
import reactor.core.publisher.Mono
import java.io.File
@Component
class HomeHandler {
fun upload(request: ServerRequest): Mono<ServerResponse> {
// Not used in this example, but useful for logging, etc
val id = request.pathVariable("id")
return request.body(BodyExtractors.toMultipartData()).flatMap { parts >
val map: Map<String, Part> = parts.toSingleValueMap()
val filePart : FilePart = map["file"]!! as FilePart
// Note cast to "FilePart" above
// Save file to disk – in this example, in the "tmp" folder of a *nix system
val fileName = filePart.filename()
filePart.transferTo( File("/tmp/$fileName"))
ServerResponse.ok().body(BodyInserters.fromObject("OK"))
}
}
}

view raw
HomeHandler.kt
hosted with ❤ by GitHub

Notes:

  • Spring Webflux makes it extremely easy to build reactive web services – but it takes a while to get used to the terms used to enable effective research and documentation lookup
  • The spring reactive framework is powered by project reactor – your first stop should be the documentation there.
  • Spring includes a number of really helpful “BodyExtractors” and “BodyInserters“. The former allows for easy extraction of information from the client request while the latter helps to build the server response. The hardest part of this exercise was realizing that “BodyExtractors.toMultipartData()” exists!
  • Casting the variable “filePart” as FilePart in line 28 of HomeHandler.kt is essential to expose the “transferTo()” method which makes it easy to save the file to disk
  • Sample curl command to use the above:
curl -F "[email protected]/home/dvas0004/Downloads/demo.zip" localhost:8080/upload/123

Note the “file” before the equals sign must match the map index used in line 28 of HomeHandler.kt