node js에서 stream pipe 사용시 에러 처리 방법

web/node.js|2018. 11. 9. 22:28

node js에서 데이터를 stream을 사용하여 처리하고 pipe를 사용해서 계속해서 stream을 가지고 작업을 이어나갈 수 있다. 그런데 pipe를 통해서 작업을 진행하다 보니까 중간에 오류가 발생했을 때 try / catch 로는 정상적으로 처리하지 못하는 경우가 발생했다. 

나에 경우에는 에러가 발생했을 때 try / catch에서 잡히지 않아서 프로그램이 Unhandled Promise Rejections를 출력 하며 죽어버렸다.

그 예는 다음과 같이 request를 통해서 받은 이미지를 sharp 라이브러리를 통해서 이미지 크기를 변경하려고 할 때 발생했다.

1
try {
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').pipe(transformer).pipe(writeStream);
} catch (e) {
console.error(e);
}
cs


그래서 이를 처리하기 위해서 알아봤는데 각 파이프라인에서 발생하는 에러를 처리하기 위해서는 try/catch로만 잡을 수가 없다. 그래서 이를 해결하기 위해서 각 파이프 앞단에서 error 이벤트를 잡는 설정을 해줘야한다.


1
2
3
4
5
6
7
8
// 파일로 쓰기
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').on('error', function (e) {
  console.error(e);
}).pipe(transformer).on('error', function (e) {
  console.error(e);
}).pipe(writeStream).on('error', function (e) {
  console.error(e);
});
cs


하지만 이렇게만 하면 에러는 잡을 수 있어도 pipe에서 행이 걸리는 경우가 발생된다. 그래서 에러가 발생했을 때 행 걸리지 않고 다음 로직으로 정상적으로 처리되도록 하기 위해서는 this.emit('end')를 넣어줘야 한다.


1
2
3
4
5
6
7
8
9
10
11
// 파일로 쓰기
await request('https://image.toast.com/aaaaab/ticketlink/TKL_3/ion_main08061242.jpg').on('error', function (e) {
  console.error(e);
  this.emit('end');
}).pipe(transformer).on('error', function (e) {
  console.error(e);
  this.emit('end');
}).pipe(writeStream).on('error', function (e) {
  console.error(e);
  this.emit('end');
});
cs


참고

https://stackoverflow.com/questions/21771220/error-handling-with-node-js-streams


댓글()

node.js express에서 request 사용자 아이피 찾기

web/node.js|2018. 10. 15. 22:26

요청한 사용자의 Ip를 찾아서 로그를 남기거나 Ip 지역정보를 활용해서 geoIp를 찾아내거나 할 때 request를 요청한 사용자의 IP 주소가 필요하다.


Spring에서는 간단하게 HttpServletRequest에서 getHeader의 X-FORWARDED-FOR에 있는 정보를 가져오거나 getRemoteAddr()을 통해 가져올 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
 * 주문요청.
 *
 * @param req the req
 * @return the response entity
*/
@PostMapping(value = "/order")
public ResponseEntity<?> order(@Valid @RequestBody OrderRequestDto req, HttpServletRequest request) {
    String ip = request.getHeader("X-FORWARDED-FOR");
    if (ip == null)
        ip = request.getRemoteAddr();
 
    return ResponseEntity.ok(orderService.order(req));
}
cs


우선 헤더에서 X-FORWARDED-FOR에서 왜 아이피 정보를 찾는건가? 대부분의 사용자 요청이 오면 사용자 요청은 Load Balance등을 통해서 오게되기 때문에 원천 아이피를 찾을 수 없기 때무에 X-FORWARDED-FOR에 원천 아이피를 넣어서 전송한다.

결론은 XFF는 HTTP Header 중 하나로 HTPP Server에 요청한 Client의 IP를 식별하기 위한 표준이다.

참고 : http://blog.plura.io/?p=6597


그럼 node.js에서는 어떻게 가져와야 하나? 아주 간단하다.

