웹을 통한 비 동기 호출의 두 번째 시간이 되겠습니다. 전 강좌에서는 전반적인 비 동기 호출의 의미와 고전적인 방법에 대해 소개를 드렸었는데요. 이번에는 그에 이어 조금 더 개선된(게다가, 현재도 상당히 쓸만한) 방법을 소개시켜 드리려 하는 것이죠.

이 방법은 현재 다양한 형태로 변형되어 이용하고 있는 방법이기도 합니다. 즉, 외적인 형태만 조금씩 다를 뿐, 대부분의 비 동기 호출이 내부적으로는 이번 강좌의 방법을 사용하고 있다는 의미죠. 그게 도대체 뭔데 이렇게 뜸을 들이냐구요?

뜸! 뜸이 나왔으니 드리는 말씀인데 말입니다. 뜸! 함부로 잘 못 뜨면, 상당히 당혹스러워집니다. 사실, 요즘 들어 제 강좌에 개그가 뜸하여 불만이 있으신 분들 많은데요. 말 나온 김에 [뜸] 이야기 잠시 드리고 가도록 하겠습니다. (참고, 다소 혐오(?)스러울 수 있으니 15세 이하는 보호자의 선 감상 후, 지도하에 읽어주시기 바랍니다. ㅋㅋ)

믿거나 말거나, 태오는 유전적인 영향으로 인해서 가슴에 모(毛)들이 좀 많습니다. 그것도 듬성, 듬성이 아니라 아주 섹시하고 넉넉하게 고루고루 분포되어 있지요. 좋게 말하면, 섹시한 거고 슬프게 말하면 진화가 덜 된 것인데요. 외가 쪽의 영향으로 가슴에 모들이 아주 광활하게 펼쳐져 있습니다. 그래서 남들보다 샴푸나 린스를 1.5배 정도 소비합니다만… -_-

예전에 제가 LA에 갔을 때 교통사고 후유증으로 허리가 안 좋았던 시기가 있었습니다(참고! 태오 스토리 1). 대부분 그러한 증상은 몇 년을 걸쳐서 안 좋은 편인데요. 몇 년이 지난 어느 날 뉴욕에 있는 외가를 잠시 방문했을 당시, 외할머니께서 용한 한의원이 있다고 그 곳에 한번 가자고 하셔서 따라갔었거든요. 근데, 바로 거기서 [뜸]을 추천하더라구요. 허리가 안 좋을 때는 뜸을 뜨는 게 최고라는 신빙성 있어 보이는 처방을 내리시더군요. 음.. 과연??? 어쨌든, [뜸]을 뜨는 게 뭐가 대수겠습니까마는 문제는 부위였습니다. 뜸을 떠야 하는 부위가 바로 배꼽이었거든요. 근데, 제 모들은 가슴 위부터 단전까지 직선의 코스를 내리 달리는 와중에, 그 주변으로도 광역적이기에… 배꼽 주의도 이미 모들이 장악한 지 오래되었죠. 요는 뜸을 뜨기 위해서는 배꼽 주위의 모들을 정리해야 한다는 것입니다. 뜸을 위해서, 배꼽 주위 직경 5cm 정도 원형 지역을 금모 지역으로 만들어야만 했다는 것입니다.

중요한 것은 그 때가 그러니까 7월… 즉, 바캉스 시즌을 코앞에 둔 시기였다는 것이죠. 가슴의 모들이 섹시하려면 듬성듬성은 Oh~ No 입니다. 그런데, 배꼽 주위 반경 5cm의 강제적인 원형 삭발이라니… 젊디 젊은 20세 중반의 태오. 그 해의 바캉스는 그렇게 그렇게 바닷물 한번 만나보지 못했던 것입니다. 믿거나 말거나, 그 해의 제 친구들은 제가 바다보다 산을 좋아한다고 믿었고, 저는 인자요산 지자요수를 외치면서, 스스로를 인자라 호칭하였다는… 슬픈 전설이 있습니다.

"도대체~ 뜸이랑 비 동기 호출과 무슨 상관이냐?"고 물으시는 것은 아니겠죠? 물론, 아무런 상관이 없어요~ ㅋㅋ 자꾸 제 몸을 떠올리지 마십시오. 서로의 정신 건강에 좋을 게 없지 않겠습니까?? 자. 그럼 이제 본문으로 돌아와 보도록 하겠습니다.

