썸머노트에서 이미지 업로드 할때 흔히 발견되는 nodejs 에러들

썸머노트에서 이미지 업로드하기

썸머노트에서 이미지를 서버에 올리려면 일단 아래와 같이 별도의 onImageUpload 핸들러를 구현해야한다.

var $editor = $('#editor');
var editor = $editor.summernote({
 ... 옵션 생략 ... 
 onImageUpload: function(files) {
    sendFile(files[0]);
  }
});

위에서 사용한 sendFile 함수는 아래와 같이 구현한다. 이때 ajax 옵션에 주의하자. 별거아닌 옵션을 실수로 잘못주면 하루종일 이유도 모르고 헤맬수있다. 특히 contentType은 없어야한다. 파일 업로드라고 종종 contentType을 “multipart/form-data”로 설정하는 경우가 있는데, 본래 XMLHttpRequest 객체는 파일 업로드를 지원하지 않기 때문에 파일 업로드를 위해서는 XHR2와 FormData 객체를 이용해야한다. 참고로 XHR2는 거의 모든 최신 브라우저에서 사용할수있다.

function sendFile(file) {
  var data = new FormData();
  data.append("file", file);

  $.ajax({
    type: "POST",
    url: "/api/upload/image",
    type: 'POST',
    data: data,
    contentType: false,
    processData: false, // Don't process the files
    success: function(data) {
      $('#editor').summernote("insertImage", data.url);
    }
  });
}

간혹 no multipart boundary was found 라는 오류가 발생하면 바로 컨텐츠 타입을 의심해보면 된다.

업로드 서버 구현하기

이제 파일을 업로드를 해줄 서버를 구현해보자. 노드로 구현할때 나는 주로 쓰는 multer라는 모듈을 사용한다. 사용법 또한 무지 간단하다.

var router = express.Router();
var multer = require('multer');
var upload = multer({ 
  dest: 'uploads/'
});
router.post('/upload/image', upload.single('file'), function(req, res){
  console.log(req.file);
});

하지만 간단하다고 생각할때 아래와 같은 에러를 만나게 된다.

위 두 에러는 ajax로 파일을 업로드할때 파일내용을 request body에 써버 보내게 되는데 바로 그 바디의 크기 제한에 걸려나 나는 문제다. 이럴땐 당황하지 말고 아래와 같이 설정한다.

app.use(bodyParser.urlencoded({limit: '5mb', extended: false, parameterLimit: 10000}));

물론 node 앞딴에 Nginx 같은 스태틱 서버를 두는 경우엔 nginx 설정에도 업로드 사이즈를 설정해야한다.
관련글은 검색하면 쉽게 찾을수있다.

http {
#...
    client_max_body_size 100m;
#...
}

nodejs winston 모듈을 이용해 로컬타임 출력하기

노드에서 많이 쓰이는 Logger 모듈 3가지

일단 morgan 같은 경우엔 Express4에서 정식 로거 모듈로 사용되고 있고, winston과 bunyan 모듈은 둘다 나름의 특징이 있어서 뭐가 더 좋다라고 할수는 없다. 그냥 취향에 맞는 녀석을 선택해서 쓰면 될것 같다. 둘을 비교한 내용은 이미 검색하면 많이 나오니까 요 포스트로 대신한다.

winston 모듈을 이용해 자동으로 타임로그 찍기

사실 해당 문서를 잘 읽어보면 아래와 같은 단락이 나오는데 예제가 없다보니 어떻게하라는거지? 라고 생각할수도 있다.

timestamp: Boolean flag indicating if we should prepend output with timestamps (default false).
If function is specified, its return value will be used instead of timestamps.

검색해보니 요렇게 써보라는 예제를 찾았다.

var winston = require('winston');
var logger = new (winston.Logger)({ 
    transports: [ new (winston.transports.Console)({ timestamp: true}) ]
});

오예!! 간단하구만…. 이렇게 생각했는데 출력된 로그를 보니 아래와 같이 기준시간이

2014-10-13T02:00:00.231z - info: 어쩌구 저쩌구....

그래서 좀더 검색을 해봤더니 commom.js 모듈을 직접 수정하라는 글도 찾았다.
http://blog.whitelife.co.kr/154

하지만 결론은 그냥 요렇게 하면된다.

var winston = require('winston');
var moment = require('moment');

var logger = new (winston.Logger)({
    transports: [
      new (winston.transports.Console)({
        timestamp: function(){
          return moment().format("YYYY-MM-DD HH:mm:ss.SSS");
        }
      })
    ]
});

오늘 얻은 교훈

사실 문서에도 함수를 지정할수있게 써 있는데 예제가 없으니 대충보고 없네..하고 넘어가는 습관 -_-;;;; 영어는 꼼꼼히 읽자!

생각대로 되는 nodejs, 또 잊어먹기 전에 메모!

