본문 바로가기

JavaScript

[JavaScript] XMLHttpRequest 로 URL 호출해서 팝업창 띄우기

 

 

-  만들려는 기능 -

팝업을 띄우는 기능을 공통함수로 만들려고 한다.

함수에 url 만 파라미터로 넣으면 팝업이 뜨도록 한다.

XMLHttpRequest 을 이용한다.

파라미터로 넣어준 url 을 호출하면 page 를  던져준다.

받아온 page 안에서 팝업에 해당하는 특정 구역만 골라서 긁어온다.

새로 받아온 response 를 현재 document 안에 삽입한다.

 

 

- 초안 - 

function makeRequest() {
    const xhr = new XMLHttpRequest();
    const xhrUrl = "${pageContext.request.contextPath}/mypage";
    const callbackFunc = function(data) {
        // 조건문 안 넣으면 한 번에 3번씩 호출됨. 
        if(xhr.readyState === xhr.DONE) {
            if(xhr.status === 200 || xhr.status === 201) {
                // 문자열로 넘어오는 response 를 일단 DOM element 로 생성하기
                const divWrapper = document.createElement("div");
                divWrapper.innerHTML = data.currentTarget.response;
                // popup만 긁어와서 현재 body 에 붙여넣기
                const popupComplete = divWrapper.querySelector("div#popupCenter");
                document.body.append(popupComplete);
                // popup 에서 가져다 쓸 script 도 같이 붙여넣기
                const popupScript = divWrapper.querySelector("script#popupScript");
                const newScript = document.createElement("script");
                newScript.innerHTML = popupScript.innerHTML;
                document.body.appendChild(newScript);
            }
        }
    }
    xhr.onreadystatechange = callbackFunc;
    xhr.open("GET", xhrUrl);
    xhr.send("testparam=hellowrold");
}

 

 

- 문제점 -

> 아래 조건을 넣지 않으면 콜백 함수가 3번씩 호출되는 현상
    >> if(xhr.readyState === xhr.DONE) { } 
> response 로 받아오는 데이터에서 원하는 요소를 바로 꺼내오지 못함
    >> divWrapper 같은 껍데기를 임시로 만들어 두고 그 안에 response 를 넣었다가 divWrapper 에 querySelector 를 적용함.
> innerHTML 보안 이슈  
    >> sparrow 나 fortify 등 보안 프로그램에서 XSS 위험 항목 위반으로 사용 불가

 

 

- 해결 -

> 콜백 함수가 3번씩 실행됐던 이유는 onreadystatechange 에 콜백 함수를 걸었기 때문.
    >> 즉, XMLHttpRequest 를 호출하는 과정에서 ready state가 바뀔 때마다 콜백함수를 호출하고 있었음
    >> 콜백함수가 호출되는 시점을 XMLHttpRequest 가 모두 load된 이후로 지정함으로써 해결함
    >> 콜백 함수를 onload 에 걸어주었더니 XMLHttpRequest 가 모두 load 된 후 콜백 함수가 한 번만 실행됨.
    >> 콜백 함수를 걸어둘 수 있는 라이프 사이클 이벤트는 아래에 따로 정리해 둠
> response 로 받아오는 데이터에서 원하는 요소를 바로 꺼내오지 못했던 이유는 response 를 단순한 text 로 인식했기 때문.
    >> XMLHttpRequest 를 호출하고 받아온 reponse 는 데이터 타입이 text 로 지정되어 있었음.
    >> 따로 지정하지 않았는데, 그럴 경우에는 기본값으로 "text" 가 지정됨.
    >> 호출 결과로 받아오는 response 의 데이터 타입을 document 로 지정해줌으로써 해결함.
    >> 데이터 타입이 document 이다 보니 response 에 바로 querySelector 를 쓸 수 있었음.
    >> xhr.responseXML.querySelector("main.main-container");
    >> 데이터 타입으로 지정할 수 있는 값은 아래에 정리해 둠.
> innerHTML 보안 이슈가 걸렸던 문제는 innerHTML 를 사용하지 않는 방법뿐이었음.
    >> 우회해서 사용하거나 sanitizing, filtering 하는 방법 등을 시도해 봤으나 효과가 없었음.
    >> 아마 innerHTML 라는 실행문 자체를 인식하는 듯했음.
    >> innerHTML 를 대신하여 사용할 수 있는 기능을 찾아서 대체하는 방법뿐.
    >> innerText, textContent 등.

 

 

