728x90

React를 공부하고 있는 중입니다.
React를 공부하려다 그동안 확바뀐 Node.js 까지 익히는 중입니다 ㅎㅎ
npm의 유틸리티중 하나인 npx를 이용해서 create-react-app 패키지를 설치하고 
이를 이용한 보일러 플레이팅을 하는 것이 보고 있는 책의 예제입니다.

그런데!

놀랍게도 (언제나 그렇듯이) 개발 환경 구성부터 산넘어 산입니다. 
오늘 만난 에러는 npx를 이용한 React 보일러 플레이팅 중 발생했습니다. 

% npx create-react-app test
npm ERR! cb.apply is not a function

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/nopd/.npm/_logs/2022-02-05T17_54_47_354Z-debug.log
[ 'create-react-app@latest' ] 설치가 오류 코드 1로 실패했습니다

뭔가 발음하기에도 거시기한 cb.apply가 함수가 아니라는 에러!
아버지를 아버지라 부르지 못하고... 함수를 함수라 부르지 못하는 것인가 싶었지만 차치하고...
몇 군데 검색을 해봐도 딱히 쓸만한 방법을 찾질 못했습니다. 

구글 검색에서 걸린 것들은 대부분, 
- node_module 관련 경로를 다 지우고 다시 해봐라 
- node 버전을 올려라 
- 캐시를 지워라 
정도였던 것으로 기억됩니다.
안타깝게도 전부 저한테는 쓸모가 없었습니다. 

뭐가 문제일까 하다가 발견한 것이 Mac 환경에서 brew로 설치한 패키지들과 
설치된 경로를 알긴 어렵지만, 여튼 설치되어 있는 node 관련 패키지들이
서로 다른 경로에 있지만 후자가 우선순위를 갖게 되면서 문제처럼 보였습니다. 

사실 create-react-app 을 설치하기 전에도 
node의 버전이 brew에서 확인되는 것과 다르네? 하면서
/usr 하위에 만들어져 있던 심링크를 삭제했던 기억이 뇌리를 스쳤습니다. 

혹시 npx도..!?!?!?!?!

정답이었습니다. 
brew로 최신 버전의 npx를 설치했지만, 
이는 which npx 로 확인했을 때의 경로와 차이가 있었습니다. 

// #################### BEFORE
% which npx
/usr/local/bin/npx

// #################### AFTER
% which npx
/opt/homebrew/bin/npx

그렇습니다.
brew는 독립적인 생명체라, /opt/homebrew/bin 하위에 패키지를 저장하고 있었습니다.
원래 /usr/local/bin 경로에 있던 npx를 과감하게 삭제하니 모든것이 정상으로 돌아왔습니다!

% npx create-react-app test
Need to install the following packages:
  create-react-app
Ok to proceed? (y)
npm WARN deprecated tar@2.2.2: This version of tar is no longer supported, and will not receive security updates. Please upgrade asap.

Creating a new React app in /Users/nopd/dev/clonecoding_practice/test.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...


added 1365 packages in 47s

169 packages are looking for funding
  run `npm fund` for details

Initialized a git repository.

Installing template dependencies using npm...
npm WARN deprecated source-map-resolve@0.6.0: See https://github.com/lydell/source-map-resolve#deprecated

added 33 packages in 2s

169 packages are looking for funding
  run `npm fund` for details
Removing template package using npm...


removed 1 package, and audited 1398 packages in 1s

169 packages are looking for funding
  run `npm fund` for details

8 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

Created git commit.

Success! Created test at /Users/nopd/dev/clonecoding_practice/test
Inside that directory, you can run several commands:

  npm start
    Starts the development server.

  npm run build
    Bundles the app into static files for production.

  npm test
    Starts the test runner.

  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd test
  npm start

Mac 환경에서 brew를 이용해서 node를 설치했고 
혹시나 pkg 를 다운로드받아 설치한 적이 있는 것 같은 기억이 있다면 
포스팅에 소개한 방법을 이용해 평안한 하루를 만드시기 바라겠습니다!

 

 

728x90
728x90

