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
반응형
728x90

서비스를 운영하다보면 아무리 이미지 압축을 잘 하더라도,

이미지 자체가 워낙 큰 파일이라 로드에 시간을 길게 소요하는 경우가 발생합니다.

 

이런 경우를 대비해 이미지 로드에 부가적인 처리를 좀 해주면

멋스럽고 좋은 사용자 경험을 제공할 수 있습니다.

 

간단한 js와 css를 함께 응용해야하는데,

예제 코드들로 살펴보도록 하겠습니다.

 

[ 주요 키워드: classList, Image ]

 

1. 기본 핵심코드

1.1 html

<div class='image-wrapper'>
  <img class='image-thumbnail' src='/no-image.png' alt='이미지' />
</div>

1.2 css

.image-wrapper {
  background-color: #dadada;
  
  &.visible {
    background-color: transparent;
    transition: all 0.25s;
  }
  
  &.visible > .image-thumbnail {
    transform: scale(1);
    opacity: 1;
  }
}

.image-thumbnail {
  transform: scale(0.5);
  opacity: 0;
  
  width: 100%;
  height: auto;
  object-fit: cover;
}

1.3 js

<script>
  const source = "https://t1.daumcdn.net/tistory_admin/static/top/pc/img_common_tistory_200910.png";
  const image = new Image();
  image.setAttribute('src', source);
  image.onload = function() {
    const wrapper_node = document.querySelector('image-wrapper');
    const node = document.querySelector('image-thumbnail');
    
    wrapper_node.classList.add('visible')
    node.src = source;
  }
</script>

 

 

자, 이렇게하면 이미지가 로드된 후에 고급진 이펙트와 함께 이미지가 나타나게됩니다.

가능하다면 no image상태일때 이미지를 설정해줘도 좋겠죠?

 

 

하지만, 이렇게 단일 이미지를 로드할때보다는

여러 이미지를 로드할때 이것은 더욱 빛을 바랍니다.

예를들어,  Youtube의 영상 목록을 불러올때나 와디즈에서 펀딩 목록을 불러올때

image가 로드되는동안 기본 이미지(혹은 배경색)을 보여주다가

이미지가 로드되면 약간의 효과와 함께 이미지를 띄워줍니다.

 

 

이번에는 React 예제로 살펴보겠습니다.

예시의 편의성을 위해 데이터는 어딘가 서버로부터 불러왔다고 가정하겠습니다.

그리고 실제 개발에서 쓸수있도록, 페이지 스크롤에 따라 이미지 로드와 효과 적용을 핸들링 해보도록 하겠습니다.

 