비 동기 호출의 두 번째 방법은 XMLHTTP라는 COM 컴포넌트를 이용하는 것입니다. 이는 클라이언트 스크립트인 자바스크립트 안에서도 이용할 수 있기에 매우 유용합니다. XMLHTTP 개체를 사용하게 되면, 일반적인 페이지의 요청/응답 외에도 별도의 요청 및 응답을 처리할 수 있습니다. 즉, 비 동기 호출이 요구하는 별도의 요청/응답 채널을 확보할 수 있다는 의미가 되겠죠. 초심자들에게는 어려운 말일 수도 있지만 말입니다. 일단 그림으로 확인해 보시죠~

보시다시피, XMLHTTP 개체는 자바스크립트 안에서 개체의 인스턴스를 만들어서 이용이 가능하기에, 서버 측의 페이지가 무슨 페이지이든지는 중요하지 않습니다. 즉, 이 방식도 서버 페이지가 ASP이던, PHP이던, JSP이던, .NET 페이지이던 전혀 개의치 않는다는 것이죠. 어떤 동적 페이지이던 간에 데이터 처리 결과로 출력되는 문자열이나 XML 데이터만 받아오면 되기 때문입니다. XMLHTTP는 그러한 결과를 비 동기적으로 수신 받아오는 역할을 하고, 클라이언트 스크립트는 그 반환받은 데이터를 문자열 파싱(parsing)하여 필요한 데이터를 동적으로 웹 페이지에 반영하면 되는 것입니다. 그렇다면, 샘플을 한번 같이 살펴보실까요?

샘플은 각각 클라이언트 페이지와 서버 페이지로 구분해서 코드를 보여드리겠습니다. 클라이언트 페이지는 굳이 동적 페이지일 필요가 없으니 htm 페이지로 만들고요. 서버 페이지는 단순히 서버에서 데이터를 얻어오기 위해 필요한 페이지이니 ASP, JSP 뭐로 만들어도 무관하지만, 사이트가 사이트이니만큼 ASP.NET 페이지로 만들어 보도록 하겠습니다. ASP.NET 페이지이긴 하지만, UI는 전혀 없는 페이지로 말입니다(그런 이유로, aspx 페이지의 소스는 보여드릴 필요가 없고요. Aspx의 코드 비하인드만 보여드리면 될 것입니다).

Client.htm

<html>
    <head>
    <Script language="JavaScript">
        function GetInfo()
        {
            var empID = document.all.EmpID.value;
            var pageUrl = "Process.aspx?param=" + empID;

            var xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
            xmlRequest.Open("POST", pageUrl, true);
            xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            xmlRequest.onreadystatechange = function() {CallBack(xmlRequest)};
            xmlRequest.Send(null);

            return xmlRequest;
        }

        function CallBack(xmlRequest)
        {
            if (xmlRequest == null || xmlRequest.readyState != 4) return;
            if(xmlRequest.responseText.length == 0) return;

            var vals = xmlRequest.responseText.split("\t");

            document.all.LastName.value = vals[0];
            document.all.FirstName.value = vals[1];
            document.all.Title.value = vals[2];
            document.all.BirthDate.value = vals[3];
            document.all.City.value = vals[4];
        }

    </Script>
    <LINK href="../Styles.css" type="text/css" rel="stylesheet">
    </head>
    <body>
    <p>
        <table cellpadding="3">
            <tr>
                <td align="center">사번</td>
                <td><input id="EmpID" NAME="EmpID" onchange="GetInfo();" ></td>
            </tr>
            <tr height="1" bgcolor="silver">
                <td colspan="2"></td>
            </tr>
            <tr>
                <td align="center" width="80">이름</td>
                <td width="200"><input id="LastName" name="LastName"></td>
            </tr>
            <tr>
                <td align="center">성</td>
                <td><input id="FirstName" name="FirstName"></td>
            </tr>
            <tr>
                <td align="center">직급</td>
                <td><input id="Title" name="Title"></td>
            </tr>
            <tr>
                <td align="center">생일</td>
                <td><input id="BirthDate" name="BirthDate"></td>
            </tr>
            <tr>
                <td align="center">도시</td>
                <td><input id="City" name="City"></td>
            </tr>
        </table>
    </p>
    <p>Microsoft.XMLHTTP 컴포넌트를 사용한 예입니다 (IE 5.0 이상)</a></p>
    </body>