JSON을 다루는 것은 개발자에게는 숙명입니다. 그래도 SOAP 보다 편리하고 쉽다는게 어디냐며 위로해 보지만 할 때마다 새롭고 매번 처음 보는 것 같이 헤메는 것이 또한 JSON 다루기의 특징이기도 합니다.

터미널을 위시한 커맨드라인에서 jq를 이용하면 이런 번잡스러운 일을 간단하게 줄일 수 있습니다. 이미 많은 분들이 쓰고 있고 저 역시 쓰고 있지만 JSON을 다루는 만큼 매번 새롭기에... 하나씩 활용 방법을 찾아서 정리해보고자 합니다.


JSON은 단순히 어떤 요청에 대한 결과를 하나의 데이터 셋으로 내려주기도 하지만, 데이터 셋안에 여러개의 반복되는 데이터가 포함되어 있는 경우도 많습니다. 반복되는 JSON에서 원하는 속성의 값만을 뽑아내는 방법을 살펴보겠습니다. 

오늘의 데이터는 주택금융공사의
전세자금대출 고객 금리정보입니다.

 

시작부터 엄청 구미가 당기는 데이터이지 않습니까? 국가 공공데이터포털의 오픈API중에서 가장 먼저 눈에 띈 녀석으로 가져와 봤습니다. 여전히 SOAP만 제공하는 API도 많지만 많이 사용되는 데이터 셋은 JSON을 제공하는 경우가 많아 서비스를 개발하거나 연습할 때 무척 유용합니다. 

 

jq의 시작, jq '.' 사용하기

API의 자세한 스펙도 살펴보면 좋겠지만 우리의 목적은 흥미로운 JSON을 jq로 다뤄보는 연습을 하는 것이니 규격에 대한 설명은 생략하도록 하겠습니다. 요지는 JSON으로 동일한 속성을 가진 여러벌의 JSON 데이터가 나온다는 점입니다. 2021년 7월의 전세자금대출 정보를 쿼리해보니 은행별로 최저, 최대 금리가 나오고 대출 횟수가 같이 나옵니다. 

curl로 기본적인 GET 요청을 던졌고 돌아오는 응답을 파이프로 연결하여 jq '.' 로 넘겨보았습니다. 이렇게 하는 것 만으로 아래와 같이 두가지 극단적인 결과를 볼 수 있습니다. jq를 써보신 분들은 다 아시고, jq를 처음 쓴다면 일단 외워두는 것이 jq '.' 입니다. 

아.. 머리가 아프다...
단지 jq '.' 를 파이프로 연결했을 뿐인데...

 

// 머리아픈 JSON보기
curl -v "http://apis.data.go.kr/B551408/rent-loan-rate-multi-dimensional-info/dimensional-list?serviceKey=##공공데이터포털에서_키를받아_넣으세요!##&loanYm=202105&cbGrd=1&debt=11&numOfRows=5&pageNo=1&dataType=json"

// 속시원한 JSON보기
curl -v "http://apis.data.go.kr/B551408/rent-loan-rate-multi-dimensional-info/dimensional-list?serviceKey=##공공데이터포털에서_키를받아_넣으세요!##&loanYm=202105&cbGrd=1&debt=11&numOfRows=5&pageNo=1&dataType=json" | jq '.'

 

특정 아이템만 뽑아내보자

jq '.'를 사용해서 사람이 읽기 좋은 포맷을 쉽게 만들어 보았습니다. 하지만 데이터가 많다면 이 데이터를 한 번 더 필터링 해서 원하는 정보만 발라내서 보고 싶어지기 마련입니다. 

JSON의 구조를 잘 보니 최상위 속성으로 "header"와 "body"가 눈에 띕니다. "header"는 API 호출에 대한 처리 결과를 담고 있으니 우리에겐 중요하지 않습니다. 우리는 두번째 속성인 "body"의 내용에 관심이 있습니다. "body"의 하위 JSON만 뽑아내려면 어떻게 해야 할까요?

jq '.body'

