728x90

< 5탄 - 모바일 결제 플로우에 맞는 코드리뷰 >

 

이전 글들을 못보신 분들을 위해 앞선 글들의 링크를 제공합니다.

< 1탄 - 프롤로그 >

https://blog.self-made.cloud/240

< 2탄 - Code 먼저 공개합니다. >

https://blog.self-made.cloud/241

< 3탄 - 결제 플로우가 어떻게 되는가 >
https://honeystorage.tistory.com/242 

< 4탄 - 웹 결제 플로우에 맞는 코드리뷰 >
https://honeystorage.tistory.com/243

 

 

아임포트 없이 이니시스 결제모듈 연동하기를 진행중입니다.

그 마지막 여정인대요.

 

이전에도 언급했다싶이

이니시스에서는 웹과 모바일 API를 각각 제공합니다.

따라서, 연동도 각각 나눠서 해주어야 합니다.

 

이번에는 마지막으로

모바일 연동에대해 알아보도록 하겠습니다.

 

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 모듈을 실행함
4. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  4-1. 팝업을 닫은 경우, 결제 실패 페이지로 보내거나, 기존의 페이지로 돌려보냄
  4-2. 결제 요청을 완료한 경우, 서버로 요청이 전송됨
5. 서버에서 요청을 처리 및 데이터를 저장하고 결제완료 페이지로 Redirect시킴
6. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌

 

1~3 과정을 통해 결제 모듈일 실행합니다.

// client/src/pages/shop

const onOrderRequest = () => {
  if (isMobile()) return mobileOrder();
  else return webOrder();
};

const newOrder = () => {
  return axios.get('/v1/inicis/new/order');
};

const mobileOrder = async () => {
  const { data: info } = await newOrder();
  const { status, data } = info;

  if (status === 'success') {
    orderNumber = data;

    axios.get('/v1/inicis/m/request/form', { params: { orderId: data } }).then(resolve => {
      const { data: info } = resolve;
      const { status, data } = info;

      if (status === 'success') {
        const form = document.createElement('form');
        form.method = 'post';
        form.acceptCharset = 'UTF-8';
        form.hidden = true;
        form.id = 'pay_form';
        form.action = 'https://mobile.inicis.com/smart/payment/';

        for (let o in data) {
          const input = document.createElement('input');
          input.name = o;
          input.value = data[o];
          input.hidden = true;
          form.appendChild(input);
        }

        document.querySelector('#shop-page').appendChild(form);
        form.submit();
      } else {
        alert('요청 실패');
      }
    });
  }
};

 

서버쪽 코드도 살펴볼까요