</html>

Process.aspx.cs

public class Process : System.Web.UI.Page
{
    private void Page_Load(object sender, System.EventArgs e)
    {
        string param = "";
        if(Request.Params["Param"] != null)
            param = Request.Params["Param"];
        else
        return;

        string info = string.Empty;
        string strCon = "server=(local);database=Northwind;uid=sa;pwd=**";
        string sql = " SELECT LastName, FirstName, Title, BirthDate, City FROM Employees "
                    + " WHERE EmployeeID = @empID";

        SqlConnection con = new SqlConnection(strCon);
        SqlCommand cmd = new SqlCommand(sql, con);
        cmd.Parameters.Add("@empID", SqlDbType.Int);
        cmd.Parameters["@empID"].Value = param;

        con.Open();
        SqlDataReader reader = cmd.ExecuteReader();

        if(reader.Read())
        {
            info = reader[0].ToString();
            info += "\t" + reader[1].ToString();
            info += "\t" + reader[2].ToString();
            info += "\t" + reader[3].ToString();
            info += "\t" + reader[4].ToString();
        }
        else
        {
            info = "null\tnull\tnull\tnull\tnull";
        }

        reader.Close();
        con.Close();

        Response.Write(info);
        Response.End();
    }
}

일단, 서버 페이지인 Process.aspx 페이지부터 살펴보도록 하겠습니다. Process.aspx의 역할은 보시다시피 대단한 것이 없습니다. 단지 Param이란 인자로 넘어오는 사번을 가지고 데이터베이스를 조회해서, 그 사번에 해당하는 직원의 이름 및 정보들을 탭키(\t)를 구분자로 하는 문자열을 만들어서 화면에 출력하는 것이 전부일 뿐입니다. 단지, 데이터베이스를 조회하여, 그 결과 데이터를 화면에 출력하는 역할을 할 뿐이라는 것이죠. 만일, 사용자가 존재하지 않는다면, 각각의 값을 null이라는 문자열로 만들어서 출력하고 있습니다. 반드시 null이라는 문자열을 써야하는 것은 아니겠지만 예제에서는 그냥 그렇게 해 보았습니다. 맘에 안 드시면 " "와 같은 공백문자를 이용해도 무관하겠죠.

서버 페이지의 실행 결과는 확연할 것입니다. 존재하는 사번을 Param이라는 인자로 넘겨주면 다음과 같이 올바로 사용자 데이터가 화면에 출력될 것이고, 존재하지 않는 사번을 인자로 넘겨주면 두 번째 그림처럼 null이라는 값들이 직원 정보를 대신하겠죠.

서버 페이지는 준비가 되었으니 이제 Client.htm 페이지를 살펴보도록 하겠습니다.

우선, 이 페이지에서 가장 먼저 보아야 할 부분은 사번을 입력받는 컨트롤인 empID 컨트롤입니다.

<input id="EmpID" NAME="EmpID" onchange="GetInfo();">

이는 onChange 이벤트 시에 GetInfo라는 자바스크립트 함수를 호출하게 되어 있습니다. 그럼, GetInfo 함수를 살펴볼까요? 그 함수는 어떠한 일을 하나요? 내용은 대략 다음과 같습니다.

     function GetInfo()
     {
         var empID = document.all.EmpID.value;
         var pageUrl = "Process.aspx?param=" + empID;

         var xmlRequest = new ActiveXObject("Microsoft.XMLHTTP");
         xmlRequest.Open("POST", pageUrl, true);
         xmlRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
         xmlRequest.onreadystatechange = function() {CallBack(xmlRequest)};
         xmlRequest.Send(null);

         return xmlRequest;
     }

이 함수는 현재 폼에서 empID란 아이디를 갖는 컨트롤의 입력 값을 얻어와서 그 값을 가지고 Process.aspx 페이지를 요청하는 문자열을 구성합니다. 그 다음, Microsoft.XMLHTTP의 개체 인스턴스, 즉 코드 상에서는 xmlRequest를 만들어서 별도의 페이지 요청을 수행하고 있습니다. 즉, XMLHTTP 컨트롤을 이용해서 스크립트 내부에서 별도의 웹 요청을 서버로 보내고 응답을 받는다는 것입니다.