// jq '.body' 로 파이프를 거세요!
curl -v "http://apis.data.go.kr/B551408/rent-loan-rate-multi-dimensional-info/dimensional-list?serviceKey=##공공데이터포털에서_키를받아_넣으세요!##&loanYm=202105&cbGrd=1&debt=11&numOfRows=5&pageNo=1&dataType=json" | jq '.body'

앞서 사용했던 jq 명령을 조금 더 진화시켜서 jq '.body'를 했더니 특정한 속성 하위의 JSON만 출력할 수 있었습니다. 참 쉽죠? 이쯤되면 조금 더 욕심이 나실겁니다. 잘 보니 "items"라는 배열 하위에 찐 정보들이 가득합니다. 과감하게 jq '.body.items'를 하면 원하는 값이 나오겠죠?

jq '.body.items'

 

반복되는 JSON에서 특정 속성만 뽑아내기

자 그런데 여전히 뭔가 번잡해 보입니다. 금융 서비스나 핀테크 서비스를 만든다면 사용자들에게 특정 은행의 전세자금대출 상품 소개를 하면서 최저금리를 안내해서 클릭을 유도하고 싶을 수 있습니다. 그렇다면 최소 금리를 나타내는 항목인 "minLoanRat"만 뽑아서 보면 좋을 것 같다는 생각이 듭니다. jq '.body.items.minLoanRat'을 하면 될 것 같죠?

jq '.body.items.minLoanRa' 은 에러입니다!!

하지만 결과는 제대로 나오지 않고 만나고 싶지 않았던 에러 메세지를 맞딱드렸습니다. 뭐가 문제일까요? 그것은 바로 앞선 jq '.body.items'의 결과가 배열이기 때문입니다. 배열은 인덱스라는 순서가 존재합니다. 이를 나타내기 위해서는 []를 써야 합니다. jq '.body.items[].minLoanRat'으로 명령을 바꿔서 시도해보겠습니다!

jq '.body.items[].minLoanRat'

결과가 잘 나왔습니다! 하지만 뭔가 아쉽습니다. 도대체 어느 은행에서 이 금액으로 대출을 해준건지 도통 알수가 없는 상태이기 때문이죠. 은행의 이름도 분명 원래의 JSON 데이터에 있었는데... 이걸 jq 로 함께 뽑아서 < "은행": #최소대출금리# >의 형태로 볼 수 있다면 얼마나 좋을까요?

 

반복되는 JSON을 조작하여 새로운 JSON 만들어내기

jq는 여러분을 위해 이미 그렇게 할 수 있는 방법을 준비해 두었습니다. jq는 curl과 같은 다른 명령으로부터 JSON 데이터를 파이프(|)로 전달 받을 수 있는 것은 물론이고, 자신이 스스로 데이터를 몇 번씩 가공하여 파이프로 연결해서 가공할 수 있습니다. 우리가 원하는 결과를 만들기 위해서는 아래와 같은 jq 연산을 해볼 수 있습니다.

jq '.body.items[] | { bankNm, avgLoanRat }'

jq '.body.items[] { bankNm, avgLoanRat }'

드디어 완성이 된 것 같습니다. 하지만 약간 더 손을 보면 다른 어플리케이션에서 데이터를 다루기 더 쉬워질 수 있습니다. 위 그림에서의 JSON은 JSON의 규격 위반으로 다른 프로그램에서 JSON으로 파싱할때 에러가 발생합니다. 각 항목이 콤마로 연결되어야 하고 배열이기 때문에 []로 묶일 필요가 있습니다. 

jq '[ .body.items[] | { bankNm, avgLoanRat } ]'

전체를 []로 묶어주니 자동으로 각 항목과 항목 사이를 콤마로 연결해 주어 완성된 JSON의 형태를 만들어 주었습니다. 이렇게 만들어진 JSON이 정말 문제 없는지 jsonlint.com 에서 검증을 해보았습니다. 네, 역시 문제 없네요!

 


이번 포스팅에서는 jq 를 이용하여 간단하게 데이터를 조작하는 방법을 살펴보았습니다. 다음 포스팅에서는 조건문을 활용하여 jq 를 보다 어렵게(?) 사용하는 방법을 살펴보도록 하겠습니다.

