블로그에 게시글을 쓰면 board_tb
의 content
컬럼으로 들어가게 된다.
만약 사진을 넣으면 어떻게 될까 ?
사진을 넣어보니 아래와 같은 데이터가 들어간다.
여기서 base64 은 인코딩방식을 말하는데 왜 사용하는지 알아보고 썸네일 기능을 추가해보자
사진이나 영상같은 여러 데이터들은 binary 데이터로 이루어져 있다.
사진은 수많은 픽셀로 이루어져 있는데 각각의 픽셀이 RGB를 가지고 있고 RGB는 255까지의 값을 가지므로 픽셀 하나를 표현하기 위해서는 8bit * 3 -> 24 bit의 데이터가 필요하다.
물론 사진은 픽셀 하나로 이루어져 있지 않다.
사진의 사이즈가 200x200 픽셀로 이루어져 있다면 픽셀의 위치데이터도 필요하게 되고 위치데이터를 포함하는 픽셀의 데이터를 약 40bit라고 한다면 총 1.6mb의 데이터가 된다.
이게 사진이 아닌 영상이라면 어떻게 될까 ?
영상을 초당 24프레임이라고 해도 걷잡을 수 없는 데이터 용량을 가지게 될 것이다.
이런 거대한 데이터를 있는 그대로 스트림을 통해 보내면 비용이 막대하게 들 것이다.
이를 효율적으로 보내기 위해서 인코딩이란것을 이용하게 된다.
통신을 하면 끝나지 않는 데이터의 흐름인 스트림을 세그먼트( 페이로드 ) 단위로 끊어 여러 헤더들을 붙여 보내고 다시 조립해서 읽게 되는데 이때 네트워크의 표현계층에서 인코딩을 하게 된다.
base64 인코딩을 이용하면 6bit 단위로 세그먼트를 만들어 문자열로 바꿔서 보낸다.
binary에서 문자열로 변환된 데이터는 json이나 x-www~ 타입으로 서버로 가서 테이블에 저장된다.
브라우저가 데이터를 요청하면 서버는 base64로 인코딩된 문자열을 주게 되는데 브라우저는 base64를 디코딩할 수 있기 때문에 문자열을 받아서 화면에 렌더링을 한다.
이 방법을 사용하면 매번 브라우저에게 이미지를 보내지 않아도 서버의 주소만 주면 알아서 읽는다
브라우저가 데이터를 또 다른 방법으로는 json에 파일을 넣어버리고 content-typeE을 image/jpg로 넣어서 보내면 알아서 읽게된다.
base64 인코딩을 사용하는 이유가 뭘까?
메모리는 데이터를 8bit 단위로 끊어 읽는데 8bit중에서 6bit만 채우는 base64는 오히려 데이터의 용량이 늘어나는 단점이 있다.
그럼에도 사용하는 이유는 기존에 사용하던 7bit 부호체계인 ASCII(아스키코드)의 단점 때문이다.
아스키코드는 7bit이므로 나머지 1bit를 처리하는 방법이 시스템마다 다르므로 다른 결과가 나오게 된다.
base64 는 이러한 문제점이 없기 때문에 데이터 전달에 안전하다. 그러므로 시스템과 상관없이 동일한 전송, 결과가 나온다.
사진을 넣어보자
게시글에 사진을 넣어 보내면 base64 인코딩으로 변환되어 String 문자열로 바뀐다.
들어간 사진의 데이터를 h2-console 으로 확인을 하면
<img src="data:image/jpeg;base64,엄청나게 긴 문자열...">
의 형태로 테이블에 들어가게 된다
글 상세보기를 하면 테이블 데이터는 모델이 뷰로 전달하고 서버는 SSR을 하여 브라우저에 html을 응답하는데 응답을 받는 브라우저 엔진은 data:image/jpeg;base64
을 읽고 base64 방식으로 디코딩 하여 사진을 화면에 렌더링한다.
글을 조회할때는 content
와 content에 포함된 이미지소스를 렌더링해서 화면에 뿌리면 되지만 글 목록을 볼때는 title과 썸내일만 필요하므로 content에서 이미지 소스를 추출할 방법이 필요하다.
이때 content
에서 특정한 데이터를 추출하는 방법으로 Jsoup라이브러리를 이용해보자.
Jsoup은 자바 HTML 파서로써 html을 파싱하거나, 데이터를 추출하거나, 문서내의 요소를 조작할 수 있게 해준다.
Jsoup 의 기능은 다양하지만 간단하게 썸네일만 추출하는 코드만 알아보자.
implementation 'org.jsoup:jsoup:1.15.3'
글 내용에서 이미지 소스를 추출해보자
테스트코드를 만들어서 테스트를 시작해보자
@Test
public void jsoup_test2() {
String html = "<p>123123<img src=\"data:image/jpeg;base64,/9j/4AAQSkZJRgAB\"><img src=\"data:image/jpeg;base64,/9j/34fghZJRgAB\"><img src=\"data:image/jpeg;base64,/9j/4shxcvhJRgAB\"></p>";
String img = "";
Document doc = Jsoup.parse(html);
Elements els = doc.select("img");
if (els.size() == 0){
// 임시 사진 제공
}else{
Element el = els.get(0);
img += el.attr("src");
System.out.println(img);
}
}
String html
을 content의 데이터라고 하자 ( 추출되는지 테스트 )Jsoup.parse()
는 html 에서 Document
를 추출해준다.doc.select("img")
는 도큐먼트에서 이미지 태그를 사용한 Elements
배열을 추출 해준다.els.get(0)
의 요소를 꺼낸뒤 el.attr("src")
로 이미지태그의 src
속성을 추출한다.@Test
를 이용하여 테스트를 한뒤 ""
안의 데이터가 추출되면 본 코드에 가져간다.Document 결과
Elements 결과
Element 결과
마지막 추출 결과
이제 추출한 소스( base64로 인코딩된 문자열 )를 브라우저에 응답하면 썸네일이 화면에 나오게 된다.
이 코드는 어디에 써야 할까 ?
많은 사람들이 조회를 하는 경우 글 목록의 썸네일을 매번 추출해서 전달해주면 이 코드를 매번 반복해 쓸데 없는 부하가 생긴다.
보통 글은 insert보다 select가 많이 되므로 작성할때 썸네일을 추출해서 저장하면 조회할때는 간단히 썸네일 소스를 읽으면 된다.
그러기 위해선 board_tb에 thumbnail
컬럼을 생성하고 insert시에 썸네일을 추출해서 테이블에 넣으면 된다.
그러므로 기존 코드에 약간의 수정이 필요하다.
create table board_tb (
id int auto_increment primary key,
title varchar not null,
content longtext not null,
thumbnail longtext not null,
user_id int not null,
created_at timestamp not null
);
<insert id="insertBoard">
insert into board_tb ( title, content, user_id, thumbnail, created_at)
values ( #{title}, #{content}, #{userId}, #{thumbnail}, now())
</insert>
<update id="updateBoard">
update board_tb set title=#{title}, content=#{content}, thumbnail=#{thumbnail} where id=#{id}
</update>
@Getter
@Setter
public class Board {
private int id;
private String title;
private String content;
private String thumbnail;
private int userId;
private Timestamp createdAt;
}
... 등 기타 썸네일이 필요한곳에 추가해주면 된다.
public class Thumbnail {
public static String 썸네일추출(String object){
String img = "";
Document doc = Jsoup.parse(object);
Elements els = doc.select("img");
if (els.size() == 0){
return "/images/dora1.png";
}else{
Element el = els.get(0);
img += el.attr("src");
return img;
}
}
}
이제 insert/update시에 썸네일추출 메소드를 호출하면 테이블에 썸네일 소스가 들어가게 된다.
@Transactional
public void 글쓰기(BoardSaveReqDto boardSaveReqDto, int userId){
String thumbnail = Thumbnail.썸네일추출(boardSaveReqDto.getContent());
int result = boardRepository.insertBoard(
boardSaveReqDto.getTitle(),
boardSaveReqDto.getContent(),
thumbnail,
userId);
if ( result != 1 ){
throw new CustomException("글 쓰기에 실패했습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
이제 사진을 추가하고 수정한 결과는
썸네일이 제대로 들어온 화면
썸네일이 수정된 화면
간단히 사진을 추출해서 썸네일에 넣는 방법을 알아봤다.
보다 많은 Jsoup을 공부하려면 https://jsoup.org/ 을 참조하면 된다.