판하판하!
오랜만에 글을 작성하네요.
순서상, 발헤임을 작성해야하는 게 도리지만...
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 - 슬슬... 퍼블리싱을 건드리긴 해야할 것 같은데, 어디부터 건드려야하지 ㄷㄷㄷ