μ€νλ§ νμΌ λ€μ΄λ‘λ
μ€νλ§ νλ μμν¬ κΈ°λ°μ μΉ μ ν리μΌμ΄μ μμ νμΌ λ€μ΄λ‘λ μμ λ₯Ό μ°Ύμ보면 λλΆλΆμ μλΈλ¦Ώ μ€νμ HttpServletResponseμ OutputStream μ νμΌμ λ΄μ©μ μ°λ λ°©μμΌλ‘ μ€λͺ νλ κ²½μ°κ° λ§λ€. κ·Έλ¬λ, μ€νλ§ νλ μμν¬μμλ λ°μ΄νΈ μ²λ¦¬μ λν μΆμνκ° λμ΄μκΈ° λλ¬Έμ λ μ½κ³ κ°κ²°ν νμΌ λ€μ΄λ‘λ μμ μ½λλ₯Ό μμ±ν μ μλ€. μ΄λ¦¬μ 리 μ°Ύμ보며 νμ©ν μ μλ ν΄λμ€λ€μ ν΅ν΄ μλμ κ°μ΄ μ½λλ₯Ό μμ±ν΄λ³΄μλ€.
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@RestController
public class FileController {
@GetMapping("/files/sample.csv")
public ResponseEntity<byte[]> download() throws IOException {
Resource resource = new ClassPathResource("sample/file.csv");
byte[] bytes = resource.getContentAsByteArray(); // NOTE: Use FileCopyUtils.copyToByteArray
ContentDisposition contentDisposition = ContentDisposition.attachment()
.filename("νκΈνμΌλͺ
.csv", StandardCharsets.UTF_8)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(bytes);
}
}
<!DOCTYPE html>
<html>
<body>
<a href="/files/sample.csv">sample.csv</a>
</body>
</html>
Resource μ ResponseEntity
ResponseEntity<Resource>λ κ°λ₯νμ§λ§ λ λͺ
νν ννμ μν΄μ byte[]
λ₯Ό λͺ
μνμλ€. ResponseEntityλ₯Ό μ΄ μ΄μ λ Content-Disposition
ν€λλ₯Ό ν΅ν΄ νμΌλͺ
μ μ§μ νκ³ μΌλ°μ μΈ λ°μ΄λ리 μλ΅μ μλ―Ένλλ‘ application/octet-stream
μ μ€μ νκΈ° μν΄μμ΄λ€. HttpServletResponseλ₯Ό νΈλ€λ¬ ν¨μμ νλΌλ―Έν°λ‘ λ°μμ μ¬μ©ν μλ μμ§λ§ κ΅³μ΄ νμνμ§ μμμ 보μ¬μ€λ€.
FileCopyUtils.copyToByteArray
μ€νλ§ νλ μμν¬ 6 λΆν°λ μ€λμ λΆν° μ 곡νλ FileCopyUtils.copyToByteArray
λ₯Ό μ¬μ©νμ¬ Resourceλ₯Ό λ°μ΄νΈ λ°°μ΄λ‘ λ°κΎΈλ ν¨μλ₯Ό μ 곡νλ€. λ§μ½, μ€νλ§ νλ μμν¬ 5 μ΄νμ λ²μ μ΄λΌλ©΄ Resourceμ InputStreamμ κ°μ Έμμ FileCopyUtils.copyToByteArrayλ₯Ό μ§μ μ΄μ©νλ©΄ λλ€.
ContentDisposition
μΌλΆ μμ μμλ Content-Disposition ν€λλ₯Ό μ§μ νκΈ° μν΄μ λ¬Έμμ΄μ μ
λ ₯νλ κ²μ λ³Ό μ μλ€. μλͺ»λ κ²μ μλμ§λ§ μ¬λμ΄ μ
λ ₯νλλ° μ€μλ₯Ό ν μ μκΈ° λλ¬Έμ μ€νλ§ νλ μμν¬μ ν¬ν¨λ ContentDisposition
ν΄λμ€λ₯Ό μ΄μ©ν΄μ μ€μλ₯Ό λ°©μ§ν μ μλ€. μ¬μ§μ΄ νκΈλ‘ λ νμΌλͺ
μ μ§μ νκΈ° μν΄μλ URLEncoder
λ₯Ό μ¬μ©ν΄μΌνλλ° ContentDisposition ν΄λμ€ λ΄λΆμ μΌλ‘ RFC 6266 μ RFC 2047 μ λ°λΌ URL μΈμ½λ©μ μννλ―λ‘ μ΄μ λν κ³Όμ λ μλ΅ν μ μλ€.
μ΄λ€κ°μ? μ¬λ¬λΆμ΄ μμ±ν μ½λλ³΄λ€ κ°κ²°ν΄μ‘λμ? Downloading a file from spring controllers μμ λ λ§μ μμ μ½λλ₯Ό νμΈν μ μμ΅λλ€.
λμ©λ νμΌ λ€μ΄λ‘λ
μΌλ°μ μΈ νμΌ λ€μ΄λ‘λλ μμ κ°μ΄ λ°μ΄νΈ λ°°μ΄μ μλ΅νμ¬ μ²λ¦¬ν μ μμ§λ§ μ©λμ΄ ν° νμΌμ λ€μ΄λ‘λν΄μΌνλ κ²½μ°λΌλ©΄ μ ν리μΌμ΄μ λ©λͺ¨λ¦¬μ λΆλ΄μ΄ μμ μ μλ€. μ΄ κ²½μ° StreamingResponseBodyλ₯Ό νμ©νμ¬ μλμ κ°μ΄ νμΌμ μ€νΈλ¦¬λ°ν μ μλ€.
@RestController
public class FileController {
@GetMapping("/files/sample.mp4")
public ResponseEntity<StreamingResponseBody> download() {
Resource resource = new ClassPathResource("sample/sample.mp4");
StreamingResponseBody responseBody = output ->
StreamUtils.copy(resource.getInputStream(), new BufferedOutputStream(output)); // NOTE: 8192 bytes.
ContentDisposition contentDisposition = ContentDisposition.attachment()
.filename("λμμ.mp4", StandardCharsets.UTF_8)
.build();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
}
}
λμμ μ€νΈλ¦¬λ°
κ°λ¨ν λμμ μ€νΈλ¦¬λ°μ μ 곡νκ³ μΆμ κ²½μ° ResourceRegion
μ μ¬μ©νμ¬ λμμ νλ μ΄μ΄μμ μ 체 νμΌμ λ€μ΄λ‘λ λ°μ§ μμλ μνλ μμΉλΆν° λ€μ΄λ‘λ λ°μ μ μλλ‘ μ²λ¦¬ν μ μλ€. νμ§λ§, λλΆλΆμ μ€νΈλ¦¬λ° μ¬μ΄νΈμ κ²½μ° λμμ νμΌμ μκ² μͺΌκ°ν΄λκ³ CDNμΌλ‘ μ²λ¦¬νλ κ² κ°λ€.
@RestController
public class FileController {
@GetMapping("/sample.mp4")
public ResponseEntity<List<ResourceRegion>> streamingVideo(@RequestHeader HttpHeaders headers) throws MalformedURLException {
Resource resource = new UrlResource("https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_30mb.mp4");
List<ResourceRegion> resourceRegions = HttpRange.toResourceRegions(headers.getRange(), resource);
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
.header(HttpHeaders.ACCEPT_RANGES, "bytes")
.contentType(MediaType.parseMediaType("video/mp4"))
.body(resourceRegions);
}
@GetMapping("/files/sample.stream")
public ResponseEntity<Resource> streamingVideo() throws MalformedURLException {
Resource resource = new UrlResource("https://sample-videos.com/video321/mp4/720/big_buck_bunny_720p_30mb.mp4");
return ResponseEntity.ok().body(resource);
}
}
λμμ μ€νΈλ¦¬λ°μ λν΄μλ AbstractMessageConverterMethodProcessorμ ꡬνλμ΄ μμ΄μ InputStreamResourceκ° μλλΌλ©΄ HttpRangeλ₯Ό μ§μ μ¬μ©νμ§ μμλ μμμ μ²λ¦¬λλ€κ³ νλ€.