이미지 업로드 프로토콜

이미지를 업로드하는 방법은 두가지이다.

첫번째는 file upload 버튼을 사용해서 올리게 되는 multi-part 프로토콜 방식이다. 이 때는 브라우저가 해당 프로토콜에 맞게 올려주게 된다. 파일을 업로드하는 메서드는 Post 이다. 그런데  Content-Type이 좀 독특하다.

Request할때 헤더를 fiddler를 사용해서 살펴보면, 아래와 같다.

Content-Type: multipart/form-data; boundary=xxxxxxxxxxxxxxxxxxx

body에 보내는 데이터의 content type이 form-data이긴 한데 여러부분으로 나누어 보낸다는 multipart 형태이다.  그리고 각각의 part 사이는 boundary를 이용해서 구분하는데, 앞에 – -를 붙인다. 그리고 맨 마지막에 끝날때는 다시 – -를 붙인다. 예를 들어 3 part를 전송한다고 가정하면 아래와 같이 여래개의 part로 나누어서 보내게 된다.

– -xxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data: name=”value”; filename=”aaa.jpg”
Content-Type: image/jpeg
oooooooooooooooooooooooooooooooooooooooooooooooooooooooo

– -xxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data: name=”value”; filename=”aaa.jpg”
Content-Type: image/jpeg
oooooooooooooooooooooooooooooooooooooooooooooooooooooooo

– -xxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data: name=”value”; filename=”aaa.jpg”
Content-Type: image/jpeg
oooooooooooooooooooooooooooooooooooooooooooooooooooooooo

– -xxxxxxxxxxxxxxxxxxx- –

multi-part 프토토콜을 실페 fiddler로 패킷캡쳐 해보면 다음과 같다.

두번째는 json 방식으로 보내는 방식이다. 이미지는 binary 이므로 json 으로 보내기 위해서는 string 으로 변환해야 한다. RFC2397 data URL scheme 표준 프로토콜을 참고하면 이미지 binary 데이터를 base64로 인코딩해서 보낸다. 이것을 참고해서 변환하면 스트링이 되므로 json으로 서버에 보낼수 있다.

여기서는 첫번재 방법인 file upload 버튼으로 이미지를 업로드시 multi-part 방법으로 구현을 해보겠다.

구현하는 API는 다음과 같다.

  • /api/image/upload – multi-part 방식으로 이미지를 업로드하고 DB에 데이터를 저장하는 API
  • /api/image/view/:id – id에 해당하는 이미지 보기 API. xxx.png 를 보기했을때랑 동일하게 동작되도록 한다.

이미지 업로드 및 보기 API 설계

페이지를 리턴하는 페이징 방식에서는 업로드시 한꺼번에 올리고 처리했지만 SPA 에서는 SRP 원칙으로 작게 쪼개는게 확장성에서 유리하다. 이미지 업로드하는 별도의 테이블을 설계를 하고 REST api도 별도로 구축하는것이 확장성이 있다.

서버에서 이미지 binary 데이터를 받은후에 이것을 물리적인 화일로 처리할 지 아니면 DB에 넣어서 처리할 지가 설계를 할 때 맨 처음 고민해야하는 부분이다. file로 처리할 경우는 multi server 일 경우에 처리가 곤란하기 때무에 AWS S3를 사용하는게 가장 방법이다. 이것보다 더 좋은 방법은 DB로 처리하는 방법이다. 여기서도 DB에 저장하고 꺼내오는 방법을 살펴보겠다.

entity 폴더에 Image.ts 화일을 생성한다.

data 필드는 type을 longblob으로 설정한다. 이미지 binary 데이터를 저장할 것이므로 blob 타입이여야 하는데 blob은 용량이 적어므로 longblob 타입으로 저장한다.

multi part 이미지 업로드 API 구현

controller 패키지에 ImageController.ts 컨트롤러를 생성하고 프로토타입을 구현한다.

Node에서 파일 업로드를 처리하기 위해서 multer라는 라이브러리가 필요하다. 이것의 역할은 body에서 올라오는 multi-part 이미지 바이너리 데이터를 모아서 req 에 file이라는 속성으로 넘겨준다. 또한 바이너리를 파일로 처리할지 아니면 메모리로 처리할지 결정하는 옵션도 제공해준다.

multer를 먼저 설치한다.

router 폴더에 image.ts 폴더를 만들고 위에서 언급한 패스를 매핑한다.

  • storage: multer.memoryStorage() – 바이너리를 메모리로 처리하겠다는 옵션이다. 물리적인 파일로 저장하는 것이 아니라 DB에 저장할 것이기 때문에 파일로 저장할 필요가 없으므로 메모리로 처리한다.
  • single(‘file’) – single 은 바이너리 화일을 하나만 처리하겠다는 옵션이다. file은 클라이언트에서 fileupload 키에 해당한다. 여기서도 SRP 원칙에 따른것이다. 이미지를 여러장 올리는 API를 한번 사용하나 이미지를 한 장 올리는 API를 여러번 호출하는 것이나 동일하지만 이미지 한 장을 여러번 올리는 API 가 더 확장성이 있다.

router 폴더의 index.ts에 서브라우팅을 추가한다.

이제 controller 부분을 구현한다.

postman으로 테스트해본다.

http://localhost:8080/api/image/upload을 입력하고,

body 부분에서 form-data를 선택하고  key 부분에는 file을 입력하고 파일선택 부분에 이미지하나를 선택한다.

정상적으로 업로드가 완료되면 입력된 id가 보여지게 된다.

이미지 보기 api 구현

브라우저에서 http://xxx.xxx.xx.xxx/a.png 를 하게 되면 브라우저에 이미지가 보이게 된다. 혹은 html 태그에서 <img src=”a.png” /> 하게 되면 이미지게 보이게 된다. 이것과 동일하게 이미지 보기 api를 구현한다.

controller 에 viewImage 메서드를 구현한다.

aaa.png를 브라우저 보기 후 fiddler로 패킷캡쳐해서 보면 response 헤더에 Content-type 에 image/png, Content-Length: 에 바이너리 데이터 길이가 들어가고 body에 바이너리 데이터가 들어간다. 그래서 response 헤더와 두개의 헤더를 추가하였고 body에 바이너리 데이터를 넣었다.

router 모듈에 매핑을 추가한다.

postman으로 테스트해보면 다음과 같다.