- 수정안 - 

function openPopupByUrl(popupUrl) {
    const xhr = new XMLHttpRequest();
    const callbackFunc = function(data) {
    	// 받아온 response 로부터 특정 구역 긁어오기
        const response = xhr.responseXML.querySelector("main.main-container");
        response.setAttribute("popup-id", "my-popup-container");

        // 현재 body 에 넣기
        document.body.append(response);
        
        // popup 에서 쓸 script 도 같이 붙여넣기
        const popupScriptList = response.querySelectorAll("script");
        popupScriptList.forEach((ele, idx, arr)=>{
            const tempScript = document.querySelector("script");
            tempScript.textContent = ele.textContent;
            if(ele.src) {
                tempScript.src = ele.src;
            }
            response.append(tempScript);
            ele.remove();
        });

        // 팝업 닫는 함수
        const funcClosePop = function() {
            document.querySelector("main[popup-id=my-popup-container]").remove();
        }

        // 닫기 버튼 클릭하면 팝업 삭제
        const closeBtnList = response.querySelectorAll("button.popup-close");
        funcAddEventToList(closeBtnList, funcClosePop);

    }
    xhr.onload = callbackFunc;
    xhr.open("GET", popupUrl);
    xhr.responseType = "document";
    xhr.send();
}

 

 

- XMLHttpRequest 지정할 수 있는 이벤트 -

// abort
// > 요청이 중단되었을 때 실행
const xhr = new XMLHttpRequest();
xhr.onabort = (event)=>{  }
xhr.addEventListener("abort", (event)=>{  });

// error
// > 요청에 오류가 발생했을 때 실행
const xhr = new XMLHttpRequest();
xhr.onerror = (event)=>{  }
xhr.addEventListener("error", (event)=>{  });

// load
// > XMLHTTPRequest 트랜잭션이 성공적으로 완료됐을 때 실행
const xhr = new XMLHttpRequest();
xhr.onload = (event)=>{  }
xhr.addEventListener("load", (event)=>{  });

// loadstart
// > 요청이 데이터를 로드하기 시작했을 때 실행
const xhr = new XMLHttpRequest();
xhr.onloadstart = (event)=>{  }
xhr.addEventListener("loadstart", (event)=>{  });

// loadend
// > 요청이 완료되면 실행. 요청에 성공하든 실패하든 실행됨
const xhr = new XMLHttpRequest();
xhr.onloadend = (event)=>{  }
xhr.addEventListener("loadend", (event)=>{  });

// progress
// > 요청이 데이터를 주고받을 때 주기적으로 실행
const xhr = new XMLHttpRequest();
xhr.onprogress = (event)=>{  }
xhr.addEventListener("progress", (event)=>{  });

// readystatechange
// > readyState 속성이 변경될 때마다 실행
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = (event)=>{  }
xhr.addEventListener("readystatechange", (event)=>{  });

// timeout
// > 사전 설정된 시간 만료로 인해 진행이 종료되면 실행
const xhr = new XMLHttpRequest();
xhr.ontimeout = (event)=>{  }
xhr.addEventListener("timeout", (event)=>{  });

 

 

- XMLHttpRequest 지정할 수 있는 response type -

// ""
// 공백으로 넣으면 기본값 "text 와 같음
const xhr = new XMLHttpRequest();
xhr.responseType = "";

// "arraybuffer"
// 이진 데이터를 포함하는 JavaScript ArrayBuffer
const xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";

// "blob"
// 이진 데이터를 포함하는 Blob 객체
const xhr = new XMLHttpRequest();
xhr.responseType = "blob";

// "document"
// HTML Document 또는 XML XMLDocument
const xhr = new XMLHttpRequest();
xhr.responseType = "document";

// "json"
// JSON 구문으로 만든 JavaScript 객체
const xhr = new XMLHttpRequest();
xhr.responseType = "json";

// "text"
// 텍스트 문자열
const xhr = new XMLHttpRequest();
xhr.responseType = "text";

 

- 241202

반응형