Issue
I'd like to know if there's any idiomatic way to return data from a Spring Boot Service, via a REST controller, without having to resort to writing any File() objects to the disk of the service itself.
Below is an example, where I've managed to get a Rest Endpoint to return a ByteArray of the data, without resorting to disk use. Unfortunately, with this implementation I seem to lose any control over the final file extension and name.
(I'm guessing this is down to how whatever client, making the call, handles the response body of bytes)
@RestController
@RequestMapping("/dataComparison")
@Profile("!prod")
class ComparisonReportController(
private val reportService: ReportMappingService,
) {
/*
If you get errors parsing your csv file, make sure your file is saved in utf-8 encoding
*/
@PostMapping(
"/requestReport",
consumes = [MULTIPART_FORM_DATA_VALUE],
produces = [APPLICATION_OCTET_STREAM_VALUE]
)
@ResponseStatus(HttpStatus.OK)
fun getReport(
@RequestParam("file", required = true) file: MultipartFile,
@RequestParam(
"fromDate",
required = true
) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) fromDate: LocalDate,
@RequestParam("numDays", required = true) numDays: Long,
@RequestParam("reportType", required = true) reportType: ReportType,
): ByteArray {
withLoggingContext(getItemControllerTags()) {
log.info { "Received csv file of size [${file.size}b]. Beginning to parse the file..." }
if (numDays <= 0) {
throw IllegalArgumentException("The report cannot be run for $numDays days")
}
if (file.isEmpty) {
throw IllegalArgumentException("Cannot run a data comparison with an an empty file: ${file.name} ")
}
val config = createReportConfig(file, fromDate, numDays, reportType)
val report = reportService.getReport(config)
val lines = report.render()
val bytes = ReserveGroupReport.getCsvBytes(lines)
return bytes
}
}
}
Solution
You should set some headers to your response, to let the client know, that you return not just bytes, but a file. For example, you can do it directly by returning a ResponseEntity
from your controller with HttpHeaders
inside.
I'm sorry, but I don't know Kotlin and will show this in java, for a situation when you need to send a pdf file:
@PostMapping(value = "/requestReport", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<byte[]> getReport(MultipartFile file) {
byte[] bytes = ReportService.createReport();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDisposition(ContentDisposition.attachment().build());
headers.setContentDispositionFormData("report.pdf", "report.pdf");
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}
You can choose any disposition and file name here.
Answered By - AndrewThomas
Answer Checked By - Marie Seifert (JavaFixing Admin)