본문 바로가기

개발/회고

[SS회고] Node.js와 Express (ft. multipart/form-data)

워우!

 

벌써 5번째 회고다.. ㄷㄷ

 

== 내마음..

 

예전에 프로젝트를 해볼까 한게 있었는데,

그때 친구가 node.js랑 express로 서버를 만드는걸 봤다.

 

그래서 이 둘을 사용해서 서버를 만들 수 있다는건 알고 있었다.

 

또한 웹서버로 많이 사용되는 조합이기 때문에

서버 프레임워크는 빛의 속도로! 채택했다.

 

학교 다닐때 채팅 만들기 이런거 말고는?

서버 구축은 처음으로 해보는(진짜??) 일이었다.

 

그래서 앞 길이 구만리처럼...

눈 앞이 깜깜했따...

 

사용한 서버 프레임워크

Node.js

Node.js는 대체 무엇인고...??

설명을 잘해준 멋찐 블로그가 많다!

 

[Node.JS] 강좌 01편: 소개 | VELOPERT.LOG

Node.js 가 뭐지? NodeJS 는 구글 크롬의 자바스크립트 엔진 (V8 Engine) 에 기반해 만들어진 서버 사이드 플랫폼입니다. 2009년에 Ryan Dahl에 의해 개발되었으며 현시점 (2016-02-07) 최신 버전은 v5.5.0 입니다

velopert.com

 

간단히 정리해보면,

Node.js(노드)는 브라우저 같은 존재이다..(?)

 

자바스크립트는 언어다..

 

자바를 동작시키기 위해서는 자바 런타임(JRE)이 필요한 것처럼

자바스크립트도 마찬가지인데.

 

그 런타임 환경을 제공하는것이 노드와 브라우저이다.

 

따라서 노드는 브라우저 없이는 실업자(?) 신세인 자바스크립트가

활발하게 일할 수 있는 환경을 만들어주었다.

 

노드가 고안된 이유는

웹서버와 같은 확장성있는 네트워크 프로그램을 제작하기 위해서라고 한다.

 

그러다 보니 자바스크립트로 서버를 만들어 작동시키기 위해서는

필연적으로 노드가 요구된다.

 

노드 안에서 프레임워크 도움없이

열쒸미 서버를 만들 수도 있겠지만.....??

 

 

 

 

가져다 쓰고 싶다...🥺

 

브라우저에서는 모든 파일이 url로 관리되는 반면..

노드에서는 파일시스템을 쓸 수 있다!

 

fs라는 모듈을 dependency로 추가하면

파일시스템에 접근해서 파일 입출력이 가능하다.

 

노드에서는 import와 export 대신

require과 module.exports로 모듈을 가져다 쓰고 내보낼 수 있다.

 

그 외에 또 어떤 기능이 있는지는 이 글을 참고하면 좋다!

잘 정리가 되어있어서 초반에 공부할때 많이 도움이 되었다👏

 

[Node.JS] 노드는 무엇이고 어떠한 기능들이 있는가? — (1)

안녕하세요. 캡틴체인입니다!

medium.com

 

 

딴 이야기긴 한데...

 

웹 서버와 클라를 한 프로젝트에서 진행하는 경우

따로 디렉토리를 가져가는 편(package.json을 두 개로 나누는 편)이 낫다.

 

초반에 나는 하나의 package.json 안에

서버와 클라 디팬던시와 스크립트를 모두 관리했다.

 

그랬더니 무료 ec2에서 npm install 할 때 cpu 이용량이 리밋을 넘어

ec2가 멈춰버리더라...

 

클라가 아무래도 dependency가 많다보니 따로 빌드해서 업로드하고

서버는 ec2에서 실행하는 방식으로 변경을 했는데.

 

그러기 위해서는 디렉토리도 나누고 package.json도 나눠야 했다..

(이것이 클라에 쓰이던 물건인고.. 서버인고.. 대혼란..)

 

그런데 그러면 로컬에서 테스트하기가 어려워진다..

웹서버 빌드 따로, 서버 실행 따로 해야하니까..

 

그래서 나는 root에 package.json을 하나 더 만들고 script를 추가했다.

nodemon을 써야 했기 때문에 shell 스크립트보다는 이 편이 나았다.

 

