Spring WebFluxで複数ファイルアップロード
特に必要に迫られたわけでもないのですが、WebFluxでリアクティブプログラミングにも触れておいたほうがよいのかなぁという気になりました。
浅い理解では、従来の1リクエスト1スレッドから、複数リクエスト1スレッドで処理できるのでスケーラビリティが向上するものだと思っています。言い換えれば、レスポンスやスループットが向上するというものではないということでいいんだと思います。ただし、ストリームでやり取りすることで終わったものから結果を受け取れて応答性はあがるんですかね。
ちょうど、ファイルをアップロードしてあれこれしたい要件があったのでファイルアップロードを対象に調べてみました。
コードは以下にあります。(気まぐれで消すかもしれないのでリンク切れていたらすいません)
https://github.com/nobrooklyn/garden/tree/master/ap-flux-fileup
複数ファイルをアップロードするのでFlux
Non-Blockingで実装しないといけないんだろうなぁということは分かっていてもお作法が分からないのでこれで良いのかはわかりませんが、動きはしました。
@RestController @Slf4j public class FileController { private final FileConfiguration conf; @Autowired public FileController(FileConfiguration conf) { this.conf = conf; } @ResponseBody @PostMapping( path = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public Flux<FileUploadResponse> upload(@RequestPart("f") Flux<FilePart> parts) { return parts.flatMap(part -> save(part)); } private Mono<FileUploadResponse> save(FilePart part) { String fileName = part.filename(); File file = new File("tmp/" + fileName); log.info("save to {}", file.getAbsolutePath()); return part.transferTo(file) .thenReturn(new FileUploadResponse(file, conf.getDownloadEndpoint())); } } @Getter @ToString @Slf4j public class FileUploadResponse { private String fileName; private String downloadUrl; private String contetnType; private long size; private String md5; FileUploadResponse(File file, String downloadEndpoint) { this.fileName = file.getName(); this.downloadUrl = downloadEndpoint + "/" + fileName; try { this.contetnType = Files.probeContentType(file.toPath()); } catch (IOException e) { this.contetnType = "no data"; } this.size = file.length(); try { this.md5 = DigestUtils.md5DigestAsHex(new FileInputStream(file)); } catch (IOException e) { this.md5 = "no data"; } } } @Component public class FileConfiguration { final static String downloadPath = "/download"; @Value("${file.download.url:http://localhost:8080}") private String downloadUrl; String getDownloadEndpoint() { return downloadUrl + downloadPath; } }
Servlet APIやその振る舞いに慣れている身としては「この場合やどうやって書くんや?」というのがちょいちょいあって慣れるまでもどかしい感じです。
少しずつ勉強していきたいと思います。