const getMobileRequestForm = async (req, res) => {
  const { orderId } = req.query;
  if (!orderId) {
    return res.send({ status: 'error', data: 'check parameters' });
  }

  try {
    // 요청된 orderId가 DB에 실제로 존재하는지 체크
  } catch (error) {
    console.log('check exist order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }

  try {
    const price = 100; // 실제로는 요청된 상품 정보를 조회

    const dataset = {
      P_INI_PAYMENT: 'VBANK',
      P_MID: process.env.MID,
      P_OID: orderId,
      P_AMT: price,
      P_GOODS: 'Sample',
      P_UNAME: '홍길동',
      P_NEXT_URL: getServerDomain() + '/v1/inicis/m/pay/after',
      P_NOTI_URL: getServerDomain() + '/v1/inicis/confirm/payment',
      P_HPP_METHOD: 1,
      P_CHARSET: 'utf8', // <-- 결과값 한글깨짐 방지
    };

    return res.send({ status: 'success', data: dataset });
  } catch (error) {
    console.log('make request form : ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};

 

웹 결제보다는 그 연동과정이 훨씬 간단합니다.

이니시스에서 나중에 만든 API라

신경을 더 많이쓴것 같습니다.

 

모바일 연동이라면 처음 진행해도 어렵지않게

진행할수 있지 않을까 싶네요.

 

나머지 과정은 서버로 들어온 결과를 처리하고 데이터를 저장하거나

알맞는 페이지로 사용자를 Redirect 시켜주는 정도인대요.

 

어렵지 않으니 코드를 보며 진행하면 될것같습니다.

 

여기까지 고생 너무 많으셨고요

성공적인 결제연동 되어서

비용 절약하셨기를 바랍니다.

 

다만, 이니시스 뿐만 아니라

여러 PG사를 연동하고자 한다면

개발 시간 비용을 고려했을때

아임포트 같은 서비스를 쓰는게 합리적인 선택이라고

생각이 드네요 ^^

 

728x90
반응형
728x90

< 4탄 - 웹 결제 플로우에 맞는 코드리뷰 >

 

이전 글들을 못보신 분들을 위해 앞선 글들의 링크를 제공합니다.

< 1탄 - 프롤로그 >

https://blog.self-made.cloud/240

< 2탄 - Code 먼저 공개합니다. >

https://blog.self-made.cloud/241

< 3탄 - 결제 플로우가 어떻게 되는가 >
https://honeystorage.tistory.com/242 

 

이니시스 연동을 위한 웹 결제 플로우가 아래와 같다고 3탄에서 소개했는데요.

제법 복잡하죠.. (더 단순화 할수 있을것같지만 더 만지기가 싫더라구요 ㅠㅠ)

먼저, 웹 버전의 플로우는 이와같습니다.

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 폼을 서버로 요청
4. 서버에서 결제를 진행하기위한 데이터들을 클라이언트로 반환
5. 클라이언트에서 서버로 이니시스 결제모듈 요청 (팝업형태)
6. 서버에서 이니시스 결제모듈 반환
7. 클라이언트에 팝업 모듈이 열림 + 이때부터 팝업 모듈 상태를 주기적으로 체크
8. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  8-1. 닫은경우 팝업 열람전 페이지 유지 혹은 기타 처리
  8-2. 팝업에서 결제요청한 경우 서버로 요청이 전송됨 + 팝업이 닫힘
9. 서버에서 요청 받은 정보들을 통해 결제요청 처리 및 데이터 저장
10. 팝업 모듈이 닫힌게 체크되면 결제요청 완료 페이지로 이동
11. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌

 

 

플로우별로 어떤 코드가 어디에 해당하는지 살펴보도록 하겠습니다.

 

1 ~ 4

// client/src/pages/shop

const onOrderRequest = () => {
  if (isMobile()) return mobileOrder();
  else return webOrder();
};

const newOrder = () => {
  return axios.get('/v1/inicis/new/order');
};

const webOrder = async () => {
  const { data: info } = await newOrder();
  const { status, data } = info;

  if (status === 'success') {
    orderNumber = data;

    axios.get('/v1/inicis/request/form', { params: { orderId: data } }).then(resolve => {
      const { data: info } = resolve;
      const { status, data } = info;
      
      if (status === 'success') {
        const form = document.createElement('form');
        form.method = 'post';
        form.acceptCharset = 'UTF-8';
        form.hidden = true;
        form.id = 'pay_form';

        for (let o in data) {
          const input = document.createElement('input');
          input.name = o;
          input.value = data[o];
          input.hidden = true;
          form.appendChild(input);
        }

        document.querySelector('#shop-page').appendChild(form);
        window.INIStdPay.pay('pay_form');
        inicisFormStatus = setInterval(checkInicisFormStatus, 1000);
      } else {
        alert('요청 실패');
      }
    });
  } else {
    alert('요청 실패');
  }
};

 

// server/api/controllers/inicis.controller.js

const getNewOrder = async (req, res) => {
  const { serviceId } = req.query; // 실제로 개발할때는 요청이 들어온 서비스 정보를 주문번호와 함께 저장합니다.

  try {
    const newOrderId = makeOrderId('sample');

    // save order Id
    // ...

    return res.send({ status: 'success', data: newOrderId });
  } catch (error) {
    console.log('create new order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};

const getRequestForm = async (req, res) => {
  const { orderId } = req.query;
  if (!orderId) {
    return res.send({ status: 'error', data: 'check parameters' });
  }

  try {
     // 요청된 orderId가 DB에 실제로 존재하는지 체크
  } catch (error) {
    console.log('check exist order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }

  try {
    const price = 100; // 실제로는 요청된 상품 정보를 조회
    const timestamp = dayjs().valueOf();

    const dataset = {
      version: '1.0',
      gopaymethod: 'VBank',
      mid: process.env.MID,
      signature: encryptSha256(`oid=${orderId}&price=${price}&timestamp=${timestamp}`),
      mKey: encryptSha256('SU5JTElURV9UUklQTEVERVNfS0VZU1RS'), // 개발용, 배포용에서는 발급된 key를 사용
      price,
      oid: orderId,
      timestamp,
      currency: 'WON',
      goodname: 'Sample',
      buyername: '홍길동',
      buyertel: '01012341234',
      buyeremail: 'sample@sample.com.kr',
      returnUrl: getServerDomain() + '/v1/inicis/pay/after',
      payViewType: 'popup',
      popupUrl: getServerDomain() + `/v1/inicis/popup/open/${orderId}`,
      closeUrl: '',
    };

    return res.send({ status: 'success', data: dataset });
  } catch (error) {
    console.log('make request form : ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};

이니시스의 웹 결제요청은 폼을 기본으로합니다.

폼을 기본으로 이니시스에서 제공하는 라이브러리를 통해

결제 API요청을 하기 때문에 위와 같은 처리가 필요합니다.

 

popup 형태로 결제 폼을 띄우기 위해서는 위 코드중

payViewType: 'popup',
popupUrl: getServerDomain() + `/v1/inicis/popup/open/${orderId}`

이 부분이 꼭 필요합니다.

팝업이 아니라 페이지 형태의 결제 폼을 제공한다면 위 두줄은 필요없습니다.

 

클라이언트에 띄워주는 팝업에

알맞는 결제모듈을 제공하기 위한 코드들을 살펴볼까요

 

5~7에 해당합니다.

// server/api/controller/inicis.controller.js

const openInicisModule = async (req, res) => {
  const { orderId } = req.params;
  if (!orderId) {
    return res.send({ status: 'error', data: 'check parameters' });
  }

  try {
    // 요청된 orderId가 DB에 실제로 존재하는지 체크
  } catch (error) {
    console.log('check exist order: ', error);
    return res.send({ status: 'error', data: 'error' });
  }

  try {
    const price = 100; // 실제로는 요청된 상품 정보를 조회
    const timestamp = dayjs().valueOf();

    const dataset = {
      version: '1.0',
      gopaymethod: 'VBank',
      mid: process.env.MID,
      signature: encryptSha256(`oid=${orderId}&price=${price}&timestamp=${timestamp}`),
      mKey: encryptSha256('SU5JTElURV9UUklQTEVERVNfS0VZU1RS'), // 개발용, 배포용에서는 발급된 key를 사용
      price,
      oid: orderId,
      timestamp,
      currency: 'WON',
      goodname: 'Sample',
      buyername: '홍길동',
      buyertel: '01012341234',
      buyeremail: 'sample@sample.com.kr',
      returnUrl: getServerDomain() + '/v1/inicis/pay/after',
      payViewType: 'popup',
      popupUrl: getServerDomain() + `/v1/inicis/popup/open/${orderId}`,
      closeUrl: '',
    };

    return res.render('w_inicis', { ...dataset });
  } catch (error) {
    console.log('make request form : ', error);
    return res.send({ status: 'error', data: 'error' });
  }
};
// server/views/w_inicis.pug

doctype html
html(lang="ko")
  head
    title= title
    meta(http-equiv="Content-Type", content="text/html;charset=UTF-8")
    meta(http-equiv="Cache-Control", content="no-cache")

    meta(property="og:title", content= '이니시스 결제')
    meta(property="og:description", content= '이니시스 결제 연동 해보자')
    meta(property="og:type" content= 'website')

    meta(name="description" content= '이니시스 결제 연동 해보자')
    meta(name="type" content= 'website')

    script(src="https://stgstdpay.inicis.com/stdjs/INIStdPay.js", type="text/javascript", charset="UTF-8")
  body
    script. 
      var onPay = function() { INIStdPay.pay("pay_form") }
      onPay()
    form(id="pay_form", method="post", accept-charset="UTF-8", hidden='true') 
      input(type="hidden", name="version", value= version)
      input(type="hidden", name="gopaymethod", value= gopaymethod)
      input(type="hidden", name="mid", value= mid)
      input(type="hidden", name="oid", value= oid)
      input(type="hidden", name="price", value= price)
      input(type="hidden", name="timestamp", value= timestamp)
      input(type="hidden", name="signature", value= signature)
      input(type="hidden", name="mKey", value= mKey)
      input(type="hidden", name="currency", value= currency)
      input(type="hidden", name="goodname", value= goodname)
      input(type="hidden", name="buyername", value= buyername)
      input(type="hidden", name="buyertel", value= buyertel)
      input(type="hidden", name="buyeremail", value= buyeremail)
      input(type="hidden", name="returnUrl", value= returnUrl)
      input(type="hidden", name="closeUrl", value=closeUrl)

 

결제 연동중 제일 중요한게 위 과정이라고 생각하는데요.

바로, 결제 모듈을 뷰 템플릿을 이용해 SSR(server-side rendering) 형태로 제공하는것입니다.

 

여기서 왜 어려움을 겪게되냐면

이니시스 API상에서 보면 "요청과 응답하는 서버의 Domain을 일치시켜라" 라는 부분이 있거든요.

CSR과 RestAPI를 통해 서비스를 제공하는 입장에선는

이 요구사항을 맞추기가 정말 어렵더라고요.

도대체 이걸 어떻게 해야되나... 많은 생각이 들었습니다.

 

그래서 생각해낸게 바로, "결제 모듈 페이지는 서버에서 제공하자!" 입니다.

CSR프로젝트라고해서 페이지를 모두 클라이언트에서

제공하려고 하는것은 나의 딱딱한 생각 때문구나! 싶었습니다.

 

나머지는 이제 원하는대로 요청을 처리하거나

실패/성공 페이지를 제공하는 정도입니다.

 

https://github.com/jaekwangLee/inicis-without-pg 를

참고하여 진행하면 그리 어렵지않아

웹 결제 플로우 코드리뷰는 여기까지로 마치도록 하겠습니다.

 

728x90
반응형
728x90

< 3탄 - 결제 플로우가 어떻게 되는가 >

 

이전 글들을 못보신 분들을 위해 앞선 글들의 링크를 제공합니다.

< 1탄 - 프롤로그 >

https://blog.self-made.cloud/240?category=752264 

< 2탄 - Code 먼저 공개합니다. >

https://blog.self-made.cloud/241?category=752264

 

 

이니시스 결제모듈은

웹 버전과 모바일 버전의 API가 각각 다르게 제공됩니다.

사용해보니 모바일 버전이 나중에 따로 제작된것같네요.

 

아무튼 제공되는 API가 다르니

두가지를 나눠서 설명할 예정입니다.

 

먼저, 웹 버전의 플로우는 이와같습니다.

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 폼을 서버로 요청
4. 서버에서 결제를 진행하기위한 데이터들을 클라이언트로 반환
5. 클라이언트에서 서버로 이니시스 결제모듈 요청 (팝업형태)
6. 서버에서 이니시스 결제모듈 반환
7. 클라이언트에 팝업 모듈이 열림 + 이때부터 팝업 모듈 상태를 주기적으로 체크
8. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  8-1. 닫은경우 팝업 열람전 페이지 유지 혹은 기타 처리
  8-2. 팝업에서 결제요청한 경우 서버로 요청이 전송됨 + 팝업이 닫힘
9. 서버에서 요청 받은 정보들을 통해 결제요청 처리 및 데이터 저장
10. 팝업 모듈이 닫힌게 체크되면 결제요청 완료 페이지로 이동
11. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌

 

모바일 버전의 플로우

1. 클라이언트에서 서버로 상품 및 주문번호 요청
2. 서버에서 주문 상품을 가지고 주문을 생성, 주문 번호를 반환
3. 클라이언트에서 서버로부터 전달받은 주문 번호를 가지고 결제 모듈을 실행함
4. 사용자 액션에 따른 처리 (1. 팝업을 닫음, 2. 팝업에서 결제 요청)
  4-1. 팝업을 닫은 경우, 결제 실패 페이지로 보내거나, 기존의 페이지로 돌려보냄
  4-2. 결제 요청을 완료한 경우, 서버로 요청이 전송됨
5. 서버에서 요청을 처리 및 데이터를 저장하고 결제완료 페이지로 Redirect시킴
6. 클라이언트에서 서버로부터 결제 정보를 호출하여 사용자에게 보여줌
728x90
반응형
728x90

< 2탄 - Code 먼저 공개합니다. >

 

필요한 정보를 위해

시리즈로 제공되는 블로그를 보는것은

어느정도 기본실력이 있는분들에게는

시간낭비 일수있습니다.

 

아임포트와 같은 서비스없이

이니시스 결제 연동을 하고자하는 분들 중에서

 

일부 코드만을 참고하시고자 하는 분들을 위해

관련 소스코드를 모두 업데이트해둔

공개 레포지토리를 먼저 공유합니다.

 

https://github.com/jaekwangLee/inicis-without-pg

 

GitHub - jaekwangLee/inicis-without-pg: CSR환경에서 PG사 없이 KG이니시스 결제연동을 합니다.

CSR환경에서 PG사 없이 KG이니시스 결제연동을 합니다. Contribute to jaekwangLee/inicis-without-pg development by creating an account on GitHub.

github.com

 

728x90
반응형
728x90

< 1탄 - 프롤로그 >

웹,앱 등의 서비스에서 광고 수익외에

직접적인 수익을 창출하려면 결제연동을 필수적으로 하게됩니다.

그 과정에서 어려움을 겪게되는대요.

 

주로 그 원인은

PG사들이 제공하는 가이드라인이

부실하기 때문입니다.

 

사실 부실할 수 밖에 없은 이유가있죠.

이제는 너무나도 다양해진 개발 환경에 

모두 대응되는 가이드라인을 제공할 수 없기 때문입니다.

 

이번에 React 프로젝트에 결제연동을 하다보니

react와 같은 CSR에 대해서는 가이드라인이 부실함은 물론

관련 자료도 매우 부족함을 알게되었습니다.

 

미약하지만

아임포트 같은 서비스없이

KG이니시스 결제모듈 연동에 성공하여 정보를 공유하고자합니다.

 

다룰 내용은

Client : React 

server : node js

PG: inicis
결제수단 : 가상계좌 ( 다른 결제수단도 PG사와 제휴만 맺으면 해당 방법으로 얼마든지 응용가능해보입니다.)

 

해당 상황에 해당하시는분들에게

많은 도움이 됐으면 합니다.

감사합니다.

728x90
반응형
728x90

올바른 개발자가 개발을 하다보면

자기의 입맛, 혹은 팀의 입맛에 맞게

개발에 도움이 되는 환경을 세팅해나가게 된다.

 

그러다보면 로컬에서는 적용되는데 서버에서는 뭔가 적용이 안되거나하는

경우가 종종 발생한다.

이번엔 그중에서 font 적용에 대해서 알아본다.

 

/MyApp
  /public
    /fonts
      honey-Bold.ttf
      honey-Bold.eot
      honey-Regular.ttf
      honey-Regular.eot
    /images
    index.html
  /src
    app.js
    index.css

 

자, 주어진 프로젝트 환경의 파일트리가 위와 같은 상황이다.

index.css에서 프로젝트 내 모든 텍스트의 폰트를 잡아주려고한다.

어떻게 하면될까?

 

// index.css

@font-face {
    font-family: 'honey';
    src: url('/public/fonts/honey-Bold.eot');
    src: url('/public/fonts/honey-Bold.eot') format('embedded-opentype'), url('/public/fonts/honey-Bold.ttf') format('truetype');
    font-weight: 600;
    font-style: normal;
}

@font-face {
    font-family: 'honey';
    src: url('/public/fonts/honey-Regular.eot');
    src: url('/public/fonts/honey-Regular.eot') format('embedded-opentype'), url('/public/fonts/honey-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
}

* {
    font-family: 'honey', sans-serif;
}

 

honey라는 폰트는 없겠지만 예를들면 이렇다.

woff 까지 이와 같은 방식으로 설정해주면 된다.

 

eject한 CRA 프로젝트라면 빌드 했을때,

dist폴더가 자동으로 생성되며

dist/ 폴더아래로 빌드된 파일이 들어가게되는데

여기서 부터 문제가 발생한다.

 

빌드해 서버에 업로드했더니 폰트가 먹지않는 이슈

 

왜그럴까?

dist/ 폴더에는 public/fonts/ 가 존재하지 않는다.

간단한 webpack 설정을 통해 경로를 맞춰주어야한다.

 

npm install file-loader --save-dev
npm install copy-webpack-plugin --save-dev

 

준비물은 위 두가지이다.

 

const CopyWebpackPlugin = require('copy-webpack-plugin');

...

module: {
  rules: [
    {
      test: /\.(woff|woff2|eot|ttf|otf)$/,
      use: [
        {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            outputPath: 'fonts/',
            publicPath: '/public',
          }
        }
      ]
    }
  ]
},

...

plugins: [
  new CopyWebpackPlugin({
    patterns: [
      {
        from: 'public/fonts',
        to: 'publi/fonts',
      }
    ]
  })
],

핵심은 CopyWebpackPlugin 부분이다.

여기서 무슨 일이 일어나냐면

public/fonts/ 경로의 파일들은

dist 아래에도 똑같이 만들어주는것이다.

 

그러면 빌드후에도 css설정이 당연하게도 잘 들어맞는다.

 

728x90
반응형
728x90

base64? buffer? stream?

일반적으로 자주 사용하지는 않으나

파일 처리를 할때면 나타나 곤란한 상황을 만드는 녀석들이다.

 

이번에 우연한기회에

AWS에 업로드된 파일 URL을 노출시키지 않으면서

클라이언트에서 파일을 내려받게 해줘야하는 상황이 발생했다.

 

해당 과정에서 삽질하며 얻은 조각 지식들을 공유한다.

 

[ 가정 ]

1. 클라우드 저장소에 파일이 있다.

2. 클라이언트에서 파일 다운로드 요청 발생.

3. 서버에서 해당 파일을 불러온다.

4. 클라이언트에 해당 파일을 전송한다.

5. 다운로드 실시

 

1.  클라우드 저장소에 파일이 있다.

- AWS의 S3에 특정 파일이 저장되어있는 상황입니다.

- 해당 파일은 비문은 아니지만 공유를 원치않아 파일의 링크는 노출되지 않기를 원합니다.

- 마찬가지로 해당 파일의 버킷은 퍼블릭 버킷이 아닙니다.

- 따라서, 허가된 key를 가진 사용자만 파일을 호출할 수 있습니다. (즉, 서버에서만 요청 가능)

 

2. 파일을 필요로 하는 클라이언트는 지정된 서버로 요청을 보냅니다.

- 지정된 서버는 당연히 해당 AWS계정의 접근권한, key를 가진 서버겠죠.

 

3. 서버는  S3로부터 파일을 가져옵니다.

const AWS = require('aws-sdk')

const confg = {
  accessKeyId: '',
  scretAccessKey: '',
  region: '',
  signatureVersion: 'v4',
};

AWS.config.update(config);
const S3 = new AWS.S3();

function getObjectFromS3(key_name) {
  return new Promise((resolve, reject) => {
    S3.getObject({ Bucket: '', Key: key_name }, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    })
  });
}

 

getObjectFromS3를 이용하면 쉽게 파일을 읽어올 수 있습니다.

자, 지금부터가 집중이 필요합니다.

불러온 데이터는 JSON 형태인데

파일은 Body에 Buffer형태로 따라옵니다.

 

이, Buffer를 어떤식으로 클라이언트로 반환해줄건지

이걸 받으려면 클라이언트는 어떻게 해야하는지

주목해야합니다.

 

다시, 돌아가 클라이언트에서 파일을

어떻게 요청해야되는지 살펴봅시다.

 

4. 클라이언트에서 서버에 특정 파일 요청하기

const requestPrivateFile = () => {
  axios.post(url, {}, { responseType: 'arrayBuffer' })
  .then(resp => {
    const { data } = resp;
    console.log(data);
    ...
  });
}

 

자, 일반적인 API요청과 다르게  responseType으로 arrayBuffer을 지정해주었습니다.

이걸 놓치면 백날 삽질해도 소용없습니다.

 

서버에서도 그러면 값을 맞춰서 반환해줘야겠죠?

 

5. 서버에서 불러온 파일 반환해주기

const getPrivateFile = async (req, res) => {
  ...
  
  const file = await getObjectFromKey('something.pdf');
  
  res.writeHead(200, [ 
    ['Content-Type', 'application/pdf'], //  다른 형식의 파일이면 알맞는 mime/type을 지정해주세요.
  ]);
  res.end(Buffer.from(file.Body, 'base64'));
}

sendFile이나 send 등이 아니라 위와 같은 방식으로

파일을 리턴해주게됩니다.

 

6. 자, 그러면 파일을 받아서 한번 다운로드해볼까요?

const requestPrivateFile = () => {
  axios.post(url, {}, { responseType: 'arrayBuffer' })
  .then(resp => {
    const { data } = resp;

    downloadFile(data, 'something.pdf');
  });
}

function downloadFile(buffer, filename) {
  const blob = new Blob([buffer], { type: 'application/pdf' });
  const url = window.URL.createObjectURL(blob);
  
  const a = document.createElement('a');
  a.download = filename; // download될때의 파일명을 지정해줍니다.
  a.href = url;
  
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a); // 더 이상 필요없으므로 삭제
}

 

자, 여기까지 "서버로부터 buffer 형식으로 파일 전송받아 처리하기"를 해보았습니다.

알고나면 간단하지만 모를때는 조각조각 흩어져있는

토막지식들로인해 어려움을 주는 사항이었습니다...

 

여러가지 상황에 응용이 가능하니 알아두면 좋을것같습니다.

728x90
반응형
728x90

프로젝트를 진행하다보면

 

점차 규모가 커져서 상대경로로 작업하는데 어려움이 찾아오곤 한다.

 

따라서, 절대경로로 작업을 하고싶을때가 오는데

 

두가지 설정해줄것들이 있다. 

 

1. 절대경로로 표기해도 경로를 자동완성 해줄  "jsconfig.json"

2. 빌드 후에도 해당 경로를 올바르게 파싱해줄 "webpack.config.js"

 

여기서는 jsconfig.json의 설정을 살펴본다

 

아래 링크에서 예제를 확인해볼 수 있다.

 

https://code.visualstudio.com/docs/languages/jsconfig

 

jsconfig.json Reference

View the reference for jsconfig.json.

code.visualstudio.com

 

 

파일경로가

App
- src
  - index.js
  - screens
    - main
      - introduce.js
      - team.js
      - contact.js
    - about.js
  - components
    - common
      - table
        - row.js
        - item.js
- public
  ...

이렇게 되어있을때, team.js에서 row.js를 import 한다고 가정해보자.

import {} from '../../components/common/table/row'

 

지금은 뎁스가 깊지않아 어렵지 않네? 어쩌피 자동완성 지원해주는데 그냥 쓸까? 싶기도 하겠지만

방심은 금물

금방 프로젝트 구조는 복잡해지고말게 분명하다.

 

// jsconfg.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

이렇게 간단한 파일 하나만 App 프로젝트 밑에 추가해주면

import {} from '@/components/common/table/row';

이런식으로 작성하여도  vscode가 자동완성을 지원해준다.

728x90
반응형
728x90

클라이언트 코드를

EC2에서 관리 및 배포하곤 했었는데

사실 용량도 많이 차지하고 좋은 방법은 아니라고 생각했다.

 

더 빠른 전달속도와 더 쉬운 프로젝트 관리를 위한 방법을 찾던중

CloudFront + Route53+ S3 + ACM(for SSL)
조합을 발견했다.

 

처음해보는 사람이라도 쉽게 할수있고,

효과는 눈이 번쩍일정도로 뛰어난것같다.

 

이에 대해 고민하던 나와 다른 분들을위해

위 조합을 이용하는 방법을 상세하게 공유하고자 한다.

 

1. S3

1-1) 먼저, 시작은 S3에서부터다

1-2) React 프로젝트가 있다면 빌드후 생성된 파일들을 S3에 올릴것이다.

1-3) S3 -> 버킷 만들기

1-4) 주의사항은 모든 퍼블릭 액세스 차단을 해제시킬것 (사이트는 누구나 접근가능해야하니까요)
1-4-1) 버킷 만들기
1-5) 버킷을 만들고나면 객체 탭에다가 빌드후 생성된 파일들을 드래그다운하여 업로드한다.
1-6) 다음, 권한 탭에서 버킷 정책을 생성해준다
1-6-1) 편집 -> 정책 생성기 
1-6-2) Select Type of Policy: S3
1-6-3) Effect: Allow

1-6-4) Principal: *

