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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) }) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")) | |
} | |
} | |
} |
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 "file=@/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