const ImageList = ({ images ) => {
  return (
    <>
    {
      images.map((img, index) => {
        const image = new Image();
        image.load = function() {
          // how...?
        }
        return (
          <div key={"image-item-" + index.toString()} className='image-wrapper>
            <img className='image-thumbnail' alt='썸네일' />
          </div>
        );
      })
    }
    </>
  )
}

 

이전과 비슷한데  React라는 점, 그리고 단일 이미지가 아닌 list라는 부분에서 차이가 발생했습니다.

여기서 어떻게 각기 알맞는 이미지 태그에 이미지를 넣어줄 수 있을까요?

 

여러가지 방법이 있을 수 있지만,

여기서는 React hooks의 useRef를 사용해보도록 하겠습니다.

(최근에, hooks를 좀 익혀보는 중이거든요)

 

[ 주요 키워드: classList, Image, useRef, useEffect ]

 

<script>
import React, { useRef, useEffect } from 'react';

const ImageList = ({ images ) => {
  return (
    <>
    {
      images.map((img, index) => {
        const image_card = useRef(null);
        
        useEffect(() => {
          scorller();
          window.addEventListener('scroll', scroller, false);
          
          return window.removeEventListener('scoll', scroller, false);
        });
        
        const scroller = () => {
          const { classList: classes, offsetTop, clientHeight } = image_card.current;
          
          // 이미 visible 처리된 element는 중복작업 방지 - 쓸데없는 메모리낭비x
          if (classes.contains('visible')) return null;
          
          // 화면 스크롤 높이에따라 element가 노출되고 있는지를 체크
          const visible_timing = window.innerHeight + windwo.scrollY >= offsetTop - (clientHeight / 2);
          
          // scroll 위치까지 고려해서 이미지 로드 처리
          if (visible_timing) {
            const image = new Image();
            image.setAttribute('src', img);
            image.onload = function() {
              const node = image_card.current.querySelector('.image-thumbnail');
              if (node) {
                classes.add('visible');
                node.src = img;
              }
            }
          }
        }
        
        return (
          <div 
            key={'image-item-' + index.toString()} 
            className='image-wrapper'
            ref={image_card}
          >
            <img className='image-thumbnail' alt='썸네일' />
          </div>
        );
      })
    }
    </>
  )
}
</script>

 

이 처럼 React로 이미지 목록을 구현하는 방법을 살펴보았습니다.

어떤가요?

코드가 어렵다거나 하지는 않죠?

생각보다 간단하게 괜찮은 효과를 낼수 있음을 알 수 있습니다.

 

좀 더 나아간다면 ImageCard를 모듈화 하면 더 좋겠죠?

 

 

728x90
반응형
728x90

transtion 효과를 쓰다보면 의도치 않게

주변 컴포넌트가 깜박이게되는 현상을 맞이할때가 있다.

 

transition을 먹여둔 컴포넌트에 아래의 css 한줄만 추가하면 이 문제를 해결할 수 있다.

-webkit-backface-visibility: hidden;

 

728x90
반응형

'개발, 코딩 > CSS' 카테고리의 다른 글

Bootstrap 기본 (Grid, Container, Column, Spacing)  (0) 2022.10.14
반응형 작업 돌아보기 (+ flex)  (1) 2022.09.27
jQuery - easescroll, chrome issue  (0) 2022.02.17
HTML, div를 input으로 쓰기  (0) 2021.11.16
viewport에 대하여  (1) 2021.01.21
728x90

자바스크립트를 쓰는 개발자라면 매일 같이 마주하는

Object타입

이놈, 유용하지만 조심해서 써야한다.

 

코드를 쓰다보면 객체를 '복사'하게 되는 경우가 많은데 예를들면 아래와같다

const wallet = {
  card: 3,
  money: 0
};

const friend1_wallet = wallet;
const friend2_wallet = wallet;

console.log(wallet) // { card: 3, money: 0 }
console.log(friend1_wallet)  // { card: 3, money: 0 }
console.log(friend2_wallet)  // { card: 3, money: 0 }

 

자, 너무 나도 당연한 결과이다 그렇다면 이번엔 아래의 결과를 예상해보자

wallet.card = 4;

console.log(wallet);
console.log(friend1_wallet);
console.log(friend2_wallet);

 

아래의 결과가 정확히 예상되는가?

console.log(wallet); // { card: 4, money: 0 }
console.log(friend1_wallet); // { card: 4, money: 0 }
console.log(friend2_wallet); // { card: 4, money: 0 }

 

왜, 이런일이 생길까?

c와 같은 언어를 배우고 사용해본 개발자라면 메모리 참조에 대해서 알것이다.

const friend1_wallet = wallet; // 복사가 아닌 참조

 

friend1_wallet은 wallet이 가리키는 메모리 영역을 똑같이 바라보게 했을 뿐이기 때문에,

wallet을 바꾸면 당연하게도 firend1_wallet의 값이 바뀌게 되는것이다.

 

그렇다면, 

"지갑을 똑같이 복사한 다음 복사한 지갑에만 카드를 두개 더 넣어두고싶어요~"

라고 했을때 어떻게 처리를 해야할까?

 

우리의 javacript는 이를 위해 Object.assign() 메소드를 제공한다.

const wallet = {
  card: 3,
  money: 0
};

const friend1_wallet = Object.assign({}, wallet);
const friend2_wallet = Object.assign({}, wallet);

console.log(wallet) // { card: 3, money: 0 }
console.log(friend1_wallet)  // { card: 3, money: 0 }
console.log(friend2_wallet)  // { card: 3, money: 0 }

이렇게 하면 우리는 빈 객체에 wallet을 복사하게 된다.

 

따라서, 아래와 같이 실했했을때 결과를 예상해보자.

wallet.card = 6;

console.log(wallet);
console.log(freind1_wallet)
console.log(freind2_wallet)

 

결과를 예상해봤는가?

console.log(wallet); // { card: 6, money: 0 }
console.log(freind1_wallet); // { card: 3, money: 0 }
console.log(freind2_wallet); // { card: 3, money: 0 }

 

자, 이제 목적을 달성했다.

그런데...

"저는 지갑에 어머니가 맡기신 돈도 보관해야되요. 돈을 두가지로 분류해서 저장해주세요!" 라고 한다면

const wallet = {
  card: 3,
  money: {
    mother: 5000,
    mine: 100,
  },
};

const freind1_wallet = Object.assign({}, wallet);
const freind2_wallet = Object.assign({}, wallet);

console.log(wallet); // { card: 3, { mother: 5000, mine: 100 } }
console.log(freind1_wallet); // { card: 3, { mother: 5000, mine: 100 } }
console.log(freind2_wallet); // { card: 3, { mother: 5000, mine: 100 } }

 

자... 이런 상황에서 엄마가 1000원을 더 맡기셔서 wallet에 돈을 추가하는 상황이 되었다.

아래 상황의 결과를 예측해보자

wallet.money.mother += 3000;

console.log(wallet);
console.log(friend1_wallet);
console.log(friend2_wallet);

 

결과는 아래와 같다.

console.log(wallet); // { card: 3, money: { mother: 8000, mine: 100 } }
console.log(friend1_wallet); // { card: 3, money: { mother: 8000, mine: 100 } }
console.log(friend2_wallet); // { card: 3, money: { mother: 8000, mine: 100 } }

 

어라! 복사하면 참조 문제는 발생하지 않는것 아니었나요?

자, 여기서 이제 "얕은 복사" 라는 개념이 나온다.

Object.assign()은 얕은 복사를 해주는 메소드로 1Depth 까지만 그 특성을 유지해준다.

 

전개연산자도 이와 마찬가지다.

// 전개연산자 얕은복사 예시
const wallet = {
  card: 3,
  money: 0
};

const freind1_wallet = { ...wallet };

console.log(wallet); // { card: 3, money: 0 }
console.log(freind1_wallet); // { card: 3, money: 0 }

wallet.card = 4;

console.log(wallet); // { card: 4, money: 0 }
console.log(freind1_wallet); // { card: 3, money: 0 }


// 전개연산자 깊은복사...
const wallet = {
  card: 3,
  money: {
    mother: 5000,
    mine: 100
  }
};

const freind1_wallet = { ...wallet };

console.log(wallet); // { card: 3, money: { mother: 5000, mine: 100 } }
console.log(freind1_wallet); // { card: 3, money: { mother: 5000, mine: 100 } }

wallet.money.mother += 3000;

console.log(wallet); // { card: 3, money: { mother: 8000, mine: 100 } }
console.log(freind1_wallet); // { card: 3, money: { mother: 8000, mine: 100 } }

 

이런 문제를 해결하려면 어떻게해야 할까?

늘 그렇듯 방법이 있다.

1. 재귀함수를 통한 복사

2. JSON 메소드 응용하기

3. lodash등 라이브러리르 이용하기

 

1. 재귀함수를 통한 복사는 빈 객체를 생성하고 for...in 함수로 객체의 key,value를 빈 객체에 집어넣는 방식이다.

프로젝트 내에 deepCopy함수를 하나 생성해두고 두고두고 쓰면 제법 유용하다.

function deepCopy(obj) {
  const newObj = {};

  for (let key in obj) {
    if (typeof obj[key] === 'object') {
      newObj[key] = deepCopy(obj[key]);
    } else {
      newObj[key] = obj[key];
    }
  }

  return newObj;
}

 

2. JSON 메서드를 응용하는 방법은 간편하지만 퍼포먼스 적으로 좋지않다고 한다. 또한, File 타입 복사간 문제가 발생하니 사용을 지양하는게 좋을것으로 생각된다.

const obj = {
  a: 1,
  b: {
    b_1: 2,
    b_2: 3,
  }
}

const copiedObj = JSON.parse(JSON.stringify(obj));

 

3.  lodash 등 라이브러리 사용하기는 쉽고 강력하므로 개인적으로 이 방식을 권장하는바이다.

import _ from 'lodash';

const obj = {
  a: 1,
  b: {
    b_1: 2,
    b_2: 3,
  }
}

const copiedObj = _.cloneDeep(obj);
728x90
반응형
728x90

nodejs에서 pdfjs-dist 라이브러리를 사용하기위해 canvas가 필요했다.

(pdfjs가 canvas에 의존적이기 때문)

 

그런데 linux기반 서버에서 canvas설치 후 실행시 다음과 같은 에러가났다.

Cannot find module '../build/Release/canvas'

 

한참을 헤맸다.

node-gyp rebuild니 permission 에러니...

 

정답은 늘 가까이에 있고, 많이 사용하는 라이브러리는 늘 답안이 나와있다. (감사해요 stackoverflow)

I just use "npm uninstall canvas" and then install using "npm i canvas"

 

이 단 한줄의 문장으 내 머리를 후려쳤다.

생각해보니 canvas에서 지시한 지시사항을 실행하기 전에 canavs를 먼저 설치한것이다.

 

sudo apt-get install build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev

 

위는 canvas에서 ubuntu환경일 경우 먼저 설치하라고 안내해준 library들이다.

 

나는 이미 진행했으므로

npm uninstall canvas
npm install canvas

 

canvas를 다시 설치해주는것만으로 해결되었다.

 

pdfjs는 pdf to png작업을 위해 사용하였는데, 곧 관련내용을 정리해볼 예정이다.

728x90
반응형
728x90

브라우저 캐시는 사이트의 속도나 보안에 큰 영향을 미친다.

과거 캐시에 데이터를 때려박는 형태에서는 브라우저 캐시만 탈취하면 사용자 정보를 취득할수도 있었다.

현대에는 간단한 정보는 browser의 localStorage/sessionStorage를 이용해 처리한다.

 

유저 기반 사이트라면 반드시 들어가는 기본이자 가장 중요한 로그인/로그아웃

위 주제는 파도파도 끝이없다.

 

Node.js로 서버 개발을 하는 나는 cache를 이용해서 사용자 정보를 주고받는다.

이때, javascript로는 캐시를 읽어볼수 없게하는 httpOnly옵션과

https에서만 캐시를 캐치가능하게하는 secure옵션을 사용해 보안성을 높인다.

 

사용자 데이터는 JWT를 이용해 토큰화 해서 캐시에 담아 주고받는다.

통신 패킷 자체가 탈취당하더라도 암호화된 데이터는 비밀키가 탈취당하지 않는한 안전하다.

 

그런데, 아주 기본 상식적인 부분에서 문제를 캐치하지 못해 오늘 고생을 하였다.

요점은 이것이다.

naver.com에 로그인하면 m.naver.com  / blog.naver.com / finance.naver.com 등

naver.com의 서브도메인에 해당하는 사이트들에 재 로그인할 필요없이 활동이 가능하다.

그 방법은 브라우저 캐시에 있다. 네트워크 쿠키탭을 뜯어보면 NIDSES (domain: .naver.com)가 포함되어있는것을 알수있다. 각 쿠키가 어떤역할을 하는지 상세한 내용은 알수없으나 분명 사용자 데이터를 포함하고 있고 해당 데이터는 브라우저에 저장된다.

그렇기 때문에 하위 도메인에서도 계정상태를 유지할수 있는것이다.

 

이 부분을 잊어버린 나는 문제아닌 문제와 힘겨운 싸움을 했다.

회사 메인 도메인 사이트에서 로그아웃을 했더니, 서브 도메인 사이트에서도 자꾸만 로그아웃 되는게 아닌가?

이거는 곤란한대?! 하며 속앓이를 했다.

 

어떻게 "잘" 해결할수있는 방법은 없을까? 하고 고민도 해보고 여러가지 시도도 해보았다.

보안이 중요하지 않은 사이트에서는 꼼수는 있지만, 정상적인 사이트에서라면 해당 이슈를 거스리지않는게 맞다는 결론을 내렸다.

728x90
반응형
728x90

검색의 기본, 여러 데이터 중에 특정 대상만 불러오는것

기본이자 가장 어려운 파트라고도 할 수 있다.

 

특히, 보기좋은 코드가 개발자를 기분좋게하는데 검색쿼리를 짜다보면 자칫 코드가 길어질수있다.

여러가지 조건들이 붙기 때문이다.

 

그와중이 addFields를 남발하여  길어져버린 나의 코드가 눈에띄었다.

어디에 주로 addFields를 썼나 보니,

$lookup을 통해 다른 collection과 join한뒤 특정 document만 불러오고자 할때 리스트를 벗겨내는 과정에서 남용했다.

 

{
  $lookup: {
    from: 'users',
    localField: 'userId',
    foreginField: '_id',
    as: 'user'
  }
}

// 결과 => [{ _id: ObjectId('...'), name: '...', ... }]
// 원하는 데이터는 => { _id: ObjectId('...'), name: '...', ... }
// 따라서, 사용했던 코드

{
  $lookup: {
    from: 'users',
    localField: 'userId',
    foreginField: '_id',
    as: 'user'
  }
},
{
  $addFields: {
    user: {
      $arrayElemAt: ['$user', 0]
    }
  }
}

// 결과 => { _id: ObjectId('...'), name: '...', ... }

 

원하는 document만 불러오기 위해 코드가 무려 7줄이나 길어지는 문제가 발생했다.

$addFields는 이런식으로 쓰라고 있는 기능도 아닐것으로 생각이 든다.

$addFields는 저장된 데이터는 아니지만 클라이언트에 필요한 데이터, 예를들어 sum, count 등의 데이터들을 추가하기 위해 존재할것이다.

 

그렇다면, 무엇이 적절할까. 정답은 이미 나와있다.

 

{
  $lookup: {
    from: 'users',
    localField: 'userId',
    foreignField: '_id',
    as: 'user'
  }
},
{
  $project: {
    ...,
    user: { $arrayElemAt: ['$user', 0] }
  }
}

 

7줄의 코드가 아닌 단 한줄의 코드면 충분하다.

 

728x90
반응형