지난달부터 재미삼에 패치팡이란 애니팡 아류작을 하나 맹글어보고 있는데, 부득이(?)하게 랭킹서버까지 만들어야했다. 그리고 지난주였나? 페북과 연동해서 친구들과 경쟁구도를 만들기 위해 랭킹서버를 약간 수정했다. 물론 데이터 모델도 달라졌고, API도 약간의 수정이 있었다. 귀차니즘에 수정을 최소화하다보니 많은 기능을 넣지는 않았음에도 불구하고 삽질의 연속이었다. 그때 정리했어야하는데 일단 잊어먹기 전에 생각나는대로 정리해보자.

1. 청크(Chank)데이터 이어붙이기 일단

페이스북 Graph API를 이용해 친구목록을 가져와야했다. 페이스북의 모든 API는 https로 호출한다. (혹시 http로 호출하는 API가 있을찌도 모르겠으나,.. 일단 문서에는 없는듯..) 뭐 https도 암호화와 패킷 검증을 제외하면 프로토콜은 http와 거의 대동소이하다.  여튼 nodejs를 이용해 API 호출후 응답을 받으려면 다음과 같이 ‘data’ 이벤트를 통해 데이터를 수신받는다.

var https = require('https');
var url = 'https://graph.facebook.com/me/friends?fields=id&access_token='+ token;
https.get(url, function(response) {
    response.on('data', function(chunk) {
        console.log('receive.. chunk data...', chunk);
        var friends = JSON.parse(buffer.join(''));
    });
});

HTTP와 HTTPS 객체에 대한 보다 자세한 설명은 nodejs API 문서 를 참고하자. 위와 같이 코드를 작성하고 실행해보면 간간히 JSON.parse 중에 에러가 난다. 아! 왜 자꾸 에러나는거야? 짱내고 있었는데.. 생각해보니 data 이벤트로 받는 녀석은 chunk 단위다! 그럼 당연히 데이터가 짤릴수도 있는거지! 아하~~ 😀 물론 한번에 담겨서 넘어올때도 있지만ㅋㅋㅋ 여튼 콘솔에 값을 출력해보면,.. [Buffer 0x45…..] 버퍼 어쩌구저쩌구가 찍힌다 그래서 버퍼에 담았다가 이어붙이면 되겠다 싶었다. 그런데!!! 어떻게하지? 어떻게 붙이지? Buffer 객체를 만들어야하나? Stream 객체로 해야하나? 서버개발에 익숙치 않으니 Java에서 쓰던 방법들을 동원해서 별에별 방법을 다 써봣다. 너무 Java틱했나? 결론은 다 잘 안됐다. OTL… 도대체 왜 안되는거야? 라고 씩씩대면서 4시간을 날렸다. 그런데 해결책이 아~ 허무해~~! 너무나 단순했다. 그냥 배열에 담았다가 join만 하면 되는거였어!! 정말 javascirpt 다운 해결책!~ ㅜㅜ 아~ 내 아까운 시간~ 그래도 뭐 4시간동안 API문서보고 이거저거 해봤다는거에 위안을 삼으며 다음과 같이 수정!

var https = require('https');
var url = 'https://graph.facebook.com/me/friends?fields=id&access_token='+ token;
https.get(url, function(response) {
    var buffer = [];
    response.on('data', function(chunk) {
        console.log('receive.. chunk data...', chunk);
        buffer.push(chunk);
    });
    response.on('end', function() {
        var friends = JSON.parse(buffer.join(''));
    });
});

위와 같이 수정하면 데이터 짤림 없이 온전한 데이터를 파싱하는데 성공!!

2. Express 모듈에 미들웨어 장착하기

이번에도 다소간의 삽질이 있었지만, 이번엔 지극히 자바스크립트 다운 생각으로 임했더니 매우 쉽게 해결된 문제다. 먼저 nodejs 로 서버를 만들때 가장 많이 쓰는 모듈이 바로 Express 모듈이다. 이젠 거의 socket.io와 더불이 이바닥 표준이 아닌가 싶다. 여튼 GET 방식이든 POST 방식이든 요청 URL에 한글이 들어갈 경우 반드시 인코딩해서 넘겨야하고 서버에선 디코딩 과정이 필요하다.  아니면 서버에서 무조건 URL을 자동 인코딩해서 처리해야한다. 그런데 난 이 모든 것들을 Express가 알아서 해주는줄만 알았다. 그런데 그게 아니었다. ㅜㅜ 너무 맹신했나? 그래서 데이터를 넘길때 서버에서 인코딩하고 다시 응답을 만들때 디코딩하는 생쑈를 했다. 그런데 말이지.. 자꾸 이상한 생각이 든다,.. 이거 너무 귀찮은데,…-_-;;.. 왜 이걸 Express에서 안해주는거지?… API를 뒤졌다.. 역시!!.. 저 밑에 있네.. 바로

Middleware 라는 항목에.. 빙고~!!

app.use(express.bodyParser());

// is equivalent to:
app.use(express.json());
app.use(express.urlencoded());
app.use(express.multipart());

바디파서 한줄이면 인코딩은 알아서 척척척 해준다. -_-;… 역시 자바스크립트는 생각대로 하면된다. 느무 조아요!! 일단 여기까지 또 생각나면… 이어서..