728x90
728x90

CORS (Cross Origin Resource Sharing) 은 서로 다른 도메인 간에 리소스를 활용할 필요가 있을때, 어떤 규칙으로 누구에게 허용할 것인지를 정의하는 HTTP 표준의 일부입니다. 모던 브라우저들은 CORS 헤더에 대한 지원을 충실히 하고 있습니다만, 서버측에서는 어떤식의 구현이 필요한지, 그리고 커스텀 클라이언트를 사용하는 경우에는 어떤 요청을 보내는 것이 적절한지에 대해 오해도 많고 시행착오도 많습니다.

CORS 에 대한 기본적인 정의를 먼저 살펴보고 CORS 구현을 위한 RFC 규격을 살펴보면서 어떤 요청 헤더와 응답이 필요한지 살펴봄으로써, 혹시나 CORS 에 대하여 시행착오를 겪는 분들이 적어지기를 바라며 포스팅을 시작해 보도록 하겠습니다. 


CORS 란 무엇일까?

웹이 태동하고 급성장하던 시기에는 서로 다른 리소스를 가져다 쓰는 것에 대하여 큰 제약이 없었습니다. 하지만 리소스를 사용하는 것은 분명 서버측의 비용이 발생하는 일이고, 보안 관점에서도 접근하는 사용자를 적절히 통제할 필요가 생겼습니다. 이런 요구사항을 수용하기 위하여 정의된 것이 CORS (Cross Origin Resource Sharing, 서로 다른 원본 서버간의 리소스 공유에 관한 규칙) 입니다. 

2009년경에 출시된 파이어폭스 Firefox 3.5 와 사파리 Safari 4.0 에서부터 강화된 동일 오리진 정책 SOP (Same Origin Policy) 이 적용되기 시작했고 현재 시장에서 사용되는 대부분의 브라우저는 이 정책을 준수하고 있습니다. 이 즈음부터 XHR (XML Http Request) 을 이용해 서로 다른 원본 서버에서 리소스를 <비동기> 로 가져다 쓰는 것에 대한 혼란(?)이 시작되었다고 봐도 무방합니다. 

근래의 브라우저들에서는 Fetch 도 제공하기 시작했고 Fetch 역시 XHR 과 마찬가지로 CORS 의 영향권 아래에 있습니다. 따라서 CORS 에 대한 정확한 이해를 바탕으로 구현을 해야 커머셜 브라우저는 물론이고 커스텀 클라이언트 개발에 대응할 때도 불필요한 시행착오를 줄일 수 있습니다. 

브라우저보다 조금 늦게 (늘 그렇듯) 2010년에 Drafting 된 CORS

 

다른 Origin 에서 리소스를 가져오는 두가지 방법

JSONP 는 논외로 두고 다른 Origin 에서 리소스를 가져오는 방법은 앞서 이야기 한 것처럼 XHR 을 이용한 방식와 Fetch 를 이용한 방식으로 나뉘어 집니다. 많은 자바스크립트 프레임웍 (jquery, axios...) 도 이들을 래핑하고 있기 때문에 동일하게 CORS 규칙의 영향을 받게 됩니다. 

비동기로 리소스를 가져오는 방식은 다른 관점에서 보면 단순 요청 Simple Request 와 예비 요청 Pre-flight Request 로 다시 나뉘어 집니다. 이 두가지의 차이점은 간단합니다. 메소드와 헤더에 관한 규격들이 더 있지만, 일단 크게 아래의 구분이 있다는 점을 인식하는 것이 중요합니다. 

구분 내용
단순 요청 Simple Request - GET, POST, HEAD 메소드로 하나의 요청에 필요한 CORS 요청 헤더를 포함하여 전송
예비 요청 Pre-flight Request - OPTIONS 메소드를 이용하여 본 요청에 대한 스펙을 CORS 요청 헤더를 포함하여 전송
- 성공 응답을 받은 경우 본 요청을 전송

 

CORS 의 기본, Origin 요청 헤더