{
  "name": "root",
  "version": "0.1.0",
  "description": "for only development usage",
  "private": true,
  "devDependencies": {
    "nodemon": "^2.0.7"
  },
  "scripts": {
    "setUp": "cd client && npm install && npm run devBuild && cd ../server && npm install",
    "launch": "nodemon --ignore client/build --exec 'cd ./client && npm run devBuild && cd ../server && npm run devServer'",
    "docker": "./docker_push.sh"
  }
}

 

프로젝트를 진행하면서 어떤 일이 일어날지 모르니..

 

처음부터 속편하게 나누자!!!

 

Express

Node.js와 Express 초심자를 위한 모질라 문서가 있다!

(그저 빛..🎇)

 

Express/Node 소개 - Web 개발 학습하기 | MDN

첫번째 Express 수업에서는 Node, Express를 알아보고, Express 웹 프레임워크 제작의 전반에 대해 배우게 됩니다. 우선 주요 특징들에 대한 틀을 정리한 후 Express 어플리케이션을 구성하는 주요 구성

developer.mozilla.org

 

문서에 따르면 왜 Express를 써야하는지 똑부러지게 설명해두었다!

 

Node 자체가 다른 일반적인 웹 개발 기능을 지원하지 않습니다. 만약 다른 HTTP 패턴 (예 : GET, POST, DELETE 등)에 대한 특정 처리를 추가하려면 서로 다른 URL 경로("routes")를 사용하여 요청을 개별적으로 처리, 정적 파일을 제공, 템플릿을 사용하여 동적으로 응답을 생성할 수 있으며, 코드를 직접 작성할 필요가가 생기게 됩니다. 또는 기본적인 것들을 직접 구현하는 작업을 피하고 웹 프레임 워크를 사용할 수 있습니다! 

 

 

Express는 노드 기반으로 웹 서버를 쉽게 만들 수 있도록

위에서 인용한 기능들을 제공한다.

 

노드에서 가장 인기 있는 웹 서버 프레임워크다보니

Express 기반으로 이런저런 기능을 지원하는 라이브러리도 많다.

 

생태계가 두터운 프레임워크를 써야

두 발 뻗고 잘 수 있다ㅎㅎ

 

난 React Router를 이용해서 클라이언트 사이드 렌더링을 하다보니

웹 서버에서 할 일이 많진 않았다.

 

1) 클라이언트 페이지를 던져줄때 (80%......)

2) 보안이 중요한 작업을 클라이언트 대신 해줄때

3) 외부 서버와 통신할때

 

훗. SPA 선택한건 나 자신

 

그!러!치!만!

 

그래도 어떻게 해야할지 잘 모르겠던 부분이 있었으니..

 

1) 클라에서 어떻게 음성파일을 보내는지.. (클라 이야기가 여기서 왜 나와??)

2) 서버에서 어떻게 그 음성파일을 받아 다시 외부 서버로 던져주는지

 

이 두 가지였따..

 

음성 파일을 서버로 보내는 방법

multipart/form-data

이때까지 ContentType이 application/json인 서버 API만 연동해봤기 때문에...

 

오디오 데이터 처리 다음으로 또 시간을 쏟았던 것이

클라에서 ML 서버로 어떻게 오디오 파일을 보내는지였다.

 

파일 또한 문자로 이루어져있기 때문에

body에 담은 파일 스펙을 contentType에 명시하여

지정한 타입에 맞게 인코딩해서 전송하게 된다.

 

하지만 우리는,

 

1) text를 하나 보내야 했다.

2) plus, 여러 개의 오디오 파일을 보내야 했다.

3) 오디오 파일 각각 번호를 가졌다.

 

그래서 ML 서버를 만드는 동기가

ContentType을 multipart/form-data로 정해주었다!

 

multipart/form-data는 key-value 형태로

각기 다른 ContentType을 가진 데이터도 여러 개 담을 수 있다.

 

key-value 데이터는 하나의 뭉탱이로 묶여 보내진다.

뭉탱이 하나씩은 boundary라는 문자열을 이용해 구분이 된다.

 

http request의 form data를 까보면 이렇게 생겼다!

------WebKitFormBoundaryRhMtwuGq8cLAmGuk Content-Disposition: form-data; name="id" {id}