XMLHTTP 컴포넌트는 특정 URL로 요청을 보내서 응답 데이터를 받아오는 역할을 수행할 수 있습니다.

요청을 할 서버 경로는 Open 메서드에 인자로 실어서 보내면 됩니다. 물론, 그 url 경로 뒤에 GET 방식의 매개변수들을 첨부할 수 있는 것은 너무나도 당연하겠죠? 해서, 소스 첫 번째 라인에서 URL을 구성할 때, URL 뒤에 ?param= 이라는 것을 붙이고, 인자 값을 붙여준 것입니다. 그리고, XMLHTTP 개체의 Send 메서드를 통해서 요청을 보내고 나면, 이제 개체는 해당 URL 페이지를 요청하게 되고, 그 결과 값을 받아오게 됩니다.

한 가지 주의할 점은 이 호출이 비동기적이 되기 위해서는 Open 메서드의 세번째 인자로 반드시 true를 지정해야 한다는 것입니다. 바로 이 세 번째 인자가 호출을 비동기적으로 처리하라는 의미를 가지기 때문입니다. 이 말은 비동기 호출을 위한 방안도 XMLHTTP가 이미 내장하고 있다는 것이라 보시면 됩니다. ^^

그리고, 소스에서는 onreadystatechange 를 특정 함수명으로 지정하고 있습니다. onReadyStateChange 속성에 콜백을 위한 메서드를 지정해 주게되면, 상태변경(요청시작, 에러발생, 요청완료 등)이 일어날 때마다 지정된 CallBack 메서드는 반복적으로 호출되게 됩니다. 소스에서는 편의를 위해서 인자로 현재의 xmlRequest 개체를 넘기고 있지만 말입니다. 반드시 그래야하는 것은 아닙니다. XMLHTTP 컴포넌트를 전역으로 선언했다면 굳이 인자로 넘길 필요는 없겠죠?

CallBack 메서드는 호출의 상태가 변경될 때마다 호출이 되어지는데요. 아래의 소스에서 보이다시피, 상태를 확인해서 그 상태가 4(성공적으로 처리되었음을 의미)가 아닌 경우에는 처리를 건너뛰게 하고 있구요. 반환된 데이터가 전혀 없는 경우에도 return을 하게 하고 있습니다. 반환된 데이터가 올바를 경우에만 그 데이터를 잘라서 화면에 반영하겠다는 것이죠 ^^

     function CallBack(xmlRequest)
     {
         if (xmlRequest == null || xmlRequest.readyState != 4) return;
         if(xmlRequest.responseText.length == 0) return;

         var vals = xmlRequest.responseText.split("\t");

         document.all.LastName.value = vals[0];
         document.all.FirstName.value = vals[1];
         document.all.Title.value = vals[2];
         document.all.BirthDate.value = vals[3];
         document.all.City.value = vals[4];
     }

결과 값은 responseText라는 속성을 통해서 얻을 수가 있는데요. responseText 값은 서버의 페이지가 출력한 문자열 데이터(html 혹은 xml 혹은 예제의 경우, 일반 문자열)을 그대로 가져옵니다. 현재의 경우는 Process.aspx 페이지의 결과, 탭(tab) 키로 구분된 문자열이 넘어올 것입니다.

해서, 스크립트에서는 그 responseText 데이터를 탭 키로 Split해서, 일단 문자열 배열을 만든 뒤, 각각 필요한 문자열을 추출하여 사용하고 있는 것을 보실 수 있습니다.

조금 더 고급스러울 라면, 서버에서는 데이터를 xml 형태로 구성해서 출력하고, 클라이언트는 그 xml 데이터를 Parsing해서 사용하면 더욱 낫겠지만, 그를 위해 추가적인 코드가 필요하기에 예제에서는 간단하게만 해 보았습니다.

자. 그럼 이제 페이지(Client.htm)를 실행하고, 결과를 보도록 하겠습니다. 역시나 예상대로 잘 동작하는 것을 보실 수 있을 것입니다. 사번(1부터 9 사이의 임의의 숫자)을 입력하고 포커스를 옮기기만 하면 자동으로 다른 텍스트박스에는 해당 사번의 직원 정보가 출력되는 것을 보실 수 있을 것입니다. 그쵸?

+ Recent posts