앞서 설명한 두 요청의 상세한 차이점은 다음 포스팅에서 소개할까 합니다. 절단 신공이라기 보다는... 두 요청의 공통 요소 중 하나인 Origin 헤더의 규격에 대해서 살펴보고 가는 것이 더 중요하기 때문입니다. 다른 Origin 으로 리소스를 요청하는 경우, 원래의 요청이 어떤 도메인에서 시작된 것인지를 다른 Origin 으로 알려주어야 할 필요가 있습니다. 이 때 사용하는 것이 Origin 요청 헤더입니다. 

가령 https://a-server.nopd-genius.com 이라는 도메인에서 XHR 혹은 자바스크립트 프레임워크를 이용하여 https://b-server.nopd-not.com 이라는 도메인으로 요청을 보내는 경우를 생각해 보겠습니다. 자바스크립트 코드는 첫번째 서버(a-server)에서 사용자 브라우저로 전달해 주었기 때문에, 이 코드가 만든 두번째 서버(b-server)로의 요청은 `Origin: https://a-server.nopd-genius.com` 이라는 헤더를 포함해야 합니다. 

이 요청을 받은 두번째 서버(b-server)는 Origin 값에 해당하는 정책을 확인하여 이를 CORS 응답 헤더로 내려주게 됩니다. 이 과정에서 Origin 헤더 값이 사전에 약속된 원본 도메인이 아니라면 에러 응답을 하거나 CORS 헤더 없이 응답하게 되어 결과적으로 브라우저에서는 응답을 사용하지 못하는 상황이 되게 됩니다. 

출처 : 모질라 CORS 문서 

 

그런데 말입니다, 모질라의 CORS 문서에서 발췌한 위의 내용은 한가지가 잘못되어 있습니다. RFC 의 규격 문서에 따르면 Origin 헤더의 값은 반드시 스킴 Scheme (http 혹은 https) 을 포함해야만 합니다. 위의 그림처럼 `Origin: foo.example` 로 던지면 규격에 대한 위반이 되게 됩니다. 왜냐하면 http://foo.example 과 https://foo.example 은 서로 다른 Origin 으로 활용될 수 있기 때문입니다. WHATWG 의 규격 문서를 보면 이 헤더의 규격은 아래와 같습니다. 

특정한 포트를 지정해주는 ":port" 는 필요 없는 경우 생략해 줄 수 있지만, 스킴은 생략 가능한 항목으로 명기되어 있지 않습니다. 따라서 Origin 요청 헤더는 위의 규격을 준수하여 전송할 필요가 있습니다. 모질라의 문서 그림도 업데이트가 되어야 하겠죠? ^^ 이렇게 Origin 헤더가 중요합니다. 

다행히도 근래의 모던 브라우저들은 CORS 요청을 하는 경우 항상 스킴을 포함한 Origin 요청 헤더를 보내주고 있습니다. 따라서 Origin 헤더 값은 상용 브라우저가 아닌 클라이언트를 사용하는 경우에 유의해 주시면 큰 문제는 발생하지 않을 것으로 생각됩니다. 다음 포스팅에서는 Simple Request 와 Pre-flight Request 의 요건에 대하여 보다 자세히 살펴보도록 하겠습니다. 


참고자료

 

교차 출처 리소스 공유 (CORS)

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

 

Cross-Origin Resource Sharing

Must be deployable to IIS and Apache without requiring actions by the server administrator in a configuration where the user can upload static files, run serverside scripts (such as PHP, ASP, and CGI), control headers, and control authorization, but only d

www.w3.org

 

 

cross-site xmlhttprequest with CORS – Mozilla Hacks - the Web developer blog

Editor's Note: This article sure is a popular one! The Fetch API is now available in browsers and makes cross-origin requests easier than ever. Check out this Hacks post or ...

hacks.mozilla.org

 

 

Fetch Standard

 

fetch.spec.whatwg.org

 

728x90
728x90