------WebKitFormBoundaryRhMtwuGq8cLAmGuk Content-Disposition: form-data; name="file"; filename="{filename}" Content-Type: audio/wav
....

 

name에는 key가, 그 다음에는 value값이 들어간다.

 

여기서는 string과 audio/wav지만 그외에 다른 타입들도

동시에 한 메시지에 담을 수 있다!!!

 

브라우저에서는 html 없이도 쉽게 form data를 생성할 수 있도록

FormData API를 제공한다. 

 

export default class XxxAPI {
  constructor({id, wavs}) {
    this.body = new FormData();
    this.body.append('id', id);
    wavs.forEach((wav, index) => {
      this.body.append('file', wav, {SENTENCE_ID});
    });
  }
  // ...
}

 

그런데 유의할 점이 있다!

 

FormData API를 사용해서 메시지를 만든 경우에는..

'Content-Type': 'multipart/form-data'를 헤더에 넣게 되면

제대로 동작하지 않았다.

 

export default class XxxAPI {
  //...
  async getXxx() {
    return await fetcher({API_NAME}, {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        //'Content-Type': 'multipart/form-data' 넣지마시오!!
      },
      body: this.body,
    });
  }
}

 

그럴수 밖에 없는 것이..

 

메시지 뭉탱이를 parsing 하는데 사용되는 boundary 문자열은

다음처럼 ContentType 안에 들어간다.

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

 

FormData API를 사용하면 자동으로 헤더에 얠 넣어주는데,

우리가 다시 넣어주면 overwrite가 되어서..

 

request를 받는 측에서 boundary 문자열이 뭔지 몰라서..

파싱을 못하게 된다..!

 

이것 때문에 삽질을 했던 기억이..

아직까지 생생하다 ㅎ.ㅎ

 

새삼스레..

 

Multer

자 이제 클라 단에서 웹서버로 오디오 데이터를 잘 보냈으니!

웹 서버가 이걸 받아서 다시 ML 서버로 보내야한다..

 

후훗..

 

form-data는 req.body로는 못받는다.. (몰랐...)

 

post로 들어온 req를 찍어보면 body가 {}로 빈채로 오는 것을 볼 수 있다.

 

근데..

form-data가 어떤 형태로 req 안에 들어있는지는 확인하지 못했다.

(누군가 알려주었으면..😭)

 

뭐 어딘가에 들어있다 치자...

 

그걸 Content-Type에서 받은 boundary를 가져다가

메시지를 다 파싱하기엔... 솔찍히 귀찮다..

 

그래서 multer라는 라이브러리를 쓰면 된다!

 

multer는 form-data를 파싱해서

text 타입의 경우 req.body에 넣어주고

그외 타입의 경우 req.files(한 개이면 req.file)에 넣어준다!

 

multer README에서 업어온 예시 코드이다.

 

// uploded-file이라는 key의 file 한 개, nspeakers라는 key의 text 한 개를 가진 form-data
<form action="/stats" enctype="multipart/form-data" method="post">
  <div class="form-group">
    <input type="file" class="form-control-file" name="uploaded_file">
    <input type="text" class="form-control" placeholder="Number of speakers" name="nspeakers">
    <input type="submit" value="Get me the stats!" class="btn btn-default">            
  </div>
</form>

 

// multer로 file이 한 개만 들어있는 form-data 다루기
const multer  = require('multer')
// const upload = multer({ dest: './public/data/uploads/' }) 파싱말고도 파일 저장까지 하려면 사용하기
app.post('/stats', upload.single('uploaded_file'), function (req, res) {
   // req.file is the name of your file in the form above, here 'uploaded_file'
   // req.body will hold the text fields, if there were any 
   console.log(req.file, req.body)
});

 

여러 개의 file이 들어있을때는 upload.array나 upload.fields를 이용하면 된다!

 

form-data/form-data

잊지 말 것은 웹 서버에서는 브라우저가 아닌 노드에서 돌아간다는 점!!!

 

그래서 ML 서버에 다시 form-data로 만들어서 전달할때,

브라우저에서 제공하는 FormData API를 쓸 수가 없다.

 

대신 form-data/form-data라는 라이브러리를 쓰면 된닷^_^

 

사용법은 FormData API랑 동일하고!

Content-Type 또한 라이브러리에서 넣어주기 때문에 생략하자!