개발새발 박스/js

비동기 처리로 불러온 파일 내 script가 동작하지 않아요!

업폰 2023. 5. 2. 15:43
반응형

판하판하!

 

오랜만에 글을 작성하네요.

순서상, 발헤임을 작성해야하는 게 도리지만...

 

4월 초 이사 이후, 짐 정리가 좀처럼 되지 않아-

이 일도 저 일도 되지 않는 하루의 연속이었습니다.

 

방송도 해야되는데...

 

아무튼 때문에 밀린 외주 업무들을 처리하는 와중에 발생한 이슈 중 여러분께 도움될 만한 녀석이 있어 글을 작성합니다.

 


 

여러분도 가끔씩 경험해보셨을 겁니다.

 

스크립트가 정상적으로 동작하지 않는 것을...

 

대부분 오타 등의 단순 이슈일 경우가 많은데, 그렇지 않은 경우도 있죠.

프레임워크 내 작업 중 발생하는 보안상 이슈라던가...

 

이번에 설명할 javascript의 XMLHttpRequest / jquery의 ajax 등을 이용한 비동기 처리시 발생하는 이슈가 바로 그 경우입니다.

 

그럼 어떤 경우인지 예시를 들어 설명을 시작하죠. 아래를 보시지요.

 

<!--- file_1.php ------------------------------------------------------------>
<style>
	.div_test {display:none;}
</style>

<div id="div_test">
	테스트
</div>

<script>
    window.onload = function(){
        // div 를 출력
        document.getElementById( 'div_test' ).style.diplay = "block";
        // 확인을 위해 얼럿
        alert("test");
    }
</script>
<!-------------------------------------------------------------------------->
<!--- file_2.php ------------------------------------------------------------>
<div id="div_con">
</div>

<script>
	var con = document.getElementById( 'div_con' );
	window.onload = function(){
        var httpRequest = new XMLHttpRequest();
        // httpRequest의 readyState가 변화했을때 함수 실행
        httpRequest.onreadystatechange = () => {
            // readyState가 Done이고 응답 값이 200일 때, 받아온 response innerHTML로 처리
            if (httpRequest.readyState === XMLHttpRequest.DONE) {
                if (httpRequest.status === 200) {
                    con.innerHTML = httpRequest.responseText;
                } else {
                    alert('Request Error!');
                }
            }
        };
        // Get 방식으로 name 파라미터와 함께 요청
        httpRequest.open("GET", "/file_2.php");
        httpRequest.responseType = "text";
        // 정의된 서버에 요청을 전송
        httpRequest.send();
    }
</script>
<!-------------------------------------------------------------------------->

 

그냥 간략하게 file_2.php 에서 비동기 처리를 위해 div_con으로 innerHTML 처리하는 내용입니다.

그런데 file_1.php는 원래 onload 시 안보이던 div 가 보이게 되고, test 라는 오류 메세지를 나타내야 합니다.

 

이를 종합하자면, 이 코드의 의도한 바는?

> 페이지가 로딩완료되면, 새로운 페이지를 div_con 내로 불러오고, 오류 메세지를 출력하는 것입니다.

 

그런데, 인생은 그리 순탄치가 않습니다.

제 인생처럼 말이죠.

 

 

처음부터 말씀드렸던 것 처럼 비동기 처리로 innerHTML을 한 내용에 있는 script는 동작하지 않습니다.

 

이는 innerHTML이라는 함수의 문제로 자세한 내용은 아래를 확인하시면 알 수 있고, innerHTML이 code injection 문제로 <script> 라는 태그를 태크가 아니라 text로 인식하기 때문입니다.

 

 

HTML 5

If the document has an active parser<!--XXX xref-->, then stop that parser, and throw away any pending content in the input stream. what about if it doesn't, because it's either like a text/plain, or Atom, or PDF, or XHTML, or image document, or something?

www.w3.org

 

