티스토리 뷰
앞선 글에서 이어지는 작업이다.
우리는 서버의 로컬 (프로젝트 폴더 외부)에 이미지나 동영상같은 파일을 저장하기로 결정했다.
개발을 진행하며 CRUD의 업로드까진 성공했다.
R- 클라이언트에서 파일을 읽는것도 성공할줄알았다...
1. path(string)로 클라이언트에서 서버의 로컬 파일에 접근한다 (-> 불가능)
우리는 DB에서 filePath, fileName을 저장하고있었고, 이를 통해 서버는 파일 path를 String으로 클라이언트로 보내도록 하였다. 클라이언트는 다음과 같이 로딩된다.<img src\="C:/@@/@@/.." ... \>
문제는 이런 경우, 파일이 정상적으로 로드되지않고, 캡쳐하진 못했지만 개발자도구 콘솔에서 permisson denied였나, 이런 에러메시지를 확인할수있다.
알아보니, 예전 IE에선 되기도 했다고 하나 최근의 대부분 브라우저에선 보안상의 이유로 막혀있다고 한다.
다시 생각해보니, 상식적으로도 서버의 로컬path에 마음대로 접근하는것이 말이 안되긴한다. (일반적으로 url로 접근하는 파일서버들은 별도 설정을 통해 열어줄것으로 추측)
이를 우회하기위해 검색해서 Spring boot의 config를 설정해 path를 매핑하는 방법도 나오긴하지만, 해결되지않았고 파일이 프로젝트 폴더 내부(resources/statics 같은)에 있을때 가능할것같았다.
참고자료
- https://zepinos.tistory.com/36 ★
- https://antananarivo.tistory.com/63 ★
- https://byson.tistory.com/20
- https://gdtbgl93.tistory.com/88
- https://m.blog.naver.com/PostView.nhn?blogId=kkm9509&logNo=220468785787&proxyReferer=https%3A%2F%2Fwww.google.com%2F
그래서 결국 파일을 통째로, 즉 바이너리 데이터를 통째로 주고받을수밖에 없었다.
점점 기술이 원시적으로 내려간다. "처음이니까..."란 마인드로 기본에 충실하자며 개발을 계속했다.
2. 서버에서 클라이언트로 파일(바이너리 데이터)을 통째로 보낸다
파일을 통째로 보낼때도 역시 쉽진 않았다.
이제부턴 그 구현방법에 대한 구체적인 내용이다. (읽지않아도 무관)
먼저 컨트롤러에선 filePath를 통해 파일에 접근해 파일을 가져온다.
자바에서 File과 path를 통해 File -> Byte[]를 만드는 방법은 쉬웠고,
ResponseEntity<byte[]>로 리턴시켜 프론트의 AJAX로 보내줄수있었다.
클라이언트에선 AJAX의 response로 받는데, 이를 arrayBuffer()를 태워 생긴 buffer를, Uint8Array(buffer)를 태워 byte[]까진 다시 만들수있었다.
만든 byte[]를 붙여 String으로 만든뒤 'data:image/png;base64;'뒤에 붙여 src에 넣어주면 이미지가 떴다.
문제는 바이너리 데이터가 통째로 들어있기 때문에, 다음과 같이 코드가 매우 길어진다. 말도안되는 상황인거다.
때문에 Blob으로 만들어 이 긴 문자들을 메모리에 보관하고, blob url로 src에서 접근해 이미지를 띄울수있었다.
주의할점은, Blob 생성자에 byte[]만 들어갈수있다는 점이다.
String 만들기전에 byte[]를 바로 blob에 넣어서 이미지를 띄울수있긴했다.
하지만, 네트워크 통신때 최소한 Base64인코딩을 해줘야할것같았다.
그래서 컨트롤러에서 Base64인코딩을 해준뒤 byte[]로 만들어 클라이언트로 보내고, 이를 클라이언트에서 다시 decode해서 이미지를 띄웠다.
이 과정에서 각 메소드를 거치며 적절히 변수타입을 맞춰주느라 힘들었다.
순서도 그림 그리기 귀찮아서 최종 흐름도는 다음과 같다.
최종 흐름도
- 서버
path에 접근해 File 생성 - byte[]로 변환 - Base64 인코딩 및 그 결과도 byte[]로 나온다 - ResponseEntity<>에 태워 클라이언트로 보낸다 - 클라이언트
response로 받는다 - response.arrayBuffer().then(function (buffer) { 로 buffer를 만든다 - buffer로 Uint8Array를 거쳐 byte[]로 만든다 - Base64 디코드를 위해 String으로 만들것임 - for문으로 binary라는 변수명의 String을 생성했다(코드참고) - atob 메소드로 Base64 디코드 및 그 결과도 String으로 나온다 - 복잡한 for문을 거쳐 다시 byte[]로 쪼개진다 - 이를 blob에 넣어 blob을 생성한다 - blob으로 url을 생성 - 이 url을 src에 넣으면 이미지가 드디어 뜬다!
Blob 참고자료
전체 참고자료
- https://medium.com/front-end-weekly/fetching-images-with-the-fetch-api-fb8761ed27b2
- https://stackoverflow.com/questions/20756042/javascript-how-to-display-image-from-byte-array-using-javascript-or-servlet
- https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
@GetMapping("/test1")
public String showImg(Model model) throws IOException {
File file = new File("C:/Users/@@@/Desktop/public/work/Tomcat/localhost/ROOT/articles/files/qwe.png");
byte[] byte1 = Files.readAllBytes(file.toPath());
byte[] base64 = Base64.getEncoder().encode(byte1);
String str1 = Base64.getEncoder().encodeToString(byte1);
model.addAttribute("byte", base64);
model.addAttribute("str", str1);
return "test";
}
@GetMapping("/test2")
public ResponseEntity<byte[]> showImg1() throws IOException {
File file = new File("C:/Users/@@@/Desktop/public/work/Tomcat/localhost/ROOT/articles/files/qwe.png");
byte[] byte1 = Files.readAllBytes(file.toPath());
byte[] base64 = Base64.getEncoder().encode(byte1);
String str1 = Base64.getEncoder().encodeToString(byte1);
return new ResponseEntity<>(base64, HttpStatus.OK);
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--<img th:src="'data:image/png;base64,'+${byte}" />-->
<!--<img th:src="'data:image/png;base64,'+${str}" />-->
<img id="img" src="">
</body>
<script th:inline="javascript">
/*<![CDATA[*/
var imgStr = [[${str}]];
var imgByte = [[${byte}]];
/*]]>*/
</script>
<script>
const b64StringToBlob = (b64Data, contentType='', sliceSize=512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
return blob;
};
fetch ('/articles/test2', {
method: "GET",
}).then(response => {
response.arrayBuffer().then(function (buffer) {
var bytes = new Uint8Array(buffer);
console.log('byte', bytes);
var binary = '';
bytes.forEach((b) => binary += String.fromCharCode(b));
const blob = b64StringToBlob(binary, {type: 'image/png'});
console.log('blob', blob);
const blobUrl = URL.createObjectURL(blob);
document.getElementById('img').src = blobUrl;
});
});
</script>
</html>
기억안나면 우테코 레벨2 미니프로젝트 코드를 다시 살펴보자.
마찬가지로 결론은 그냥 다음부턴 별도의 파일서버를 띄우던지, 맘편히 S3를 쓰자! (기본 공부는 충분히 한걸로..)
'Server' 카테고리의 다른 글
인수테스트 디렉토리 분리 (with gradle) (0) | 2020.01.12 |
---|---|
Spring Rest Docs로 API 문서화하기 (0) | 2020.01.12 |
Server Push 기술 / Pull vs Push + Poll 차이 (0) | 2019.11.12 |
이미지와 동영상 파일은 어디에 저장할것인가? (0) | 2019.09.14 |
- Total
- Today
- Yesterday
- 회고
- sort
- Android
- 웹해킹
- FRAGMENT
- OneToMany
- 개발자
- webhacking.kr
- git
- socket
- JPA
- reversing
- Algorithm
- bfs
- dfs
- javascript
- Stack
- Vo
- mysql
- queue
- Java
- 해외여행
- C
- graph
- 우아한 테크코스
- brute-force
- 리버싱
- Data Structure
- 프로그래머스
- Android Studio
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |