이 포스팅에선 postman을 이용하여 업로드를 진행할 것이다. 추후에 안드로이드 어플을 통해서 업로드 Client 부분을 작성할 예정이며 작성하면 이 글을 수정하여 링크를 달아놓을 예정이다.
우선 파일을 업로드하여 서버 upload라는 폴더 안에 저장할 것이고, DB에 파일에 대한 정보를 넣을 생각이다. 이 이유는 맨 아래에 남겨놨다.
우선 NPM 에서 사용하는 기본 예제다.
var express = require('express')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
var app = express()
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file is the `avatar` file
// req.body will hold the text fields, if there were any
})
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files is array of `photos` files
// req.body will contain the text fields, if there were any
})
var cpUpload = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', cpUpload, function (req, res, next) {
// req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
//
// e.g.
// req.files['avatar'][0] -> File
// req.files['gallery'] -> Array
//
// req.body will contain the text fields, if there were any
})
코드에서 보면 알 수 있듯이 upload할 때 속성이 다르다.
.single(fieldname)
이름을 가진 단일 파일을 허용합니다fieldname.단일 파일은req.file. 로 사용하면 된다.
.array(fieldname[, maxCount])
이름이 모두 인 파일 배열을 허용합니다fieldname.둘 이상의maxCount파일이 업로드되면 선택적으로 오류가 발생합니다.파일 배열은req.files. 로 사용하면 된다.
.fields(fields)
fields 파라미터로 넘어오는 것들을 배열 형태로 저장된다. 파일은 req.files로 사용하면 된다.
var storage = multer.memoryStorage()
var upload = multer({ storage: storage })
파일 정보를 DB에 저장해둬야 할 필요가 있다. 왜냐면 파일을 다운 받을 경우 자신이 업로드한 파일을 정확하게 받을 수 있다. 그리고 파일 이름은 한글로 넣을 경우 깨지는 경우가 발생했다. UTF-8 등 ... 인코딩 많은걸 해봤지만 안됐다. 나중에 듣기론 이런 경우가 많아 파일 오리지널 이름을 같이 업로드하여 DB에 넣어둔 뒤 다운로드 할 시 DB에서 파일 이름을 꺼내와 다시 붙여주는 걸로 알고 있다. 물론 파일 리스트 조회 할 시도 DB에서 파일 오리지널 이름을 가져와 Client로 넘겨준다.
지금까지C나 JAVA에서는 include나 import를 하면 그 클래스나 함수, 변수들을 사용할 수 있었다. Node에선 require를 해도 그 변수나 함수를 사용하지 못했다.
Node에서는 모듈에서 exports를 하지 않으면 그 변수나 함수를 사용하지 못하는 것이다.
구글링으로 알아본 결과 A라는 모듈을 require를 하면 해당 모듈의 module.exports를 return 한다고 보면 되는 것이다. 그러므로 A모듈에서 module.exports를 하지 않았다면 그 어떤 것도 참조할 수 가 없다.
아무튼 본론으로 넘어와서 module.exports를 사용함에 많은 방법이 있었다.
우선 module.exports와 exports가 있다. 이 둘의 차이점... 사실 없었다. 공식 문서를 보면 그저 module.exports의 더 짧은 버전... 즉 module 조차도 길었다고 생각했나보다... 아무튼 둘의 차이점은 성능이 아닌 그저 더 짧다는 것에 있다.
그렇다면 어떻게 사용하면 되는건가... 여기부터는 솔직히 쓰는 사람의 특성과 그 모듈의 특성에 따라 나뉘는 듯 하다.
아래 예를 들어 코드를 작성해봤다. 기호에 맞게 사용하면 될듯하다.
exports.func = function (){console.log('func')}
exports = {func:func} // 이 경우 따로 func 함수가 정의가 되어 있어야함.
exports = { func : function (){ console.log('func') } }
exports.a = b
처음 Node.js 개발을 하다가 갑자기 함수의 실행 순서가 이상했다. 난 당연히 내가 뭔가를 잘못 짰다는 생각만 하고 에러를 찾아봤다. 그렇지만 10분 뒤 내게는 이해할 수 없는 일이 일어났다. 잘못 짠게 하나도 없었다. 지금까지 계속 순차적인 함수 실행 방식만을 생각하고 코딩을 했기 때문이다. 지금까진 계속 안드로이드 자바 위주로 코딩을 하다보니 비동기는 생각을 하지 못했다. 우연하게 콘솔을 찍어보고 실행 순서가 이상하다는 걸 발견하고 Node.js는 비동기로 처리를 하는지 찾아봤다.
Syncronous동기 - 요청을 보낸 후 해당 요청의 응답을 받아야 다음 동작을 실행하는 방식
Asynchronous비동기 - 요청을 보낸 후 응답과 관계없이 다음 동작을 실행하는 방식
그러니깐 비동기는 요청을 보내면 서버에서 오래 걸리면 먼저 응답을 하고 그 후에 처리를 하고 있었다. DB에서 데이터를 받아오는 등의 작업을 해야한다면 우선적으로 그에 대한 응답을 해버리고 다른 작업들도 실행한다.
가끔 동기식 처리를 해야할 경우가 생긴다. 그에 대한 해결 방안도 있다.
Node.js 에서 많이 찾아봤다. 보통 웹에선 callback을 사용했을 것이다. 물론 난 자바스크립트를 모르기 때문에 callback의 개념을 아예 몰랐다. 나중에 callback을 썼는데 문장이 계속 길어지며 한 눈에 보기도 불편했다.
솔직히 Promise가 callback보단 낫지만 그렇다고 가독성 또한 좋은건 아니다. 그리고 또 다른 문제들도 있었다. return 뒤에 생성자를 통해서 인스턴스화를 하였는데 결국은 return 과는 조금 다른 성질이다. 보통의 return은 함수 종료가 되서 아래의 내용이 있더라도 실행하지 않는데 resolve가 있더라도 아래 내용을 실행한다. 이 부분은 코드 개발을 하다보니 발견하였다. 난 return의 개념을 적용되는 거라고 생각했지만 그게 아니었다. 물론 비동기이기 때문일수도 있다고 생각한다. 위에 코드에서 IF문 안에 조건문을 통해서 else로 내려와 resolve에 오면 함수가 종료되지 않고 함수가 끝까지 실행된다. 이 부분에 대해서 내부 구조를 다시 살펴보고 수정하겠다.
물론 이 방법만 있는건 아니다 그보다 더 좋은 문법이 있긴하다. node.js 8 LTS 버전에서 정식 지원하는 문법인 async와 await이다. async와 await의 최고의 장점은 node.js의 장점인 비동기를 유지하면서 동기적인 모습의 코드 스타일을 적용할 수 있고, 실행시 코드를 비동기적으로 동작하면서 코드를 동기적으로 실행한다. 사용방법 또한 어렵지도 길지도 않다.
사용법은 function 앞에 async 붙여주고 그 함수 안에서 실행하려는 함수 앞에 await를 붙여 사용한다. 시스템에서 코드를 실행하다가 await 함수 실행을 만나면 바로 실행된다. await는 async가 붙은 함수 안에서만 작동한다.
async function foo(data) {
var result1 = await fooPromise(data);
var result2 = await fooPromise(result1);
var result3 = await fooPromise(result2);
}
물론 async와 await가 만능은 아니었다. async function은 결국엔 promise를 반환하게 된다. 그렇기 때문에 위에 Promise와 연결 된다. 그리고 다중 함수 구조에서는 async와 await가 제대로 작동하지 않는다. 그렇기 때문에 어느순간부터는 비동기화 되어 함수 순서가 바뀔 수 있다. 나도 정확한 이유를 파악하지 못했다. 그렇기 때문에 foo 함수 안에서 fooPromise를 호출하고 fooPromise 함수 안에서 다른 함수 A를 호출하게 된다면 async/await가 깨지는 걸 발견했다. 그래서 나는 아래의 코드처럼 다른 함수 A 안에서는 return new Promise를 사용하여 처리한다. 그렇게 되면 제대로 순서대로 작동하는 걸 발견했다.
async function foo(data) {
var result1 = await fooPromise(data);
var result2 = await fooPromise(result1);
var result3 = await fooPromise(result2);
}
async function fooPromise(data){
var result1 = await A();
}
async function A(){
return new Promise((resolve, reject)=>{
...
})
}
나처럼 자바나 C 계열을 자주 했다면 return을 자주하는 형식이 편했기 때문에 자주 화가 날 것이다...
그리고 이거는 아마 다른 모듈을 많이 사용하게 되면 발견하게 될 것인데...
단편적으로 지금 당장 생각나는건 MySQL 쿼리시 콜백에서 다른 함수를 호출해야할 경우... 아래처럼 사용하면 된다
MQTT 통신 때문에 생긴 문제였다. MQTT는 message 통신 즉 문자열을 넘기기 때문에 여러개의 데이터를 보낼 수가 없다. 그렇기 때문에 데이터를 구분하려고 문자열에 특수문자로 구분하였다.
그런데 HASH화를 한 데이터는 구분하려고 넣은 특수문자가 포함되는 일이 생겼다. 우선 데이터를 구분하여 배열로 넣었다. 구분 특수문자로는 '/' 를 사용하였다. 의문은 HASH 알고리즘에서 다른 특수문자는 안생기나? 라는 의문이다. 그런데 한편으론 뭐가 됐던 만들어 놓으면 어떤 문자든 상관이 없겠다는 생각이 들어서 만들었다.
client.on('message', async function (topic, message) {
var _message = message.toString().split('/');
그 후 HASH 데이터는 배열의 끝 번째에 배치를 했기 때문에 특수문자가 없다면 4번째에 배치된다고 가정하면 4번째부터 배열의 끝까지 합치는 개념이었다. 그래서 급하게 N번째부터 끝까지 합치는 함수를 만들었다.
함수의 파라미터는 해당 배열과 HASH 데이터의 배열의 위치를 보내주면 된다. 위에서 말했던 것처럼 HASH 데이터가 배열의 4번째의 위치해있다면 4를 넘겨주면된다.
async function _combineData(data, num) {
var _data = data[num]
for (var i = num + 1; i <= data.length - 1; i++) {
_data += '/' + data[i]
}
return _data
}
우선 구분할 때 split 함수를 사용했기 때문에 특수문자가 빠진다. 그렇기 때문에 합칠 경우 특수문자가 없으므로 주의해야한다. 그렇기 때문에 데이터를 합칠 때에는 특수문자를 같이 넣어줘야한다. 그리고 끝까지가 아니라 N번째에서 M번째까지 합치려고 하면 for문 조건식에 i<= M 으로 변경해주면 된다.
구독을 했다면 메시지가 왔을 때 message 이벤트로 리스너를 만들어 처리한다. 보통 문자열이 전송되기 때문에 구분 문자(/, \...)를 두어 구분하여 사용하면 된다. 구분 문자를 사용하여 여러개의 데이터를 문자열로 전송할 때 주의할 점은 해당 메시지 안에 구분 문자를 사용하면 안된다. 예를 들어 해당 데이터가 암호화가 되면 특수문자도 포함된다. 그렇기 때문에 여러개로 인식되니 주의 하자.
client.on('message', async function (topic, message) {
var _message = message.toString().split('/');
})
그렇지만 Router 부분이 다르다. HTTP Express에선 아래처럼 router 에서 method를 설정하며 경로를 설정할 수 있지만 CoAP은 조금 다르다.
CoAP은 createServer()를 한 후 coap.on을 하면 서버의 수신부가 된다. 이후부터는 HTTP랑 동일하며 method 및 다른 설정을 할 수가 없었다. 나도 왠만한 방법을 다 해봤지만 궁금해지기 시작했다. 그래서 현재는 req.payload를 사용하여 데이터를 주고받고 있다. 현재 상태에서 해볼만 한건 다 해본것 같다. 혹시 Node.js 에서 CoAP 프로토콜을 잘 사용하시는 분 댓글로 남겨주세요...