1
const ip = req.headers['x-forwarded-for'||  req.connection.remoteAddress;
cs


그리고 만약 로컬에서 ::1로 사용자 IP가 출력되는 경우가 있다. ::1은 IpV6에서 로컬 호스트를 의미한다. 그래서 express 서버를 생성할 때 IPV4를 사용하도록 설정하면 된다.


댓글()

Javascript에서 epoch second 구하기

web/javaScript|2018. 10. 13. 10:07

epoch second 는 유닉스 시간을 나타내고 Epoch 시간 이라고 한다. 이를 자바스크립트로 구하는 방법을 알아보자.

1
Math.floor(new Date('2018-03-11') / 1000)
cs


다시 Date 값으로 돌리는 것도 어렵지 않다.

1
new Date(1520726400 * 1000)
cs



댓글()

nodejs 비동기 프로그래밍을 위한 deferred

web/javaScript|2018. 10. 6. 23:57

node.js에서 비동기 프로그래밍을 위해서 사용할 수 있는 deferred 라이브러리를 정리해보자.


우선 deferred의 경우에는 이전글에 작성했던 Promise와 동일한 개념이다. (https://wedul.tistory.com/508) promise와 마찬가지로 deferred는 비동기로 작업을 진행하고 비동기 처리가 완료되고 resolve, reject 메소드를 실행해서 비동기 동작 이후에 결과를 전달 할 수 있다.


우선 필요한 라이브러리를 다운로드 받아보자. 

https://www.npmjs.com/package/deferred

1
npm i deferred
cs


그리고 라이브러리 예제에 나와있는대로 deferred 라이브러리를 사용해서 비동기 프로그램을 실행시켜보면 간단하게 promise와 동일 한 방식으로 진행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
'use strict';
 
const deferred = require('deferred');
const promiseTest = require('./promise-test');
const deferredTest = module.exports;
 
deferredTest.deferTest = () => {
  return () => {
    const def = deferred();
 
    setTimeout(async () => {
      try {
        let dd = await promiseTest.proTest(true);
 
        if (dd.message == 'pnpsecure') {
          def.reject('Error');
        } else {
          def.resolve(dd);
        }
      } catch (e) {
        def.reject(e);
      }
    }, 1000);
 
    return def.promise;
  };
};
 
 
 
// 테스트 
const deferredTest = require('../../lib/libraryTest/deferred-test');
 
 it('deferred Test', async () => {
    try {
      let dd = await deferredTest.deferTest()();
      console.log(dd);
    } catch (e) {
      console.log(e);
    }
 
  });
cs


댓글()

Javascript promise에 대해 알아보자.

web/javaScript|2018. 10. 6. 23:02

기존에 ajax와 같은 비동기 요청이 종료가 되고 특정한 작업을 수행하고 싶을경우 콜백함수를 설정하여 진행했었다.

1
2
3
4
5
6
7
8
9
10
11
  Common.sendAjax({
    url: Common.getFullPath('user/cert/check'),
    param: { 'otp' : $otpNum.val(), 'userId' : $joinEmail.val() },
    type: 'POST',
    success: (e) => {
      // 성공 시 발생할 콜백함수
    },
    failed: () => {
      // 실패시 발생할 콜백함수
    }
  });
cs


이런 비동기 프로그래밍은 기존 동기식 프로그래밍 보다 작업을 요청하고 다른 작업을 할 수 있는 장점이 있다. 하지만 이는 그 유명한 콜백 지옥을 만들 수 가 있다. 

아래 보면 이런 콜백 지옥을 경험을 다들 해봤을거다.  실제로 이렇게 콜백지옥이 발생한 경우가 굉장히 많다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  Common.sendAjax({
    url: Common.getFullPath('user/cert/check'),
    param: { 'otp' : $otpNum.val(), 'userId' : $joinEmail.val() },
    type: 'POST',
    success: (e) => {
      // 성공 시 발생할 함수
      Common.sendAjax({
        success:(e) => {
          Common.sendAjax({     
            /**
                콜백지옥     
           
            */ 
          });
        }
      });
    },
    failed: () => {
      // 실패시 발생할 함수
    }
  });
cs


Promise를 적용해서 비동기 프로그래밍 진행하기.

promise 객체를 만들고 동작을 수행한 후 성공하면 resolve, 실패할 경우에 reject 메소드를 실행시킨다. resolve 메소드를 실행할 경우 promise 상태는 Fulfilled 상태가 된다. 그리고 reject 메소드가 실행되면 Rejected 상태가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Promise 
 
'use strict';
 
const test = module.exports;
 
test.proTest = (isSucess) => {
  return new Promise((resolve, reject) => {
    isSucess ? resolve('dbsafer') : reject(new Error('pnpsecure'));
  });
};
 
 
 
 
// 테스트 코드
 
'use strict';
 
const promiseTest = require('../../lib/libraryTest/promise-test');
const assert = require('assert');
const chai = require('chai');
const should = chai.should;
const expect = chai.expect;
 
describe('promise 테스트', () => {
  it('resolve 테스트', async () => {
    try {
      let resolveTest = await promiseTest.proTest(true);
 
      assert(resolveTest == 'dbsafer');
    } catch (e) {
      should.not.exist(e);
    }
 
  });
 
  it('reject 테스트', async () => {
    try {
      let resolveTest = await promiseTest.proTest(false);
    } catch (e) {
      console.log(e);
      expect(e).to.exist;
    }
  });
 
});
cs


Promise Chaining 

프로미스를 이용해서 콜백 지옥 대신에 프로미스를 연결해서 연속된 동작을 진행할 수 있다. 하지만 이런 프로미스 체이닝도 문제가 있는게 에러 발생시에 catch로 받을 수 있지만 어느 프로미스에서 발생한 에러인지를 알기가 어렵다는 문제가 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';
 
const test = module.exports;
 
test.proTest = (isSucess) => {
  return new Promise((resolve, reject) => {
    isSucess ? resolve('dbsafer') : reject(new Error('pnpsecure'));
  }).then((result) => {
    return result + 'babo';
  }).then((result) => {
    return result + 'Hi!';
  }).catch((err) => {
    return err;
  });
};
cs



댓글()

node.js 애플리케이션 프로세스 관리 도구 매니저

web/node.js|2018. 10. 6. 01:38

PM2 built-in 되어있는 Load Balancer 함께 node.js 애플리케이션의 runtime and process 관리 기능을  제공해 주는 라이브러리이다. PM2를 이용하여 application 죽지 않고 돌아갈 수있게 watch 기능을 제공해주는 기능도 제공한다.

그리고 error.log, info.log, 자원 점유 등등 여러 기능을 제공해준다.

간단하게 설치부터 사용법에 대해 정리해보자.


설치
$ npm install pm2 -g


실행방법
$ pm2 start app.js


사용법은 아주 간단하다. 위와 같이 command로 프로그램을 실행할 때 여러 옵션도 줄 수 있다.

1
2
// wedul_app.js 를 max_space_size를 500으로 지정하고 실행하라.
pm2 start wedul_app.js —node_args=“—max_old_space_size=500”
cs


두 개 이상의 옵션도 부여할 수 있다.

1
2
// max_space_size와 harmony(es 2015 옵션)를 둘다 사용.
pm2 start wedul_app.js --node-args="--max_old_space_size=500 harmony"  
cs


하지만 실제 운영 에서는 옵션이 한두가지가 아니기 때문에 계속 command로 설정을 지정 하기에는 부담이 된다. 

그래서 별도의 json 파일에 옵션값들을 정의할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
  "apps": [
    {
      "name": "wedul_batch",
      "script": "./batch/index.js",
      "watch": true,
      "node_args":"--harmony",
      "env": {
        "NODE_ENV": "development"
      },
      "env_production": {
        "NODE_ENV": "production"
      }
    },
    {
      "name": "wedul",
      "script": "./bin/www",
      "watch": true,
      "node_args":"--harmony",
      "env": {
        "NODE_ENV": "development"
      },
      "env_production": {
        "NODE_ENV": "production"
      }
    }
  ]
}
cs


위의 옵션은 각 실행시키고자 하는 애플리케이션의 이름과 위치 그리고 옵션등을 지정할 수 있다. 

위와 같이 설정하고 아래 명령어로 실행시킨다.

 pm2 start pm2test.json --name wedul_batch --env development

이렇게 실행하면 pm2test.json 옵션에 맞게 실행이 된다.( --env 옵션을 사용하면 이전에 공부했었던 process.env.NODE_EVN 전역변수값을 사용할 수 있다.)

이렇게 모든 설정을 하고 실행하면 다음과 같은 화면으로 실행된다.

그리고 더 여러 기능들을 제공하는데 그 중 몇가지만 살펴보자.


# detail (특정 애플리케이션 현재 상태 확인)

1
pm2 show [appname]
cs



# 실시간 로그 및 자원 현황 모니터링

1
pm2 monit
cs


#로그 확인

1
pm2 log [appname]
cs


댓글()
  1. BlogIcon node 2018.11.02 19:06 댓글주소  수정/삭제  댓글쓰기

    watch로 소스코드 변경 후 재시작 기능도 확인할 수 있어요.
    http://pm2.keymetrics.io/docs/usage/watch-and-restart/

  2. node 2018.12.26 14:40 댓글주소  수정/삭제  댓글쓰기

    cluster기능을 통해 싱글 코어에서 이벤트 루프를 받던 방식을 멀티 코어로 바꿀수도 있어요

Rest Operator와 Spread Operator

web/javaScript|2018. 10. 6. 01:20

es6를 사용하면서 보게된 Rest Operator와 Spread Operator에 대해 정리를 해보자.


Rest Operator

기존에 리터널 문법을 사용하여 객체나 배열의 값을 변수로 바인딩하여 사용할 수 있었다.
근데 object라는 객체의 값이 많은경우에는 여기서 값을 모두 하나하나 뽑아내는건 어렵기 때문에 하나의 객체로 뽑아낼때 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 기존에 하나씩 속성을 뽑아서 사용하던 방식
const object = {a : 'wedul', b : 'cjung', c : 'gglee', d : 'babo'};
const {a, b} = object;
console.log(a, b);
 
#출력
wedul, cjung
 
// Rest Operator 를 사용하여 여러 속성을 하나로 묶어서 진행
const object = {a : 'wedul', b : 'cjung', c : 'gglee', d : 'babo'};
const {a, b, ...obj} = object;
console.log(a, b, obj);
 
#출력
wedul cjung { c: 'gglee', d: 'babo' }
 
// 배열일때도 마찬가지로 사용가능한데 배열인 경우에는 뒤에 요소가 배열로 만들어진다.
// Rest Operator
const array = ['wedul', 'cjung', 'gglee', 'babo'];
const [a, b, ...obj] = array;
console.log(a, b, obj);
 
#출력
wedul cjung [ 'gglee', 'babo' ]
cs


Spread Operator

배열이나 Object에 있는 내용들이 열거되어 사용할 수 있도록 해주는 기능
아래의 예에서는 입력된 배열의 요소들이 getlternalTest() 함수수의 내용들이 name, age, address 순서대로 열거된다.

1
2
3
4
5
6
7
8
9
10
11
12
getleternalTest(name, age, address) {
  console.log(name, age, address);
  #출력 결과 cjung 23 seoul
}
 
 
#테스트
it('liternal test', () => {
  // array
  test.getleternalTest(...['cjung', 23, 'seoul']);
});
 
cs


Push로 기존에 배열에 데이터를 추가할 수도 있다.

1
2
3
4
let data = ['dd', 'cg', 'zz', 'cjung'];
data.push(…data);
console.log(data);
-> [“dd", "cg", "zz", "cjung", "dd", "cg", "zz", "cjung"] 
cs


Object도 동일하게 사용가능


궁금사항으로 이렇게 복사된 항목들은 깊은복사가 될까였다. 그래서 테스트를 해봤다.
-> 테스트 결과 해당 객체가 만들어질때 데이터가 값이 복사가 되기 때문에 서로 별개로 동작하게 된다. 아래 예를 보면 동작이 이해가 가능하다.

1
2
3
4
5
6
7
8
> copyBabo // babo 오브젝트에서 복사된 copyBabo의 항목
< {name: "cjung", age: 23, addr: "seoul"}
 
// copyBabo name 속성 변경
> copyBabo.name = 'gglee';
 
> babo // babo의 속성을 확인했을 때 바뀌지 않은걸 알 수있다.
< {name: "cjung", age: 23, addr: "seoul"}
cs




댓글()

html의 화면을 캡쳐해서 이미지로 변경하는 html2canvas와 pdf로 저장하는 jsPDF 라이브러리 소개

web/javaScript|2018. 6. 29. 23:21

회사에서 업무를 진행하면서 현재 화면을 pdf로 만들어야하는 일이 있었다.

기존에 html을 pdf로 스프링에서 만드는 업무는 진행을 했었다. 이때는 이전에 포스팅을 했었으니 참고 하시면 좋을 것 같다. (Spring에서 html2pdf를 사용해서 pdf파일 만들기)

우선 현재 화면을 딱 캡쳐해서 image로 만드는 작업이 필요했다. 그 작업을 제공해주는 라이브러리가 바로 html2canvas이다.

 

html2canvas

우선 html2canvas를 사용하기 위해 파일을 받아보자.

npm install --save html2canvas

캡처를 진행하기 위해서는 캡처하고자 하는 영역을 selector로 위치를 받아서 지정할 수 있다.

우선 캡처를 진행할 화면을 간단하게 만들어보자.

<div id="capture" style="padding: 10px; background: #f5da55">
    <h4 style="color: #000; ">Hello world! Wedul Capture Test!</h4>
</div>

<button id="captureBtn" style="padding:20px;margin-top:15px;">Capture Start!!</button>

<script src="./node_modules/jquery/dist/jquery.min.js"></script>
<script src="./node_modules/html2canvas/dist/html2canvas.min.js"></script>
<script src="./index.js"></script>

하단에 captureBtn 버튼을 누를 경우에 capture영역이 image로 떨어지도록 javascript를 만들어보자.

// 버튼 클릭할 시 캡처한 내용 body에 붙혀보기
$('#captureBtn').click((e) => {
  html2canvas(document.querySelector("#capture")).then(canvas => {
    document.body.appendChild(canvas)
  });
});

캡처 방식이 생각보다 너무 간단하다. querySelector로 영역을 선택하고 html2canvas라이브러리를 호출하면 프로미스를 사용하여 나온 canvas객체를 조작하여 image로 넣을수도 있고 특정 파일로 변경할 수도 있다. 결과는 다음과 같이 버튼을 눌렀을 때 바로 밑에 이미지가 추가되는 것을 확인할 수 있다.

 

jsPDF

그럼 html2canvas로 출력된 image를 pdf 파일로 빼고 싶다면 어떻게 해야할까? 바로 jsPDF 라이브러리를 사용하면 된다.

홈페이지에 들어가면 자세하게 샘플코드가 나와있다. 그럼 간단하게 한번 만들어보자. 

우선 pdf파일로 빼내기 위해서는 jsPDF 라이브러리가 필요하니 설치부터 진행하자.

npm i jspdf --save

// import
<script src="./node_modules/jspdf/dist/jspdf.min.js"></script>

그리고 위에 html2canvas에서 사용한 문장을 그대로 사용하고 출력되서 나온 canvas를 사용하여 pdf로 만들어보자.

// 버튼 클릭할 시 캡처한 내용 body에 붙혀보기
$('#captureBtn').click((e) => {
  html2canvas(document.querySelector("#capture")).then(canvas => {
    // jsPDF 객체 생성 생성자에는 가로, 세로 설정, 페이지 크기 등등 설정할 수 있다. 자세한건 문서 참고.
    // 현재 파라미터는 기본값이다 굳이 쓰지 않아도 되는데 저것이 기본값이라고 보여준다.
    var doc = new jsPDF('p', 'mm', 'a4');

    // html2canvas의 canvas를 png로 바꿔준다.
    var imgData = canvas.toDataURL('image/png'); //Image 코드로 뽑아내기

    // image 추가
    doc.addImage(imgData, 'PNG', 0, 0);

    // pdf로 저장
    doc.save('sample-file.pdf');
  });
});

 

버튼을 클릭해보면 짠하고 pdf가 우선은 다운로드 된 것을 확인할 수 있다. 그럼 pdf 파일을 열어보자!

오호 잘나온다. 참 좋은 시대다 좋은 오픈소스 라이브러리도 많고 공부할 것도 많고 참 본받을 것이 많은 사람들이다.

추가적인 옵션은 문서에 보면 자세히 나와있다.

여기서 참고할만한 내용이 하나 있어서 적어놓았다. 만약에 캡처한 이미지가 출력하는 pdf 페이지보다 클 경우에는 페이지 별로 짤라서 이미지를 붙혀넣으면 자연스럽게 pdf 파일이 만들어진다. 코드는 아래 내용을 참고!

var imgData = canvas.toDataURL('image/png');

var imgWidth = 210; 
var pageHeight = 295;  
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;

var doc = new jsPDF('p', 'mm');
var position = 0;

doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;

while (heightLeft >= 0) {
  position = heightLeft - imgHeight;
  doc.addPage();
  doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
  heightLeft -= pageHeight;
}
doc.save(filename + '.pdf');

 

 

자세한 문서는 https://rawgit.com/MrRio/jsPDF/master/docs/index.html에서 확인해보자.

댓글()
  1. 2018.08.08 00:53 댓글주소  수정/삭제  댓글쓰기

    안녕하세요! 혹시 소스파일 받아볼 수 가 있을까요?

  2. 김삿갓 2019.01.10 15:49 댓글주소  수정/삭제  댓글쓰기

    body에 이미지를 보여짖말고 바로 다운이 가능한가요?
    pdf처럼요

  3. 호잇 2019.03.27 16:18 댓글주소  수정/삭제  댓글쓰기

    정말 너무너무 감사합니다~
    덕분에 회사업무를 잘 처리할 수 있었습니다 ㅠㅠ!
    유용하게 잘 사용했습니다. 다시한번 감사합니다 ^^

  4. Jack 2020.02.07 13:14 댓글주소  수정/삭제  댓글쓰기

    고맙습니다! 많은 도움이 되었습니다!
    정말 깔끔한 설명과 소스 덕분에 너무 간단하게 처리했습니다.
    둘러보니 정말 좋은 글들이 많네요.
    즐겨찾기 하고 자주 놀러오겠습니다.
    감사합니다!
    애드블럭끄고 광고도 누르고 왔읍니다! ㅋㅋ

    *참고(html2canvas, jspdf CDN)
    <script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
    <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>