1-6-5) Aws Service: S3

1-6-6) Actions: GetObject
1-6-7) ARN: 정책 편집 페이지에 있는 버킷ARN을 복사해 붙여넣는다. 그리고 뒤에 /*을 덧붙인다
1-6-8) ARN 예시: arn:aws:s3:::coke.cola/*
1-7) 속성탭으로 이동한다.
1-7-1) 맨 하단에 정적 웹 사이트 호스팅을 편집
1-7-2) 정적 웹사이트 호스팅 -> 활성화, 유형 -> 정적 웹사이트 호스팅, 인덱스 문서 -> index.html
1-7-3) 변경 사항 저장

여기까지 했으면 S3설정은 끝났다.

심심하다면 index.html의 객체 URL로 접근해보면 접속됨을 확인할 수 있다.

 

이제 누구나 쉽게 접속할수있게 도메인을 설정해주는 것과 좀더 빠른 페이지를 위한 CloundFront 적용이 남았다.

아, 안전한 서비스 제공을 위한 SSL도 필수니까 당연히 진행하는거로


이전 포스팅에서 했던것처럼 ACM(Certificate Manager)을 통해 SSL 인증서를 생성해보자

 

2.  SSL 인증

2-1) ACM (Certificate Manager)에 접속
2-2) 인증서 요청 -> 공인 인증서 요청
2-3) 도메인 이름 사용하고자 하는거로 입력  (S3에 저장한 사이트를 운영하고자하는 사이트 도메인), (구매해둔 도메인은 있다는 가정)

2-4) DNS 검증 -> 태그 설정 (내가 식별을 위한거니까 영문으로 알아볼수 있게 쓰면됨)

2-5) 검토를 하고나서 생성된 정보를 기반으로 Route 53에 해당 CNAME을 등록해주면 됩니다.

 

 

SSL은 간단히 이정도만 해주면 끝입니다.

그러면 잠시 뒤 발급완료 처리가 됩니다.

 

다음 순서는 CloudFront입니다.

S3에서 파일을 제공하는것보다 빠를 뿐아니라, SSL을 연결해주는것도 쉽게 가능합니다.

 

3. CloudFront 설정하기 - 조금 번거로움

3-1) CloudFront 접속 

3-2) Create distribution

3-2-1) Origin Domain 검색 (로딩이 좀 걸립니다) ->  S3 버킷이 나타나면 클릭

3-2-2) Viewer protocol policy: Redirect HTTP to HTTPS

3-2-3) Custom SSL certificate - optional: 이전에 생성해둔거로 지정

3-2-4) Default root object: index.html

3-2-5) Create distribution
3-3) 생성됐다면, 도메인을 설정해줄건데요. 다시 Roue53에 해당 호스팅 영역으로 갑니다.

3-3-1) 레코드 생성 - "A 유형"

3-3-2) 도메인 이름 설정 (2-3과 일치하게)

3-3-3) 트래픽 라우트 대상 -> 별칭 active -> Cloud Front 배포에 대한 별칭 선택 -> 검색란에 CloudFront의 Distribution domain name 이름 검색 및 선택

3-3-4) 3-3-1 ~ 3-3-3의 작업을 한번 더 반복하여 "AAA 유형"도 생성

3-4) 거의 끝났습니다. 다시 Clound Front로 돌아갑니다.

3-4-1) 해당 객체를 Edit

3-4-2) Alternative domain name에 "Add Item" 하여 2-3에서 만든 도메인과 동일하게 입력해줍니다.
3-4-3) 그리고 저장하면 끝

 

자 이제 두근두근 하면서

해당 도메인에 https://로 접속해 봅니다.

 

잘되나요? 잘됐길바라며 여기서 마치겠습니다.

728x90
반응형
728x90

서브도메인은 정말로 유용하다

하나의 도메인을 여러 방면으로 사용할 수 있게해준다.


예를들어,

블로그용: blog.self-made.cloud

상품판매: shop.self-made.cloud
포트폴리오: portfolio.self-made.cloud

이런식으로 도메인별로 역할을 구분한다던지

혹은 서브도메인에 cname을 붙여서 다른 서비스와 연동한다던지
(지금 글쓴이의 도메인이 tistory와 연동된것처럼)

 

하는것처럼 말이다.

 

이렇게 사랑스러운 서브도메인을 적극 활용하기 위해서는

SSL 인증은 필수라고 볼 수 있다.

 

AWS에서는 ACM 서비스를 통해 쉽게 SSL인증서를 발급 및 적용할 수 있는데, 

한번 알아보도록 하자. (도메인을 하나 이미 구매했다는 전제)

 

1. AWS -> Route53

1-1) 호스팅 영역을 먼저 생성해보겠습니다.

1-2) Route53에 해당 도메인명과 내가 이해할수 있는 간략한 설명을 기록한뒤 생성합니다.

1-3) 생성하고나면 기본적으로 MX, SOA에 해당하는레코드들이 생성되어있습니다.

1-4) 도메인을 다른 업체에서 구매했다면 도메인 구매 업체 네임서버를 aws에 맞게 변경해주어야합니다.

(NS 타입으로 도메인 구매 업체의 네임서버값 레코드를 추가해주면 됩니다.

(*팁:  EC2 등의 서비스를 도메인과 연결하려면 여기서 설정 가능합니다. A레코드 - 서비스의 IP 쌍으로 레코드 생성)

 

2. AWS -> ACM (Certificate Manager)

2-1) 인증서 요청 -> 공인 인증서 요청
2-2) SSL인증을 받고자하는 도메인의 이름을 입력 (예를들어, 블로그 도메인을 생성하고자 한다면 blog.domain.com)
2-3) DNS 검증
2-4) Tag생성 -> 영문으로 해주세요. 내가 해당 인증서를 식별하기 위함이니 헷갈리지 않게 적어주세요

3. 인증서 추가인증 받기

3-1) SSL 인증서 요청으로 끝나는게 아니라 Route53에서 일을 마무리 해주어야합니다.

3-2) 다시 Route53으로 돌아와 인증서 생성후 표기된 CNAME의 이름과 값을 등록해줍니다.

3-3) 레코드 생성 -> CNAME

4. 마무리
4-1) 해당 서브도메인에 SSL설정은 완료되었습니다.
4-2) 잠시 기다린뒤 ACM에서 새로고침을 몇번 해보면 인증서 "발급 완료"를 확인해 볼 수 있습니다.

 

여기까지 하고 이제 해당 도메인과

연결하고자 하는 서비스를 연결해주기만 하면됩니다.

 

위에서 팁으로 안내했던 방법 등을 이용하면됩니다.

728x90
반응형