I’m working on a Spring Boot application where I’m trying to implement a file download feature using reactive programming. I have a little experience in this area, so please bear with me. I am able to download the file, but its content is encoded in base64 like this:
[{"nativeBuffer":"aW1wb3J0IGphdmEuaW8uSW5wdXRTdHJlYW07DQppbXBvcnQgamF2YS5uaW8uQnl0ZUJ1ZmZlcjsNCmltcG9ydCBqYXZhLnV0aWwuY29uY3VycmVudC5GbG93Ow0KaW1wb3J0IGphdmEudXRpbC5jb25jdXJyZW50LlN1Ym1pc3Npb25QdWJsaXNoZXI7DQoNCnB1YmxpYyBjbGFzcyBJbnB1dFN0cmVhbVB1Ymxpc2hlciBleHRlbmRzIFN1Ym1pc3Npb25QdWJsaXNoZXI8Qnl0ZUJ1ZmZlcj4gaW1wbGVtZW50cyBGbG93LlB1Ymxpc2hlcjxCeXRlQnVmZmVyPiB7DQoNCiAgICBwcml2YXRlIGZpbmFsIElucHV0U3RyZWFtIGlucHV0U3RyZWFtOw0KICAgIHByaXZhdGUgZmluYWwgaW50IGJ1ZmZlclNpemU7DQoNCiAgICBwdWJsaWMgSW5wdXRTdHJlYW1QdWJsaXNoZXIoSW5wdXRTdHJlYW0gaW5wdXRTdHJlYW0sIGludCBidWZmZXJTaXplKSB7DQogICAgICAgIHRoaXMuaW5wdXRTdHJlYW0gPSBpbnB1dFN0cmVhbTsNCiAgICAgICAgdGhpcy5idWZmZXJTaXplID0gYnVmZmVyU2l6ZTsNCiAgICB9DQoNCiAgICBAT3ZlcnJpZGUNCiAgICBwdWJsaWMgdm9pZCBzdWJzY3JpYmUoRmxvdy5TdWJzY3JpYmVyPD8gc3VwZXIgQnl0ZUJ1ZmZlcj4gc3Vic2NyaWJlcikgew0KICAgICAgICBzdXBlci5zdWJzY3JpYmUoc3Vic2NyaWJlcik7DQogICAgICAgIHN0YXJ0UmVhZGluZygpOw0KICAgIH0NCg0KICAgIHByaXZhdGUgdm9pZCBzdGFydFJlYWRpbmcoKSB7DQogICAgICAgIG5ldyBUaHJlYWQoKCkgLT4gew0KICAgICAgICAgICAgdHJ5IHsNCiAgICAgICAgICAgICAgICBieXRlW10gYnVmZmVyID0gbmV3IGJ5dGVbYnVmZmVyU2l6ZV07DQogICAgICAgICAgICAgICAgaW50IGJ5dGVzUmVhZDsNCiAgICAgICAgICAgICAgICB3aGlsZSAoKGJ5dGVzUmVhZCA9IGlucHV0U3RyZWFtLnJlYWQoYnVmZmVyKSkgIT0gLTEpIHsNCiAgICAgICAgICAgICAgICAgICAgQnl0ZUJ1ZmZlciBieXRlQnVmZmVyID0gQnl0ZUJ1ZmZlci53cmFwKGJ1ZmZlciwgMCwgYnl0ZXNSZWFkKTsNCiAgICAgICAgICAgICAgICAgICAgc3VibWl0KGJ5dGVCdWZmZXIpOw0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICBjbG9zZSgpOw0KICAgICAgICAgICAgfSBjYXRjaCAoRXhjZXB0aW9uIGUpIHsNCiAgICAgICAgICAgICAgICBjbG9zZUV4Y2VwdGlvbmFsbHkoZSk7DQogICAgICAgICAgICB9IGZpbmFsbHkgew0KICAgICAgICAgICAgICAgIGNsb3NlKCk7DQogICAgICAgICAgICB9DQogICAgICAgIH0pLnN0YXJ0KCk7DQogICAgfQ0KDQogICAgQE92ZXJyaWRlDQogICAgcHVibGljIHZvaWQgY2xvc2UoKSB7DQogICAgICAgIHRyeSB7DQogICAgICAgICAgICBpbnB1dFN0cmVhbS5jbG9zZSgpOw0KICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gZSkgew0KICAgICAgICAgICAgLy8gTG9nIG9yIGhhbmRsZSBleGNlcHRpb24NCiAgICAgICAgfQ0KICAgICAgICBzdXBlci5jbG9zZSgpOw0KICAgIH0NCn0NCg=="}]
Service:
public CompletableFuture<Flux<DataBuffer>> downloadFile(String bucketName, String key) {
return bucketManager.bucketExists(bucketName)
.thenCompose(bucket -> {
if (!bucket){
return CompletableFuture.failedFuture(new RuntimeException("Bucket does not exist"));
}
GetObjectRequest getRequest = GetObjectRequest.builder()
.bucket(bucketName)
.key(key)
.build();
return client.getObject(getRequest, AsyncResponseTransformer.toPublisher())
.thenApply(publisher -> Flux.from(publisher)
.map(byteBuffer -> new DefaultDataBufferFactory().wrap(byteBuffer))
);
});
}
Controller:
@GetMapping("/download")
public Mono<ResponseEntity<Flux<DataBuffer>>> handleFileDownload(@RequestParam("username") String username,
@RequestParam("fileName") String fileName) {
return Mono.fromFuture(() -> fileService.downloadFile(username, fileName))
.map(flux -> ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="" + fileName + """ )
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.body(flux))
.onErrorResume(e -> Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Flux.empty())));
}