이를 해결하기 위해 다른 방법으로 textContent 를 소개하는 곳도 있는데 직접 해보시면 아시겠지만, 이는 의도한 결과와는 완전히 다른 raw text 를 출력하는 그야 말로 날 것의 방법입니다.

 

때문에 우리는 다른 방법을 찾아야하는데, 기본적으로 이를 해결하기 위해서는 가져온 HTML 코드 중 <script> 내 코드를 분해하여 별도로 다시 조립하여 입력하면됩니다.

 

왜냐하면, 사실 append 등의 데이터를 추가 입력하는 코드를 이용한다면, script를 정상적으로 동작시킬 수 있기 때문이죠. 편하게 처리하기 위해 사용 innerHTML 함수가 위와같은 특징을 가진 것일 뿐...!

 

가장 쉬운 방법은 그냥 jQuery 를 이용해 html() 처리하는 것이 좋겠지만, 저는 되도록이면 jQuery를 지양하기에 js로 처리할 수 있는 1안을 선택했고...

 

여러분도! 이를 해결하기 위해 아래의 함수를 사용하면 됩니다.

 

function nodeScriptReplace(node) {
        if ( nodeScriptIs(node) === true ) {
                node.parentNode.replaceChild( nodeScriptClone(node) , node );
        }else {
                var i = -1, children = node.childNodes;
                while ( ++i < children.length ) {
                      nodeScriptReplace( children[i] );
                }
        }

        return node;
}

function nodeScriptClone(node){
        var script  = document.createElement("script");
        script.text = node.innerHTML;

        var i = -1, attrs = node.attributes, attr;
        while ( ++i < attrs.length ) {                                    
              script.setAttribute( (attr = attrs[i]).name, attr.value );
        }
        return script;
}

function nodeScriptIs(node) {
        return node.tagName === 'SCRIPT';
}

 

위 스크립트는 앞서 설명한 내용을 script로 구현한 것으로 실제 사용시에는 nodeScriptReplace 만을 사용하면됩니다.

아래와 같이 말이지요.

 

<!--- file_2.php ------------------------------------------------------------>
<div id="div_con">
</div>

<script>
	var con = document.getElementById( 'div_con' );
	window.onload = function(){
        var httpRequest = new XMLHttpRequest();
        // httpRequest의 readyState가 변화했을때 함수 실행
        httpRequest.onreadystatechange = () => {
            // readyState가 Done이고 응답 값이 200일 때, 받아온 response innerHTML로 처리
            if (httpRequest.readyState === XMLHttpRequest.DONE) {
                if (httpRequest.status === 200) {
                    con.innerHTML = httpRequest.responseText;
                    
                    // script run
                    nodeScriptReplace(con); // <- 요것만 추가 됨
                } else {
                    alert('Request Error!');
                }
            }
        };
        // Get 방식으로 name 파라미터와 함께 요청
        httpRequest.open("GET", "/file_2.php");
        httpRequest.responseType = "text";
        // 정의된 서버에 요청을 전송
        httpRequest.send();
    }
</script>
<!-------------------------------------------------------------------------->

 

위와 같이 처리하시면, 처리완료와 함께 안내메세지도 div 의 display block 도 정상적으로 처리됩니다.

 


 

 

실제 사용시에는 보안적 취약점을 보완하여 사용하시면 될 것 같습니다.

무턱대고 모든 것을 가져오기 보다 특정 함수나 변수, 상수는 이용하지 않는 등의 예외 처리로 말이죠.

 

보다 자세한 내용은 아래 링크를 확인하여주시기 바랍니다.

 

 

Can scripts be inserted with innerHTML?

I tried to load some scripts into a page using innerHTML on a <div>. It appears that the script loads into the DOM, but it is never executed (at least in Firefox and Chrome). Is there a way t...

stackoverflow.com

 

 

항상 긴 글 읽어주셔서 감사합니다.

 

ps - 슬슬... 퍼블리싱을 건드리긴 해야할 것 같은데, 어디부터 건드려야하지 ㄷㄷㄷ

반응형