이유는 알 수 없습니다만 여러곳에서 지속적으로 사용자 유입이 되고 있는 포스팅이 "JSON 포맷에서 주석을 사용할 수 있을까?" 라는 글입니다. 유입이 많은 이유로 생각되는 것은 많은 분들이 JSON 포맷을 사용하면서 주석 사용에 대한 욕망(?)이 있고, 이에 대한 방법을 찾으려다 검색 유입이 되는 것이라는 생각이 듭니다. 저 역시 글을 쓴 이유가 JSON 포맷에서 주석을 왜 쓸 수 없을까 였기 때문에 결국 같은 갈망을 가지고 검색후 실망-_-이라는 수순을 밟고 있는 안타까운 현실이라 하겠습니다.



요즘 읽고 있는 책 중 하나가 제이펍에서 출간한 "자바스크립트와 Node.js 를 이용한 웹 크롤링 테크닉"(책 내용 자세히 보기 [바로가기]) 이라는 책입니다. 곧 이직을 계획하고 있어서 여유 시간을 알차게 활용하고자 Node.js 와 파이썬(Python), 그리고 인프라스트럭쳐 배포 자동화에 심취해 있는데요, 여튼 이 책을 읽던 중 JSON5 에 대한 정보를 습득하게 되어 간단하게 공유해 보고자 합니다. 



JSON5 (http://json5.org) 는 JSON 이 가지고 있는 몇 가지 단점들을 ECMAScript 표준의 진화에 맞추어 쓸만한 형태로 개선하고자 하는 일종의 제안으로 시작된 프로젝트입니다. 시작된지는 좀 된 과제이지만 여전히 가야할 길을 열심히 걷고 있는 과제이기도 합니다. JSON5 는 JSON 의 규격을 조금 더 완화시키고 유연하게 만들어 다양한 데이터 포맷을 수용하고, 사람들에게도 더 친숙한 (= 주석으로 지저분해지는?) 형태로 만드는 것을 목적으로 하고 있습니다. JSON5 포맷으로 구성한 데이터 포맷의 예를 살펴보면 변화하는 부분을 쉽게 인지할 수 있습니다.



글의 시작에 이야기 했던 주석은 한줄 주석, 혹은 여러 줄로 구분된 주석 형태를 제공하고 있습니다. Key 를 표현할 때 꼭 사용해야 했던 따옴표 역시 제거되어 key 입력에 대한 불편함이 사라졌고, 파싱 에러의 대부분을 차지하고 있는 콤마에 대한 사용도 완화되어 어레이의마지막에 콤마가 들어가도 무방하도록 변경되었습니다. Value 에 멀티라인 텍스트가 들어갈 수 있는 것도 고무적인 부분입니다. 그 외에 Hex 형태의 표현 허용, 소수점 이하 표기법의 자유도 등도 눈에 띄는 부분입니다. Node.js 를 사용하는 경우 npm 을 통해서 json5 모듈을 쉽게 다운로드 받아 사용해 볼 수 있습니다.


$ npm install json5


모듈이 설치되면 Require 문으로 json5 모듈을 불러와야 한다는 점을 제외하면 parse 와 stringify 메소드를 이용해서 기존 JSON 내장 객체처럼 사용할 수 있습니다. 간단한 Node.js 예제코드는 아래와 같습니다.


var JSON5 = require('json5');

var fs = require('fs');

var json5 = fs.readFileSync("data.json5", "utf-8");


var obj = JSON5.parse(json5);


console.log(obj);

console.log(obj.multi_line);

console.log(obj.hex_data);

console.log(obj.items);


당연한 것이겠지만 JSON5 를 이용하여 데이터를 주고 받을 각 주체들은 JSON5 를 지원할 수 있도록 준비되어야 합니다. Node.js 이외의 다른 언어에서도 미리 준비된 패키지가 있을 것으로 생각됩니다만, 규격의 변경에 따른 사용 가능 여부를 확실히 점검하고 넘어가는 것이 좋을 것 같습니다. 주석을 이용할 수 있다는 것은 분명 의미있는 변화이지만 이로 인해 증가할 수 있는 데이터 파일의 크기, 새로운 처리 모듈의 사용 등은 고민을 해봐야 할 부분입니다.


/* 추천도서 */ API 설계 실무에 바로 사용하는 JSON [자세히 살펴보기]






728x90

+ Recent posts