서블릿은 단독으로 사용하는 것보다 HTML이나 JSP 페이지와 서블릿 사이에서 데이터를 주고 받는데 사용하는 것이 일반적이다. 이들 사이에서의 데이터 교환 및 공유에 필요한 기능으로서 "리퀘스트, 세션, 어플리케이션의 값 저장"과 "포워드/리디렉션"라는 기능에 대해 설명하겠다.

HTML에서 서블릿에 폼 전송

페이지 간의 데이터 교환의 기본이라고 하면 역시 "폼(form)"이다. HTML과 JSP 페이지에 폼을 준비하고, 거기에서 서블릿으로 보내는 것과 같은 방법은 서블릿 이용의 기본이라 할 수 있다.

서블릿에 송신된 폼의 내용을 얻으려면 doGet/doPost 인수에 준비되어 있는 HttpServletRequest 인스턴스를 사용한다. 이 안에 있는 "getParameter" 메소드로 전달된 값을 꺼내 올 수 있다.

String 변수 = request.getParameter(이름);

예를 들어, <input type="text" name="txt1">와 같은 입력 요소라면 getParameter("txt1")으로 지정하여 값을 얻을 수 있다. 얻을 수 있는 값은 모든 String으로 되기 때문에 숫자 등의 값은 필요에 따라 거기에서 변환하여 처리하면 된다.

이 getParameter라는 메소드는 이미 다른 곳에서 본 적이 있을 것이다. JSP 암묵 객체 request의 "getParameter"이다. 이는 단순한 우연이 아니다. 사실을 말하자면, 암시 객체 request는 doGet의 인수로 전달되는 HttpServletRequest의 것이기 때문이다.

JSP는 사실 서블릿이라고 설명 했었다. 이는 JSP의 스크립트도 모든 서블릿으로 변환되는 것이다. 서블릿은 기본적으로 doGet 메소드에서 실행되도록 되어 있다. 여기서 "암묵적 객체"라는 것은 "서블릿으로 변환될 때 미리 준비되어 있는 변수"라는 것을 알 수 있을 것이다.

request와 response라는 암묵적 개체는 doGet 인수 그 자체이다. 또한 out의 암묵적 개체는 response에서 얻은 PrintWriter를 보관하고 있는 변수로 생각할 수 있다. 그렇게 생각하면 JSP에서의 지식이 그대로 서블릿에서도 사용할 수 있음을 알게 될 것이다.

그럼 간단한 사용 예제는 아래와 같다.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Hello App Engine</title>
    <style>
    h1 {
        font-size: 16pt;
        background: #AAFFAA;
        padding: 5px;
    }
    </style>
</head>
<body>
    <h1>Hello App Engine!</h1>
    <p id="msg">무엇가를 써서 송신해 주세요.</p>
    <form method="post" action="/mygaeapp">
    <table>
        <tr>
            <td>입력</td>
            <td><input type="text" id="input" name="text1"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="송신"></td>
        </tr>
    </table>
    </form>
</body>
</html>

MyGaeAppServlet.java

package com.devkuma.mygaeapp;

import java.io.*;

import javax.servlet.http.*;

@SuppressWarnings("serial")
public class MyGaeAppServlet extends HttpServlet {
    
   public void doGet(HttpServletRequest request, HttpServletResponse response)  throws IOException {
       response.setContentType("text/plain");
       request.setCharacterEncoding("utf8");
       response.setCharacterEncoding("utf8");
       PrintWriter out = response.getWriter();
       out.println("Hello, world!");
   }

   public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
       response.setContentType("text/html");
       request.setCharacterEncoding("utf8");
       response.setCharacterEncoding("utf8");
       String param = request.getParameter("text1");
       PrintWriter out = response.getWriter();
       out.println("<html><head></head><body>");
       out.println("<h1>result</h1>");
       out.println("<p>you typed: " + param + ".</p>");
       out.println("</body></html>");
   }
}

index.html에 간단한 양식을 놓고, 그것을 먼저 만든 MyGaeAppServlet 서블릿에서 수신 결과를 표시하도록 하려고 한다. 아주 간단히 값을 서블릿으로 받아 넘기고 있다는 것을 알 수 있다.



Ajax으로 서블릿에 접근

앞에서 설명한 "양식을 제출하여 값을 전달"하는 방식은 별로 편리할 것 같지는 않다. 서블릿에서 PrintWriter를 사용하여 페이지를 출력하는 것은 매우 귀찮은 일이다. 역시 화면 표시는 HTML과 JSP를 이용하고, 서버 측의 처리 부분만 서블릿을 사용하는 방식이 효율이 좋다.

하나의 방법으로 생각나는 것이 Ajax를 사용하는 것이다. HTML 내에서 Ajax에서 서블릿에 액세스하고, 그 결과를 얻어 표시한다. 이렇게 하게 되면 서블릿 측은 결과 값만을 출력하게 되고, 표시는 HTML에서 하게 되어 개발이 편해 진다.

그럼, 샘플을 만들어 그것을 보면서 설명을 하도록 하겠다. 아래에 간단한 예제가 있다.

index.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Hello App Engine</title>
    <style>
    h1 {
        font-size: 16pt;
        background: #AAFFAA;
        padding: 5px;
    }
    </style>
    <script type="text/javascript">
    function doAction() {
        var req =  createRequest();
        if (req == null) {
            alert("실행이 되지 않는다!");
            return;
        }
        var s = document.getElementById('text1').value;
        req.open("post", "/mygaeapp", true);
        req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.onreadystatechange = function(){
            if (this.readyState == 4 && this.status == 200){
                var msg = document.getElementById('msg');
                msg.innerHTML = this.responseText;
            }
        }
        req.send("text1=" + encodeURIComponent(s));
        msg.innerHTML = "<<< please wait... >>>";
    }
         
       function createRequest(){
        var httplist = [
            function(){ return new XMLHttpRequest(); },
            function(){ return new ActiveXObjct("Msxml2.XMLHTTP"); },
            function(){ return new ActiveXObject("Microsoft.XMLHTTP"); }
        ];
        for(var i = 0;i < httplist.length;i++){
            try {
                var http = httplist[i]();
                if (http != null) return http;
            } catch(e){
                continue;
            }
        }
        return null;
    }
    </script>
</head>
<body>
    <h1>Hello App Engine!</h1>
    <p id="msg">정수 입력</p>
    <p name="msg"></p>
    <table>
        <tr>
            <td>입력</td>
            <td><input type="text" id="text1"></td>
        </tr>
        <tr>
            <td></td>
            <td><button onclick="doAction();">송신</button></td>
        </tr>
    </table>
</body>
</html>

MyGaeAppServlet.java

package com.devkuma.mygaeapp;

import java.io.*;
import java.net.*;

import javax.servlet.http.*;

@SuppressWarnings("serial")
public class MyGaeAppServlet extends HttpServlet {
    
   public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
       response.setContentType("text/plain");
       request.setCharacterEncoding("utf8");
       response.setCharacterEncoding("utf8");
       PrintWriter out = response.getWriter();
       out.println("Hello, world!");
   }

   public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
       response.setContentType("text/plain");
       request.setCharacterEncoding("utf8");
       response.setCharacterEncoding("utf8");
       String param = URLDecoder.decode(request.getParameter("text1"),"utf8");
       int result = 0;
       try {
           int n = Integer.parseInt(param);
           for(int i = 1;i <= n;i++){
               result += i;
           }
       } catch (NumberFormatException e) {
           e.printStackTrace();
       }
       PrintWriter out = response.getWriter();
       out.println(result);
   }
}

index.html과 MyGaeAppServlet를 사용한 샘플이다. HTML 내에서 Ajax으로 MyGaeAppServlet에 액세스하여 결과를 받아 와서 표시를 하도록 되어 있다.

이번 샘플에서는 입력 폼에 정수를 입력하면 Ajax 서블릿에 액세스하여 0부터 그 숫자까지의 합계를 계산하고 받아와서 표시하도록 되어 있다. 여러가지 숫자를 입력하여 동작을 확인해 보도록 하자.

그럼 흐름을 살펴 보자. 먼저 Ajax 통신에서 이용하는 XMLHttpRequest 객체의 생성은 createRequest라는 함수에 정리되어 있다. 이것을 호출하여 XMLHttpRequest 객체를 포함하고 open에서 액세스 대상을 지정하여 요청을 오픈하고 send에서 필요한 값을 설정하여 액세스 시작한다. 이 부분의 처리에 대해서는 JavaScript의 Ajax 통신에 대한 이야기가 되기 때문에 생략한다.

그런데, 서블릿 측의 처리하지만, 이것은 사실 먼저 번의 샘플과 거의 차이가 없다. 유일한 차이점은

  1. response.setContentType("text/plain");라고 되어 있다. HTML이 아닌 일반 텍스트를 써서 내보내고 있다.
  2. out.println하는 내용이 단순한 텍스트로만 되어 있다. HTML 태그는 일절 없다.

이 정도이다(물론 숫자를 계산하는 처리는 새로 증가하고 있지만...). Ajax 사용은 서블릿 측에는 거의 의식하는 것은 아니다. 기본적으로 JavaScript 측에서의 문제라고 생각해도 좋을 것이다.



JSP에 전달하고 결과를 forward하여 표시

또 다른 방법은 포워드로 결과를 표시하는 JSP에 표시를 전환하는 것이다. 서블릿에서 결과를 표시하는 JSP를 포워드(forward)로 표시 시키면 서블릿에서 화면의 표시 등을 할 필요는 없다.

단, 이 방법을 사용하는 경우, "어떻게 서블릿에서 포워드 대상의 JSP에 결과 데이터를 전달 하느냐"는 생각을 두어야 한다. 그럼 이도 샘플을 보면서 설명하겠다.

hello.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Sample jsp</title>
<style>
h1{
    font-size: 16pt;
    background: #AAFFAA;
    padding: 5px;
}
</style>
</head>
<body>
    <h1>Hello App Engine!</h1>
    <p>Result:<%=request.getAttribute("result") %></p>
    <hr>
    <p id="msg">정수 입력:</p>
    <form method="post" action="/mygaeapp">
    <table>
        <tr>
            <td>입력</td>
            <td><input type="text" id="input" name="text1" value="<%=request.getAttribute("input") %>"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="전송"></td>
        </tr>
    </table>
    </form>
</body>
</html>

MyGaeAppServlet.java

package com.devkuma.mygaeapp;

import java.io.*;
import java.net.URLDecoder;

import javax.servlet.*;
import javax.servlet.http.*;

@SuppressWarnings("serial")
public class MyGaeAppServlet3 extends HttpServlet {

   public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
       response.setContentType("text/plain");
       request.setCharacterEncoding("utf8");
       response.setCharacterEncoding("utf8");
       PrintWriter out = response.getWriter();
       out.println("Hello, world!");
   }

   public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
       response.setContentType("text/html");
       request.setCharacterEncoding("utf8");
       response.setCharacterEncoding("utf8");
       String param = URLDecoder.decode(request.getParameter("text1"), "utf8");
       PrintWriter out = response.getWriter();
       int result = 0;
       try {
           int n = Integer.parseInt(param);
           for (int i = 1; i <= n; i++) {
               result += i;
           }
       } catch (NumberFormatException e) {
           out.println(e);
       }
       request.setAttribute("input", param);
       request.setAttribute("result", result);
       ServletContext app = this.getServletContext();
       RequestDispatcher dispatcher = app.getRequestDispatcher("/hello.jsp");
       try {
           dispatcher.forward(request, response);
       } catch (ServletException e) {
           out.println(e);
       }
   }
}

위에 있는 양식을 가진 JSP와 그 대상이 되는 서블릿 소스 코드이다. hello.jsp에 액세스하여 폼에 정수를 써 보내면 /mygaeapp에 POST로 전송되어 그대로 hello.jsp에 전달된다. 이 때, "Result : OO"와 같이 서블릿에서 합계한 결과가 표시된다.

여기에서는 서블릿에서 JSP에 필요한 값을 전달하는데, 다음과 같은 처리를 하고 있다.

서블릿에서 값을 저장

request.setAttribute("input", param);
request.setAttribute("result", result);

JSP에서 값을 꺼내오기

<%=request.getAttribute("result") %>
<%=request.getAttribute("input") %>

request는 HttpServletRequest 클래스의 인스턴스이다. 이 "setAttribute"라는 메소드를 호출하는 것으로, HttpServletRequest 내의 값을 보관할 수 있다. 이것은 "getAttribute"메소드에서 언제든지 꺼낸 올 수 있다. 이러한 메소드의 사용법을 정리하면 다음과 같다.

값의 보관

"HttpServletRequest".setAttribute(이름, 값);

값의 취득

변수 = "HttpServletRequest".getAttribute(이름);

이제 HttpServletRequest(요청)에 값을 저장할 수 있게 되었다. 이 후에는 포워드를 사용하여 JSP에 표시를 바꿀 뿐이다. 이 처리는 의외로 귀찮은 일이다. 다음에 같이 정리된다.

1. ServletContext를 얻기

ServletContext app = this.getServletContext();

ServletContext이라는 것은 현재 움직이고 있는 Web 애플리케이션을 관리하기 위한 것이다. 이 인스턴스에는 Web 어플리케이션의 중요한 기능을 포함되어 있다.

2. RequestDispatcher를 얻기

RequestDispatcher dispatcher = app.getRequestDispatcher("/ hello.jsp");

ServletContext에서 "RequestDispatcher"라는 인스턴스를 가져온다. 이것은 요청를 디스패치(다른 곳으로 보내는 것)하는 것이다. 아무튼, 우선은 "이 가운데, 포워드가 이 메소드으로 제공된다"라는 점만 기억하면 된다.

3. forward 메소드로 포워드하기

try {
    dispatcher.forward(request, response);
} catch (ServletException e) {
    out.println(e);
}

취득한 RequestDispatcher에 제공하고 있는 "forward"로 포워드가 실행된다. 인수는 HttpServletRequest와 HttpServletResponse를 지정한다. 이 두개의 인수는 모두 doGet/doPost에 인수로 전달된 인스턴스를 그대로 설정하면 된다.

포워드는 forward라는 메소드에서 실행되지만, 그것을 호출까지 여러 인스턴스를 가져와야 한다. ServletContext와 RequestDispatcher를 얻어와서, 그 안에 메소드를 호출하여 포워드가 실행되는 것이다.



redirect을 이용하여 결과를 표시

포워드의 경우, URL 주소 표시 줄에 표시되는 주소는 변경되지 않기 때문에 /mygaeapp로 표시가 그대로인 상태로 hello.jsp의 내용이 표시되게 된다. 뭐, 그래도 크게 문제는 없지만, 리로드를 하게 되면 서블릿의 doGet 표시되어 버린다.

hello.jsp에 제대로 주소를 돌려 보내기 위하여는 포워드가 아닌 "리다이렉트(redirect)"을 이용해야 한다. 그러나 리다이렉트을 사용하는 경우에는 포워드와는 또 다른 형태로 값을 전달하는 방법으로 해야한다.

아래에 이전에 포워드를 사용한 예제를 리다이렉트을 이용하는 형태로 다시 수정하였다.

<%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Sample jsp</title>
<style>
h1{
    font-size: 16pt;
    background: #AAFFAA;
    padding: 5px;
}
</style>
</head>
<body>
    <h1>Hello App Engine!</h1>
    <p>Result: <%=session.getAttribute("result") %></p>
    <hr>
    <p id="msg">정수 입력:</p>
    <form method="post" action="/mygaeapp">
    <table>
        <tr>
            <td>입력</td>
            <td><input type="text" id="input" name="text1" value="<%=session.getAttribute("input") %>"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="전송"></td>
        </tr>
    </table>
    </form>
</body>
</html>

MyGaeAppServlet.java

package com.devkuma.mygaeapp;

import java.io.*;

import javax.servlet.http.*;

@SuppressWarnings("serial")
public class MyGaeAppServlet4 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/plain");
        request.setCharacterEncoding("utf8");
        response.setCharacterEncoding("utf8");
        PrintWriter out = response.getWriter();
        out.println("Hello, world!");
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        request.setCharacterEncoding("utf8");
        response.setCharacterEncoding("utf8");
        String param = request.getParameter("text1");
        PrintWriter out = response.getWriter();
        int result = 0;
        try {
            int n = Integer.parseInt(param);
            for (int i = 1; i <= n; i++) {
                result += i;
            }
        } catch (NumberFormatException e) {
            out.println(e);
        }
        HttpSession session = request.getSession();
        session.setAttribute("input", param);
        session.setAttribute("result", result);
        response.sendRedirect("/hello.jsp");
    }
}

이것을 동작을 시켜보자. 폼을 전송하면 다시 폼의 표시로 돌아와서, 합계가 계산되어 표시된다. 이때, URL 주소는 제대로 hello.jsp로 돌아와 있다. 이것으로 리로드해도 문제 없다.

리다이렉트은 포워드와 달리 더 쉽게 사용할 수 있다. 다음과 같은 메소드를 호출뿐이다.

"HttpServletRequest".sendRedirect(리다이렉트 주소);

포워드에 비해 매우 간단하다. 리다이렉트 주소를 인수로 지정하면 된다. 그러나! 리디렉션을 사용하는 경우, 이동된 곳에서 "HttpServletRequest의 setAttribute를 사용하여 값 전달받기"를 할 수 없다.

HttpServletRequest는 요청을 관리하는 것이다. 포워드는 서버에 표시되는 내용이 변경되기 때문에 요청이 중단될 것은 없다. 그런데 리다이렉트은 HTTP 헤더 정보를 사용하여 브라우저에 지정된 페이지로 이동하는 것이다. 즉, 브라우저가 받은 헤더 정보를 바탕으로 다른 페이지로 이동하는 단계에서 요청이 중단이 된다. 따라서 HttpServletRequest는 거기에서 사라지고 새로운 HttpServletRequest가 다시 제공이 되는 것이다. 그래서 Request의 setAttribue에 값을 보관해도 값을 얻을 수 없다.

그럼 어떻게 해야 하나? 이런 경우를 위해 있는 것이 "세션"이다. 세션은 JSP에서도 조금 사용했었다. 세션은 서버와 클라이언트 간의 지속적인 연결을 유지하기 위한 구조였다.

이 세션은 HttpSession라는 클래스로 준비되어 있다. 이는 request에서 꺼내 올 수 있다.

HttpSession session = request.getSession();

요청에 있는 HttpServletRequest 인스턴스의 "getSession"을 호출하는 것으로, 현재의 세션을 관리하는 HttpSession 인스턴스를 얻을 수 있다. 그리고 이 인스턴스의 "setAttribute"를 호출하여 세션에 값을 저장할 수 있다.

session.setAttribute("input", param);
session.setAttribute("result", result);

이 후에는 JSP 측에서 값을 얻어 오는 부분을 요청에서 세션을 변경하면 된다. 다음과 같은 식이다. JSP는 HttpSession은 "session"라는 암묵적 객체로 사용할 수 있다.

<% = session.getAttribute ( "result") %>
<% = session.getAttribute ( "input") %>

세션는 요청이 지나도 그대로 유지되기 때문에, 리다이렉트가 된 JSP에서도 제대로 값을 꺼내 올 수 있다. 이것으로 안심하고 페이지 이동이 가능해졌다.



응용 프로그램에 값을 저장

HttpServletRequest와 HttpSession은 이용 가능한 범위(scope라고 한다)가 다르다. HttpServletRequest는 현재 요청이 있는 동안만 존재한다. HttpSession은 세션이 연결되어 있는 동안 항상 존재한다. 이 두가지를 사용 구분하여 "어느 범위에서 값을 저장하고 계속되는지"를 생각해서 데이터 보존이 가능하다.

사실은 이 외에도 값을 저장할 수 있는 또 다른 것이 있다. 그것은 "ServletContext"라는 것이다. 이것은이 Web 어플리케이션 자체를 관리하는 클래스이다. 여기에 보관된 값이 Web 응용 프로그램이 있는 동안에 계속 보관하여 유지한다.

여기서 중요한 것은 "이것은 1개 밖에 없다"는 점이다. HttpServletRequest와 HttpSession는 액세스하는 클라이언트마다 준비된다. 즉, 동시에 여러 사람이 사용하더라도 각각 다른 개체가 준비되어 개별적으로 값이 저장된다.

그런데 ServletContext는 Web 응용 프로그램에 하나 밖에 존재하지 않는다. 이는 누가 접근해도 모두 같은 인스턴스에 액세스한다는 것이다. 즉, 거기에 저장된 값은 누가 접근하더라도 같은 것이 얻게 된다는 것이다.

이를 이용하면 "모두가 공유할 수 있는 데이터"를 쉽게 가질 수 있다. 실제로 해보도록 하자.

hello.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
<%@ page import="java.util.ArrayList" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Sample jsp</title>
<style>
h1{
    font-size: 16pt;
    background: #AAFFAA;
    padding: 5px;
}
table tr td {
    background: #DDFFDD;
    padding: 2px;
}
</style>
</head>
<body>
    <h1>Hello App Engine!</h1>
    <hr>
    <p id="msg">메시지:</p>
    <form method="post" action="/mygaeapp">
    <table>
        <tr>
            <td>입력</td>
            <td><input type="text" id="input" name="text1"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="전송"></td>
        </tr>
    </table>
    </form>
    <hr>
    <table>
    <%
    ArrayList<String> datas = (ArrayList<String>)application.getAttribute("datas");
    if (datas != null){
        for(String str : datas){
            out.println("<tr><td>" + str + "</td></tr>");
        }
    }
    %>
    </table>
</body>
</html>

MyGaeAppServlet.java

package com.devkuma.mygaeapp;

import java.io.*;
import java.util.ArrayList;

import javax.servlet.ServletContext;
import javax.servlet.http.*;

@SuppressWarnings("serial")
public class MyGaeAppServlet5 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/plain");
        request.setCharacterEncoding("utf8");
        response.setCharacterEncoding("utf8");
        PrintWriter out = response.getWriter();
        out.println("Hello, world!");
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        request.setCharacterEncoding("utf8");
        response.setCharacterEncoding("utf8");
        String param = request.getParameter("text1");
        ServletContext application = this.getServletContext();
        ArrayList<String> datas = (ArrayList<String>) application.getAttribute("datas");
        if (datas == null) {
            datas = new ArrayList<String>();
        }
        datas.add(param);
        application.setAttribute("datas", datas);
        response.sendRedirect("/hello.jsp");
    }
}

위에 예제는 전송된 메시지를 ArrayList에 보관한다. 이곳 저곳의 PC에서 액세스하여 메시지를 입력해서 전송해 보자. 모두가 입력한 모든 데이터가 저장되며, 모든 브라우저에 동일한 데이터가 표시되는 것을 알 수 있을 것이다.

ServletContext는 서블릿에서 this에 준비되어 있는 메소드를 호출해 가져온다.

ServletContext application = this.getServletContext();

JSP는 더 간단하다. "application"라는 암묵적 개체로 준비되어 있기 때문에, 그것을 그대로 사용하면 된다.

이 ServletContext에도 역시 "setAttribute", "getAttribute"라는 메소드가 준비되어 있으며, 값을 저장하고 얻어올 수 있다. 사용법은 HttpServletRequest 등과 완전 동일하다.

이 "HttpServletRequest", "HttpSession", "ServletContext"의 3개의 클래스를 구분하여 다양한 형태로 값을 저장할 수 있다. 서블릿과 JSP에서 필요한 값을 주고 받을 때, 이것들은 매우 중요하다. 이 3개는 세트로 기억해 두도록 하자.

JSP와 함께 서버 측에서 사용되는 기술이 "서블릿"이다. 이번에는 서블릿의 기본에 대해 설명한다.


서블릿과 JSP의 차이

서버 사이드 Java의 기본이라 할 수 있는 기술은 JSP 외에 또 하나가 있다. 그것은 "서블릿(Servlet)"이다. 이 두개는 도대체 어떻게 다른가?

대답은 "같다" 이다. 즉, JSP와 서블릿은 같은 것이다. 더 정확하게 말하자면, "JSP라는 서버 사이드 자체 스크립트 언어"라는 것은 존재하지 않는다. JSP는 사실 서블릿이기 때문이다.

좀 정리해보자. 서버 사이드 Java 프로그램은 일반적인 응용 프로그램과는 많이 다르다. 응용 프로그램은 그 프로그램을 기동시켜 실행하지만, 서버 사이드 Java 프로그램은 그렇지 않는다.

서버 사이드에는 Java 서버라는 것이 있고, 그 속에서 움직이는 프로그램을 개발하는 구조로 되어 있다. 예를 들면 Web 브라우저에서 "애플릿(Applet)"이라는 작은 프로그램을 포함하여 움직이는 것과 같다고 생각하면 된다. 애플릿은 응용 프로그램이 아니다. 미리 작은 프로그램을 실행하는 틀이 있고, 그 틀에 맞추어 만들어진 프로그램을 Web 페이지에 끼워 넣으면 자동으로 인식되어 움직인다.

서버 사이드도 마찬가지이다. Java 서버에는 그 안에서 프로그램을 작동시키기 위한 구조가 준비되어 있다. 그리고 그 구조에 따라 프로그램을 만들고 포함해 두었다. 사용자가 그 프로그램에 할당된 URL에 액세스하면 Java 서버는 그 프로그램을 실행하도록 되어 있다.

이 "Java 서버에서 움직이는 작은 프로그램"이 서블릿이다. "Web 페이지와 애플릿"를 그대로 "Java 서버와 서블릿"로 대체 생각해 보자.

그럼 JSP란 무엇인가? 서블릿은 결국에는 Java 프로그램이기 때문에, 만드는 것도 좀 힘들다. 모두 Java로 코딩해야 한다. 단지 내부의 처리뿐만 아니라 클라이언트(즉, Web 브라우저)에 표시되는 HTML 코드도 모두 Java 코드로 쓰지 않으면 안된다. HTML을 모두 println에서 만드는 상상해 보자. 대부분 어려운 작업이라고 생각이 될 것이다.

그래서 "더 간편하게 서버 사이드 Java를 사용할 수 있도록" 하는 것으로 생각된 것이 JSP이다. Java 서버가 수행하고 있으며, "간단한 태그를 사용하여 작성된 Java 코드를 바로 실행하는 것"은 사실 없다.

Java 서버는 JSP 코드를 읽어 들여, 그것을 서블릿 소스 코드로 변환한다. HTML 태그 등도, 모든 println으로 쓰여지도록 변환되는 것이다. 그렇게 생성된 서블릿 소스 코드를 컴파일하고, 서블릿을 생성하여 그것을 호출한다. 즉, "JSP가 서블릿"이 되는 것이다.


같다고는 하지만, 접하는 사용자에게는 상당히 차이가 있다. JSP는 HTML 안에 처리를 포함할 수 있기 때문에, HTML 페이지에 뭔가를 추가하는 경우에는 매우 편리하다. 반대로 서블릿은 HTML으로 자세하게 출력 등을 하지 않아도 되는 곳에서 활용하면 쓸모가 있다. 즉, 프런트 엔드(사용자에게 표시되는 측면)은 JSP으로 하고, 백엔드(서버에서 움직이는 보이지 않는 측면)은 서블릿으로 하는 방식으로 양쪽을 잘 구분하여 사용하면 좋다.

서블릿 버전

서블릿의 기본적으로 만드는 방법의 설명에 들어가기 전에 하나를 알아두어야 하는 것이 있다. 그것은 "서블릿 버전"이다.

서블릿도 서버 사이드 Java의 API로써 제공되고 있다. 물론 계속 향상되고 있으며, 때때로 버전 업하면 다양한 기능을 사용할 수 있게 된다. 문제는 "최신 버전에서 서블릿을 만들어도, 사용하는 서버(즉, WAS)가 지원하지 않으면 사용할 수 없다"는 점이다.

현재 서블릿을 이용하려고 하면 버전은 명확하게 두 가지로 나뉜다고 해도 좋을 것이다. 그것은 "3.0"이전과 이후이다. 서블릿 3.0에서 상당히 큰 개선이 이루어 졌으며, 만드는 방법도 많이 바뀌었다(기본 코드는 변함이 없지만). 3.0 이전에는 서블릿 코드 외에 설정 파일 등을 작성하지 않으면 안되지만, 3.0에서 서블릿 코드만 작성하면 다른 어떤 것도 필요 없게 되었다.

여기서 현재 서버, 서블릿, JSP 정보를 볼 수 있는 예제를 만들어 보도록 하자.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Info jsp</title>
<style>
h1 {
    font-size: 16pt;
    background: #AAFFAA;
    padding: 5px;
}
</style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <p>서버 정보 :<%=application.getServerInfo()%></p>
    <p>서블릿 정보 : <%=application.getMajorVersion()%>.<%=application.getMinorVersion()%></p>
    <p>JSP 정보 : <%=JspFactory.getDefaultFactory().getEngineInfo().getSpecificationVersion()%></p>
</body>
</html>

위에서 application에서 서버 및 서블릿 정보를 얻어와서 표시를 하고, JspFactory 객체에서 스팩에 대한 버전 정보를 표시하고 있다.

GAE에서 지원하는 서블릿 버전

GAE는 사실 아직 3.0을 지원하지 않는다. 그 이전의 "2.5"이라는 버전이 있다. 따라서 최신 버전에 비해 여러가지 귀찮다. 이 입문은 GAE를 사용하지만, 기본적으로 "JSP/서블릿 입문"이다. 따라서 3.0에 대해서도 언급하고자 하지만, GAE에서는 동작하지 않는다. 그래서, "2.5 기반으로 만들어 가지만, 일단 3.0에 대해도 설명을 해 둘것 이기에, 만약 GAE가 지원되게 되면 스스로 고쳐서 해보길 바란다"라는 접근법으로 가고자 한다.

서블릿 버전표

Servlet SpecJSP Spec웹소켓 스펙톰캣 버전지원 Java 버전
4.02.31.19.0.x8
3.12.31.18.0.x7
3.02.21.17.0.x6
2.52.1N/A6.0.x5
2.42.0N/A5.5.x1.4
2.31.2N/A4.1.x1.3
2.21.1N/A3.3.x1.1



서블릿의 기본 코드

그럼 서블릿의 기본을 살펴 보겠다. 서블릿이라는 것은 기본적으로 "Java 클래스"이다. 뭐, 당연하다고 한다면 당연하겠지만, "Java 클래스를 쓸 수 있어서 좋을"뿐이다. 뭔가 특별한 것이 아니다.

그럼 서블릿 클래스는 어떻게 작성되는지 보도록 하겠다. 기본 코드는 아래와 같다.

import javax.servlet.http.*;
 
@SuppressWarnings("serial")
public class 클래스명 extends HttpServlet {
     
    public void doGet(HttpServletRequest request, 
            HttpServletResponse response)
            throws IOException {
 
        ……여기에 GET 처리를 쓴다……
 
    }
 
    public void doPost(HttpServletRequest request, 
            HttpServletResponse response)
            throws IOException {
 
        ……여기에 POST 처리를 쓴다……
 
    }
}

이것이 서블릿의 기본 코드의 구조이다. 그럼 포인트를 정리하겠다.

1. 클래스는 HttpServlet을 상속받는다.

서블릿의 기본이 되는 것은 javax.servlet.http 패키지에 준비되어 있는 HttpServlet 클래스이다. 서블릿은 이 클래스를 상속하여 만든다.

2. 기본은 'doGet'과 'doPost' 메소드

클래스에 거의 필수 항목으로 제공되는 것은 'doGet'과 'doPost'메소드이다. 이들은 각각 HTTP 메소드인 GET/POST에 액세스할 때 실행된다. 이 중에 하나(또는 양쪽)가 반드시 준비될 것이다. 어느 메소드도 IOException이 발생할 수 있으므로 throws IOException해 둔다.

3. HttpServletRequest에서 요청 정보를 관리

이러한 메소드는 두 가지 중요한 객체가 인수로 전달된다. 하나는 'HttpServletRequest'이다. 이것은 요청 정보(클라이언트가 서블릿에 액세스하여 왔을 때 정보)를 관리하는 객체이다. 요청에 대한 다양한 정보는 이 객체의 메소드를 호출하여 얻을 수 있다.

4. HttpServletResponse에서 응답을 관리

또 다른 객체가 "HttpServletResponse"이다. 이것은 응답 정보(서블릿에서 클라이언트에 반환하는 정보)를 관리하는 객체이다. 클라이언트에 출력 등도 이 HttpServletResponse에서 PrintWriter를 꺼내어 써서 내보낸다.

Servlet 3.0의 어노테이션

이 밖에 Servlet 3.0을 사용하는 경우에는 이 서블릿의 공개 주소에 대해 어노테이션으로 지정할 수 있다. 이 클래스의 선언 부분(@SuppressWarnings이 있는 곳)에 작성한다.

@WebServlet(공개 주소)

이런 느낌이다. 예를 들어 http://OO/sample와 같은 주소로 공개한다면 @WebServlet("/sample")라고 작성해 두면 된다. 이것으로 서블릿은 지정한 주소로 자동으로 공개된다.

(2.5의 경우는 어노테이션은 사용할 수 없다. 별도의 설정 파일을 지정해야 한다. 이에 대해서는 계속 설명하겠다.)

web.xml에 서블릿 정보를 등록하기

Servlet 3.0을 사용하고 있다면, 이것으로 서블릿 작성은 끝이다. 이 후에는 배포할 뿐이다. 하지만, GAE처럼 아직 3.0을 지원하지 않는 환경에서는 서블릿 본체 소스 코드 외에 "서블릿 정보를 기술한 설정 파일"을 준비하지 않으면 안된다.

그것은 "web.xml"라는 파일이다. 일반적으로 Web 어플리케이션의 "WEB-INF" 폴더(외부에서 액세스되지 않는 특수 폴더이다)에서 준비한다. GAE 프로젝트는 기본적으로 자동으로 생성되어 있을 것이다.

이 web.xml에는 Web 응용 프로그램에 대한 다양한 설정 정보가 포함된다. 서블릿은 "서블릿의 이름", "사용하는 클래스", "공개 주소"와 같은 정보를 여기에 기술해야 한다.

아래에는 서블릿 관련 정보의 기본형을 정리해 두었다.

web.xml의 기본형

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
       http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

   <servlet>
       <servlet-name>서블릿 이름</servlet-name>
       <servlet-class>서블릿 클래스</servlet-class>
   </servlet>

   <servlet-mapping>
       <servlet-name>서블릿 이름</servlet-name>
       <url-pattern>공개하는 주소</url-pattern>
   </servlet-mapping>

   ……그 외에 태그 계속……

</web-app>

web.xml에 <web-app>라는 루트 태그 안에 설정 태그를 작성한다. 서블릿 정보는 다음의 2개의 태그로 제공된다.

<servlet> 태그

서블릿의 등록을 위한 것이다. 이 중에는 다음과 같은 두 개의 태그가 준비되어 있다. 서블릿 클래스는 단순히 클래스 이름뿐 아니라 패키지도 포함하여 작성한다.

  • <servlet-name> 서블릿의 이름을 등록한다.
  • <servlet-class> 사용하는 서블릿 클래스를 작성한다.

<servlet-mapping> 태그

서블릿 URL 매핑(어떤 주소로 공개하는지)을 설정하기 위한 것이다. 이 안에 이하 두개의 태그가 준비되어 있다. 이것으로 서블릿을 지정된 주소로 게시할 수 있다.

  • <sservlet-name> 서블릿 이름을 지정한다.
  • <surl-pattern> 공개하는 주소를 설명한다.

이 태그에 의해 생성된 서블릿 클래스가 지정된 주소로 공개된다. 몇번이나 설명을 했지만, 이는 "Servlet 3.0 이전"의 경우에 필요하다. 3.0 이상은 어노테이션으로 지정하면 필요없는 설정이다.

덧붙여서, web.xml에는 이 밖에 <welcome-file-list>라는 태그도 있다. 이것은 URL 경로(파일 이름)를 지정하지 않고 액세스했을 때, 디렉토리 안에 어떤 파일을 표시할지를 지정한다. 이것은 없어도 별도로 Web 응용 프로그램에 영향을 주지 않는다.



간단한 서블릿 작성

그러면 실제로 극히 초보적인 서블릿을 만들어 보자. 아래에 서블릿 샘플 코드 및 web.xml 파일의 등록 예제가 있다.

MyGaeAppServlet.java

package com.devkuma.mygaeapp;

import java.io.*;

import javax.servlet.http.*;

@SuppressWarnings("serial")
public class MyGaeAppServlet extends HttpServlet {
    
   public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
       response.setContentType("text/html");
       request.setCharacterEncoding("utf8");
       response.setCharacterEncoding("utf8");
       PrintWriter out = response.getWriter();
       out.println("<html><head></head><body>");
       out.println("<h1>Hello, world</h1><p>this is sample servlet.</p>");
       out.println("</body></html>");
   }

   public void doPost(HttpServletRequest request, HttpServletResponse response)
           throws IOException {
       // not use.
   }
}

web.xml

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
       http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
   <servlet>
       <servlet-name>MyGaeApp</servlet-name>
       <servlet-class>com.devkuma.mygaeapp.MyGaeAppServlet</servlet-class>
   </servlet>
   <servlet-mapping>
       <servlet-name>MyGaeApp</servlet-name>
       <url-pattern>/mygaeapp</url-pattern>
   </servlet-mapping>
   <welcome-file-list>
       <welcome-file>index.html</welcome-file>
   </welcome-file-list>
</web-app>

GAE 프로젝트를 만들 때 기본적으로 MyGaeAppServlet라는 클래스의 소스 코드 파일이 생성되어 있을 것이다 (프로젝트 "src"폴더에 있다). 이것을 그대로 사용한다.

만약 새로운 서블릿을 만들고 싶다면, [File]-[New]-[Class] 메뉴를 선택하고 프로젝트의 "src"폴더에 새로운 클래스의 소스 코드 파일을 작성한다.

여기에서는 MyGaeAppServlet 클래스의 doGet 메소드에 간단한 출력의 샘플 코드가 작성되어 있다. doPost는 메소드만으로 내용은 없다. 완성되면 GAE에 배치하고 다음과 같이 주소를 지정하여 방문해 본다.

http://{응용 프로그램 이름}.appspot.com/mygaeapp

간단한 텍스트를 표시하는 것이지만, 일단 제대로 표시되면 OK이다. 아주 간단하지만, 서블릿 코드와 설정을 작성하여 배포하기까지의 기본은 이것으로 알 수 있었다.

기억해 두어야 하는 서블릿의 기본 처리

이 doGet는 서블릿에서 반드시라고 해도 될 만큼 사용되는 처리만으로 구성되어 있다. 이들은 "서블릿의 기본 처리"로 먼저 기억해야 한다. 다음에 순서대로 정리를 해 보겠다.

콘텐츠 형식의 설정

response.setContentType(유형);

클라이언트 측에 어떠 결과를 표시할 경우, 그 내용이 어떤 유형의 콘텐츠인지를 설정해 둘 필요가 있다 (그렇지 않으면 브라우저가 해당 콘텐츠를 표시할 수 있을지 없을지 알 수 없게 되기 때문에). 이것을 수행하는 것이 HttpServletResponse의 "setContentType"이다. 이것은 인수에 콘텐츠 형식을 나타내는 텍스트를 지정한다. 우선, 다음 두가지를 명심하자.

  • HTML의 경우 : "text/html"
  • 일반 텍스트의 경우 : "text/plain"

인코딩 설정

request.setCharacterEncoding(인코딩 이름);
response.setCharacterEncoding(인코딩 이름);

클라이언트와 정보를 주고 받는 경우, 기본은 '텍스트'이다. 그렇게 되면, 그 텍스트가 어떤 인코딩인지 모르면 글자가 깨질 수 있다. 그 설정을 수행하고 있는 것이, 이 두 문장이다.

"setCharacterEncoding"는 인수에 지정된 인코딩 설정을 하는 것이다. 이는 HttpServletRequest와 HttpServletResponse 양쪽 모두 제공되어 있다. HttpServletRequest는 브라우저 등에서 보내온 텍스트 인코딩이고, HttpServletResponse은 클라이언트에 텍스트를 보낼 때 인코딩이다. GAE의 경우 UTF-8이 기본이기 때문에, 모두 "utf8"로 해두면 된다.

PrintWriter 얻기

PrintWriter out = response.getWriter();

클라이언트에 출력(즉, 브라우저에 뭔가를 보내는 것)은 "PrintWriter"라는 클래스를 이용한다. 이것은 HttpServletResponse의 "getWriter"메서드를 호출하여 인스턴스 얻어올 수 있다. 이렇게 꺼낸 PrintWriter의 메소드를 호출하여 출력을 한다.

클라이언트에 출력

out.println(출력하는 값);

PrintWriter으로 값의 출력에 이용되는 것이 "println" 메소드이다. 이것은 인수에 지정된 값을 클라이언트에 보낸다. 오버로드되어 있어서 인수에 어떤 값으로도 지정할 수 있다.

여기에서는 HTML 소스 코드를 작성하고 있다. 어떤 HTML 페이지를 다시 보낼 경우, 이와 같이 HTML 태그를 println에 작성하면 된다.

그래서, 우선 간단한 서블릿을 만드는 방법은 이제 알았다. 이번 기본 부분은 서블릿 작성에 반드시 필요한 지식이다. 다음에 좀 더 구체적인 서블릿 작성을 할 것이기에 이것까지는 꼭 기억해 두도록 하자.



Web에서는 브라우저에 약간의 데이터를 보관할 수 기능으로 "쿠키"를 활용된다. 또한 서버와 연결되어 있는 동안 항상 다양한 데이터를 유지하기 위해 '세션'이라는 기능도 포함되어 있다. 이러한 방법에 대해 설명한다.


쿠키의 기본 조작 및 사용

Web에서는 모든 데이터는 네트워크 건너편에 있다. 예전에는 로컬 환경에 많은 데이터를 둘 수 없었다. HTML5이 되어 여러가지로 저장할 수 있게 되면서 완전히 Web의 모습도 바뀌어 왔다. HTML5가 등장 할 때까지 브라우저에 데이터 등을 저장할 수 있는 기능이라고 하면 '쿠키(cookie)'뿐이었다.

쿠키를 이용하면 브라우저에 약간의 데이터를 저장하고 둘 수 있다. 이 쿠키의 역할은 그것뿐이다. "브라우저와 서버 사이에서 데이터를 주고 받는 정보를 교환할 수 있다"는 점에 있다. 각각의 클라이언트(Web 브라우저)에 따라 데이터를 보관하고 둘 수 있기에 여러가지로 활용을 할 수 있다. 예를 들어, ID를 할당하여 각 클라이언트를 식별하는데 사용할 수 있고, 마지막으로 액세스한 일시 등의 정보를 보관할 수도 있다.

최근 들어, HTML5가 로컬에 각종 정보를 저장할 수 있게 되어 왔지만, 이는 JavaScript를 통해서만 필요한 정보를 추출할 수 없기 때문에, 서버 사이드에서의 이용은 몹시 번거롭다. 또한 HTML5를 지원하지 않는 브라우저도 여전히 있다. 클라이언트와 서버 사이에서 원활하게 교환할 수 있는 "클라이언트 측에 저장할 수 있는 데이터"로는 쿠키의 필요성은 아직도 높다.

그럼 쿠키를 사용하는 방법에 대해 정리해 보자. 쿠키는 "Cookie"라는 클래스로 준비되어 있다. 이것은 다음과 같이 인스턴스를 만든다.

Cookie 변수 = new Cookie(이름, 값);

쿠키는 저장하는 값과 거기에 붙이는 이름이 세트로 되어 있다. 이렇게 하면 여러 값을 이름으로 정리하고 보관할 수 있도록 되어 있다. 그럼 이 쿠키는 어떻게 저장하고 검색할 수 있을까?

쿠키 저장하기

response.addCookie("Cookie");

쿠키 받아오기

Cookie[] 변수 = request.getCookies();

쿠키의 저장은 "response"라는 객체의 메소드를 호출한다. 이는 클라이언트에 대한 응답에 대한 정보를 관리하는 객체이다. 이 "addCookie" 메소드로 저장하는 Cookie 인스턴스를 인수로 지정하여 실행한다.

쿠키를 받아오기는 조금 귀찮다. JSP 기능에는 특정 쿠키만 얻어오는 기능은 없다. 준비되어 있는 것은 객체 request의 "getCookies"라는 메소드뿐이다. 이는 해당 사이트에 저장되어 있는 모든 쿠키를 Cookie 배열로 얻는다. 얻어온 Cookie는 저장되어 있는 이름과 값은 다음과 같이 꺼낼 수 있다.

쿠키 이름을 얻기

String 변수 = "Cookie".getName();

쿠키 값을 얻기

String 변수 = "Cookie".getValue();

따라서 조금 복잡하지만, getCookies에서 모든 쿠키를 얻어와서 반복하여 순차적으로 Cookie를 꺼내 그 이름을 getName에서 체크하는 형태로 필요한 쿠키를 찾아야 한다.

유효 기간을 설정하기

"Cookie".setMaxAge(초);

마지막으로 또 하나, 기억해야 할 것이 이것이다. 이는 쿠키가 저장되는 기간을 설정하는 것이다. 이렇게 하면 해당 쿠키가 언제까지 저장되는지를 설정할 수 있다. 이것으로 설정을하지 않으면 브라우저를 종료하거나 하면 그 시점에서 쿠키가 사라진다.

쿠키를 사용해 본다.

그러면 실제로 쿠키를 사용하여 데이터를 클라이언트에 저장하고 그것을 꺼내는 작업을 해보기로 한다.

여기에서는 간단한 메시지를 로드 횟수를 쿠키에 저장하는 샘플을 생각해 보았다. 아래 예제가 해당 JSP 소스 코드이다.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page import="java.net.*"%>
<%
    // 엔코딩을 설정해 둔다.
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");
    req = request;
    // 입력 필드 값을 얻는다.
    String input = request.getParameter("input");
    if (input == null)
        input = "";
    // messagecount의 쿠리를 얻는다.
    Cookie msg = getCookie("message");
    Cookie count = getCookie("count");
    // msgnull이라면 새로 쿠키를 만든다.
    if (msg == null) {
        input = URLEncoder.encode(input, "utf-8");
        msg = new Cookie("message", input);
        response.addCookie(msg);
    }
    // countnull이라면 새로 쿠키를 만든다.
    if (count == null) {
        count = new Cookie("count", "0");
        response.addCookie(count);
    } else { // null이 아니라면 count의 숫자를 1을 올려서 설정한다.
        String num = count.getValue();
        int n = Integer.parseInt(num);
        n++;
        count = new Cookie("count", String.valueOf(n));
        response.addCookie(count);
    }
    // 입력 필드에 뭔가 적혀 있다면 쿠키를 새로 설정하여 고친다.
    if (!input.equals("")) {
        input = URLEncoder.encode(input, "utf-8");
        msg = new Cookie("message", input);
        response.addCookie(msg);
        count = new Cookie("count", "1");
        response.addCookie(count);
    }
%>
<%!HttpServletRequest req;

    // 지정한 이름의 쿠키를 얻어 오는 메소드 정의
    Cookie getCookie(String s) {
        Cookie[] cookies = req.getCookies();
        Cookie res = null;
        if (cookies != null) {
            for (Cookie c : cookies) {
                if (s.equals(c.getName())) {
                    res = c;
                    break;
                }
            }
        }
        return res;
    }%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Sample jsp</title>
<style>
h1 {
    font-size: 16pt;
    background: #AAFFAA;
    padding: 5px;
}
</style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <p><%=count.getValue() + ": " + URLDecoder.decode(msg.getValue(), "utf-8")%></p>
    <form method="post" action="hello8.jsp">
        <table>
            <tr>
                <td>입력</td>
                <td><input type="text" id="input" name="input"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="송신"></td>
            </tr>
        </table>
    </form>
</body>
</html>

이 JSP 페이지에 액세스하면 입력 필드가 하나의 양식이 나타난다. 여기에 텍스트를 써서 보내면, 그 메시지가 쿠키에 저장되어 액세스할 때 페이지에 표시된다. 여러번 다시 로드해 보면, 보낸 메시지를 제대로 기억되어 페이지에 표시되는 것을 알 수 있다.

또한 새로 고침할 때마다 카운터의 수가 증가한다. 마지막 카운터 수가 쿠키에 저장되어 있으며, 거기에 1 더한 값을 쿠키에 저장하고 다시 값을 업데이트 하도록 되어 있다. 이는 메시지를 전송하여 변경하면 초기화되고 처음부터 다시 카운트한다.

여기에 이름을 지정하고 Cookie를 가져 오는데 getCookie라는 메소드를 정의해 두었다. 이 메소드는 getCookies에서 모든 Cookie의 배열을 가져온다. 반복을 하여 이름을 확인하고 있다. 여기에서 주의해야 하는 것은 "암시 개체(HttpServletRequest)"를 다루는 것이다.

이 getCookie 메소드는 req.getCookies();으로 Cookie 배열을 얻고 있지만,이 req는 암시 개체가 아니다. 사실은 이런 메소드 정의에서 암시 객체는 사용할 수 없다. 암시 객체는 <% %>에 작성된 코드(일반적으로 "스크립트"라고 한다) 내에서만 사용할 수 있다. 따라서 여기에서는 미리 글로벌 변수를 준비해 두어 이에 request를 할당하여 사용하고 있다.

또 다른 하나의 주의할 점은 "쿠키에 저장 텍스트"에 대해서 이다. 쿠키에 저장할 수 있는 것은 일반 아스키 텍스트(영숫자)뿐이다. 한글 등은 그대로 보관 할 수 없다.

그러 한글은 어떻게 보관해야 하나? 이는 "아스키 텍스트로 표현할 수 있는 형태로 인코딩하는"것이다. "URL 인코딩"라는 것으로, 이 형식으로 텍스트를 변환하여 저장하고 꺼낸 다시 디코딩하여 텍스트를 되돌린다.

텍스트를 URL 인코딩

String 변수 = URLEncoder.encode(값, 인코딩 이름);

텍스트를 URL 디코딩

String 변수 = URLDecoder.decode(값, 인코딩 이름);

URLEncoder.encode는 텍스트를 URL 인코딩한 것을 돌려준다. 그리고 URLDecoder.decode는 인코딩된 텍스트를 원본 텍스트에 되돌린 것을 돌려준다. 이것을 이용하여 텍스트를 인코딩하여 쿠키에 저장하고, 쿠키에서 꺼낸 후 다시 디코딩하여 사용하는 방식으로, 한글도 문제없이 쿠키에 보관할 수 있다.

이 밖에 요청과 응답에 각각의 텍스트 인코딩을 설정하는데 다음과 같은 문장을 처음에 실행하고 있다.

request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");

인코딩을 제대로 설정해 두는 것으로, 한글의 문자 깨짐 등을 예방하는데 도움이 된다. 이전 폼의 전송 등의 경우에는 하지 않았지만, 한글을 사용하는 경우 이러한 설정을 해두는 편이 좋다. 여기서는 이를 기억해 두도록 하자.




세션의 기본 조작 및 사용

쿠키는 작은 값을 보관하는 데 도움이 되지만, 그 이상의 것은 할 수 없다. 복잡한 값이나 큰 데이터를 보관 해 두는 것은 적합하지 않다. 그러나 액세스하여 각각의 사용자에 대해 어떤 값을 저장해서 두고 싶은 경우는 많다. 예를 들어 장바구니 같은 것이라면, 장바구니에 담은 상품 정보를 보관하고 둘 수 있다면 대단히 도움이 된다.

이렇게 "각 사용자마다 쿠키보다 복잡하고 큰 데이터를 보관"을 하는 경우에 사용되는 것이 "세션(session)"이다. 세션이란 서버와 클라이언트(브라우저) 사이의 연결되어 있는 동안 계속 유지 기능이다. 이 세션에 값을 저장하여 두는 것으로, 각 클라이언트에 데이터를 저장하고 관리할 수 있다.

세션은 "HttpSession"라는 클래스로 준비되어 있다. 이는 사실 인스턴스를 만들거나 할 필요가 없다. 처음부터 "session"이라는 암시 개체가 포함되어 있으며,이를 이용하여 세션을 조작할 수 있도록 되어 있다.

세션 값 저장 및 얻기

이 세션에는 값을 저장하거나 얻어오는 메소드가 준비되어 있다.

값을 저장

session.setAttribute(이름, 값);

값을 얻기

Object 변수 = session.getAttribute(이름);

세션 값은 어트리뷰트(속성)라는 형태로 구성되도록 되어 있다. setAttribute으로 속성을 설정하고 getAttribute로 지정된 속성을 얻어오는 것이다. 다만,이 "속성"이라고 하는 개념은 아무래도 감이 오지 않는 사람도 많을 것이다. 그래서 더 간단하게 "값을 설정하고 얻어오는" 형태의 메소드도 준비되어 있다.

값을 저장하기

session.putValue(이름, 값);

값을 얻기

Object 변수 = session.getValue(이름);

이 두 가지 방법은 사실 "완전히 동일한" 것이다. setAttribute와 putValue는 똑같은 동작을 하고 있으며, getAttribute와 getValue도 마찬가지이다. setAttribute에서 설정한 값을 getValue에서 꺼내는 것도 물론 가능하고, 그 반대도 마찬가지이다. 그래서 어떤거라도 선호하는 것을 사용하면 된다.

세션 접속 시간 및 해제

이 세션은 접속를 시작했을 때 시작해서, 접속해 있는 동안 계속 유지된다. 일정 시간 이상 접속을 하지 않거나 하면 세션도 종료되고 세션에 저장되어 있던 정보도 모두 삭제된다. 브라우저를 종료되었을 경우에는 따로 서버에 통보되지 않기에 실제로는 세션은 살아 있다. 이는 일정 시간 후에 사라지게 된다.

세션의 시작 시간과 경관 시간, 또는 세션 종료 등에 관한 메소드도 HttpSession에 준비되어 있다. 다음에 정리해 둔다.

세션을 시작하는 시간을 얻는다

long 변수 = session.getCreationTime();

마지막 액세스 시간을 얻기

long 변수 = session.getLastAccessedTime();

세션을 해제하기

session.invalidate();

getCreationTime과 getLastAccessedTime는 PC의 기준이 되는 날짜(1970년 1월 1일 자정)부터의 경과 밀리 초 단위로 얻는다. 또한 invalidate는 세션을 무효화하기 위한 것이며, 호출 이후에는 session 메소드 등은 모두 사용할 수 없게된다.

세션 유지시간을 지정

세션의 유지시간은 WAS의 기본 설정값으로 지정이 된다. 임의의 값으로 세션 유지시간을 늘리는 방법에 대해 알아보겠다.

코드상에서 유지시간 지정하기

session.setMaxInactiveInterval(초단위);
  • 세션 유지시간이 1시간인 경우 "3600"을 입력한다.

WEB-INF/web.xml에 유지시간 지정하기

<session-config>
    <session-timeout>분단위</session-timeout>
</session-config>
  • 세션 유지시간이 1시간인 경우 "60"을 입력한다.

위 두가지 방법은 세션에 대해 유지시간을 지정하는 모두 동일한 방식이다. 유지시간을 지정하지 않고 계속 유지 시키려면 0을 입력한다. 세션 유지시간은 해당 세션을 생성한 사용자의 브라우저 요청이 있을때마다 새롭게 세션시간을 갱신하게 된다. 세션 유지시간을 길게 하는것은 보안상 좋지 않으니, 적절하게 이용하자.

세션을 이용해 보기

그럼 실제로 세션을 사용하여 보기로 하자. 앞의 예제와 동일하게 하면 세션을 제대로 알기 힘들기에 이번에는 보낸 메시지를 점점 축적해 나가는 것을 만들어 보자.

아래와 같이 예제를 만들어 보자.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page import="java.util.*"%>
<%
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");

    String flg = request.getParameter("check");
    if (flg != null) {
        session.invalidate();
        session = request.getSession();
    }

    long create = session.getCreationTime();
    long last = session.getLastAccessedTime();
    long time = (last - create) / 1000;
    if (time < 0)
        time = 0;

    String input = request.getParameter("input");
    if (input == null)
        input = "";

    ArrayList<String> msgs = (ArrayList<String>) session.getValue("messages");
    if (msgs == null)
        msgs = new ArrayList<String>();

    if (!input.equals(""))
        msgs.add(0, input);

    session.putValue("messages", msgs);
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Sample jsp</title>
<style>
h1 {
    font-size: 16pt;
    background: #AAFFAA;
    padding: 5px;
}
</style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <table>
        <form method="post" action="helo.jsp">
            <tr>
                <td>입력</td>
                <td><input type="text" id="input" name="input"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="checkbox" id="check" name="check"> <label for="check">초기화</label></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="송신"></td>
            </tr>
        </form>
    </table>
    <hr>
    <p>(<%=time%> sec.)</p>
    <ol>
        <%
            for (int i = 0; i < msgs.size(); i++) {
        %>
        <li><%=msgs.get(i)%></li>
        <%
            }
        %>
    </ol>
</body>
</html>

액세스하면 입력 필드가 있는 화면이 나타난다. 거기에 뭔가 써서 보내면 그것이 아래에 표시한다. 보낼 때마다 그것이 목록의 맨 위에 추가된다. 보낼 때에 "초기화"의 체크 박스를 ON으로 해두면 세션을 초기화하고 새 메시지를 추가한다.

※ GAE에서 세션 사용 주의 사항

이 세션 이용의 샘플을 Google App Engine에서 동작시킬 경우, 주의해야 것은 "GAE에서 세션하기 위해서는 따로 ON으로 설정해야 한다"는 점이다. 프로젝트에 "appengine-web.xml"파일을 연다. 이는 GAE의 여러 설정을 담은 XML 파일이다. 이 가운데에 <appengine-web-app> 태그가 있다. 이 태그에 최초 설정이 작성되어 있다. 이 태그 내에

<sessions-enabled>true</sessions-enabled>

이러한 태그를 작성한다. 이제 세션을 사용할 수 있게 되었다. 이것을 잊으면 세션 기능은 움직이지 않기 때문에 주의하도록 하자.

이번은 ArrayList를 세션에 저장하고 있다. 메시지가 전송될 때마다 이 ArrayList에 추가된다.

ArrayList<String> msgs = (ArrayList<String>)session.getValue("messages");
if (msgs == null)
    msgs = new ArrayList<String>();

먼저 getValue으로 "messages"로 저장해 둔 ArrayList를 가져온다. 이것이 null 인 경우, 새로운 ArrayList 인스턴스를 만든다.

이렇게 ArrayList가 준비되면 전송된 텍스트를 맨 앞에 add하고 다시 세션에 보관한다.

session.putValue("messages", msgs);

샘플에는 세션을 시작한 이후로 경과 시간(초)을 표시한다. 이는 미리 경과 시간(초)을 계산하여 변수에 담아두고, 그것을 <%= %> 태그로 표시하고 있을 뿐이다.

long create = session.getCreationTime();
long last = session.getLastAccessedTime();
long time = (last - create) / 1000;
if (time < 0)
    time = 0;

getCreationTime과 getLastAccessedTime을 각각 변수로 얻어내어, 마지막 액세스 시간을 시작 시간에서 값을 빼고, 1000으로 나누면 경과한 초수를 알 수 있다. 의외로 간단하다. 마지막으로, "초기화" 체크가 ON일 때의 처리도 보도록 하자.

String flg = request.getParameter("check");
if (flg != null) {
    session.invalidate();
    session = request.getSession();
}

체크 박스는 체크가 ON이면 값이 전송되지만, OFF라면 값 자체가 보내지지 않는다. 따라서 request.getParameter( "check")으로 얻어낸 값이 null이면 OFF, null가 아니면 ON이라고 판단 할 수 있는 것이다.

ON인 경우에는 invalidate에서 세션을 삭제한다. 그 후으로는 다시 세션을 시작하고 전송된 값을 보관 해 두지 않으면 안된다. 그래서 request.getSession()라는 것으로, HttpSession을 다시 가져오고 있다. 암묵 객체인 session은 request의 "getSession"으로 세션을 얻어 올 수 있다.

우선 이것으로 세션 값의 보관 및 세션을 종료한 후에 재시작 등의 기본 조작을 할 수있게 되었다!

서버에서 클라이언트로 값을 출력하는 것은 할 수 있게 되었다. 그럼 클라이언트에서 서버로 필요한 정보를 보내려면 어떻게 해야 하나? 그 기본은 "쿼리 문자열"과 "양식(form)"이다. 이 두가지를 사용한 데이터 전달하는 방법에 대해 설명한다.


쿼리 문자열으로 값 받기

단순히 뭔가를 표시할 뿐이라는 것은 전회 설명한 JSP의 기본 태그만으로 어떻게든 할 수 있게 되었다. 이번에는 더 나아가 "클라이언트에서 서버로 뭔가를 보내면, 그것을 받아 다시 클라이언트에게 돌려 주는" 대화형 작업을 수행해 보자.

이러한 작업의 포인트는 "클라이언트에서 서버로 어떻게 필요한 정보를 보낼까"라는 점이다. 이를 알면 서버에서 필요한 처리를 하여 그에 따라 표시를 되돌려 보낼 수 있다.

여기에는 여러 방법이 있다. 먼저 가장 간단한 것으로 "쿼리 문자열을 사용"방법부터 해보자. 쿼리 문자열이라고 하는 것은, URL 주소 뒤에 붙은 파라미터의 기술 부분이다.

Web 사이트에서 http://xxx/?abc=xyz와 같이 이런식으로 URL 주소 뒤에 이런 것이 붙어 있는 것을 본 적이 있을 것이다. 그것이 쿼리 문자열이다. 이는 다음과 같은 형태로 되어 있다.

http://도메인/파일의 지정?이름1=값1&이름2=값2& ...

URL주소 뒤에 물음표(?)을 붙이고 그 이후에 "이름=값"형태로 값에 이름으로 붙여서 기술한다. 여러 값을 전달하는 경우 and(&) 기호로 연결한다. 값은 영숫자 텍스트라면 그대로도 괜찮지만, 기호나 2바이트 문자 등이 들어가면 문제를 일으키므로 일반적으로 URL 인코딩이라는 것을 사용하여 인코딩해야 한다.

그럼 이렇게 보내온 값을 JSP에 어떻게 받을 것인가? 여기에는 "request"라는 내장 객체를 이용한다. 이는 특별히 선언하지 않아도 처음부터 사용할 수 있는 상태가 되어있는 특별한 객체이다.

request은 서버에 보낸 요청(클라이언트에서의 요구)에 대한 다양한 정보를 담고 있다. 이 객체에는 'getParameter'라는 메소드가 준비되어 있다. 이것을 사용하여 쿼리 문자 값을 꺼낼 수 있다.

String 변수 = request.getParameter(값 이름);

이런 식으로 호출하면 된다. 그럼, 실제로 간단한 샘플을 만들어 서버와 클라이언트 간에 문자열을 주고 받아 보자.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%
String str = request.getParameter("param");
%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
    <script type="text/javascript">
    function doAction(){
        var s = document.getElementById('input').value;
        var url = 'hello4.jsp?param=' + encodeURI(s);
        window.location.href = url;
    }
    </script>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <p>페라미터:<%=str%></p>
    <input type="text" id="input">
    <button onclick="doAction();">Click</button>
    </body>
</html>

위에 나열된 샘플은 입력 필드에 텍스트를 쓰고 버튼을 누르면 메시지가 표시된다. 여기에서는 JavaScript를 사용하여 쿼리 문자열을 붙여서 페이지의 주소를 만들고 거기에 점프하고 있다.

var s = document.getElementById('input').value;
var url = 'helo.jsp?param=' + encodeURI(s);
window.location.href = url;

여기에는 예를 들어 필드에 "abc"라고 입력하면 helo.jsp?param=abc URL 주소가 서버로 전송된다. 서버 측에서는 JSP 코드으로 페라미터 값을 받고 있다.

String str = request.getParameter("param");

이것으로 변수 str에 "abc"라는 텍스트가 할당된다. 이 후에는 <p>페라미터:<%=str %></p>와 같이 하여 받은 값을 출력하고 있다.

이번에는 단지 표시만 하고 있지만, 물론 받은 값을 바탕으로 여러가지 작업을 수행하고 결과를 출력할 수도 있다.



폼(form) 전송

사용자으로부터 입력을 받으려면, 역시 기본은 "폼(form) 전송"이다. <form> 태그에 의한 양식에 정보를 담아 전송하고 그 전송에 대한 결과를 받아 처리하는 것이다. HTML의 기본이라 할 수 있는 기능이다.

폼에서 전송된 텍스트는 "getParameter"을 이용하여 얻을 수 있다. 아래와 같은 방식이다.

String 변수 = request.getParameter(이름);

이것으로 폼에 마련된 컨트롤에서 인수에 지정된 이름의 항목의 값을 얻어 올 수 있다. 주의해야 할 것은 "지정하는 것은 ID가 아닌 이름이다"라는 점이다. <input id="hoge">와 같이, ID만 지정하면 값을 얻어 올 수 없다. 반드시 name 속성을 지정해야 한다.

또한, 같은 이름(name)의 항목이 다수 있는 경우에는 그 값을 한꺼번에 얻어 올 수 있다. 이는 'getParameterValues'라는 메소드를 사용한다.

String[] 변수 = request.getParameterValues(이름);

이 getParameterValues는 인수로 지정된 name의 값을 모두 모와서 String 배열을 돌려준다. 이는 예를 들어. 여러 항목을 선택할 수 <select> 태그에서 전체 값을 얻우 올 때에 사용된다.

그럼 예제를 만들어 보도록 하자.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%
String inpt = request.getParameter("input");
inpt = inpt == null ? "" : inpt;
String chk = request.getParameter("check");
chk = chk == null ? "OFF" : "ON";
String rd = request.getParameter("radio");
rd = rd == null ? "" : rd;
String[] sels = request.getParameterValues("select");
String sel = "";
if (sels != null){
    for(int i = 0;i < sels.length;i++)
        sel += sels[i] + " ";
}
String str = "INPUT:" + inpt + "<br>" +
        "CHECK: " + chk + "<br>" +
        "RADIO: " + rd + "<br>" +
        "SELECT:" + sel;
%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <p><%=str  %></p>
    <table>
    <form method="post" action="hello.jsp">
        <tr><td>입력</td><td>
        <input type="text" id="input" name="input"></td></tr>
        <tr><td></td><td>
        <input type="checkbox" id="c1" name="check" value="Une">
        <label for="c1">체크 박스</label></td></tr>
        <tr><td></td><td>
        <input type="radio" name="radio" id="r1" value="first">
        <label for="r1">라디오 버튼1</label><br>
        <input type="radio" name="radio" id="r2" value="Second">
        <label for="r2">라디오 버튼2</label></td></tr>
        <tr><td></td><td>
        <select id="select" name="select" multiple>
            <option value="Eins">첫번째</option>
            <option value="Twei">두번재</option>
            <option value="Drei">세번째</option>
        </select></td></tr>
        <tr><td></td><td>
        <input type="submit" value="전송"></td></tr>
    </form>
    </table>
    </body>
</html>

위에 예제는 양식을 자신의 주소로 보내하여 입력된 정보를 함께 표시하는 예제이다. 여기에서는 hello.jsp라는 파일명으로 만들었기 때문에, 다른 파일 이름을 사용하는 경우는 <form> 태그의 action을 수정한다.

여기에서는 전송된 양식(<select> 이외의)의 정보는 getParameter을 사용하여 얻어오고 있다.

String inpt = request.getParameter("input");
String chk = request.getParameter("check");
String rd = request.getParameter("radio");

모두 name으로 지정한 이름을 인수로 지정하고 있다는 것을 알 수 있을 것이다. 조금 까다로운 것이 <select>에서 얻어 오는 거다. 이는 이번에 여러 항목을 선택할 수 있도록 되어있다.

String[] sels = request.getParameterValues("select");
String sel = "";
if (sels != null){
    for(int i = 0;i < sels.length;i++)
        sel += sels[i] + " ";
}

먼저 getParameterValues에서 String 배열을 얻어온다. 그리고 반복하여 배열에서 한 개씩 값을 얻어오고 있다. 여기서 전혀 값이 없다면, getParameterValues는 null이 되면 for 문은 에러가 되기 때문에 null이 아닌지 확인하고 실행하는 것을 잊지 않도록 하자.



Ajax를 사용하여 JSP에 정보 전달

폼 전송은 분명히 HTML의 기본이긴 하지만, 최근에는 그다지 많이 사용하지 않게 되고 있다. 보낼 때마다 페이지를 매번 서버에서부터 다시 로드해서 다시 읽어 들여 새로 고침을 하기에 싫어하는 사람도 많다.

폼 전송 대신에 서버와의 통신으로 많이 사용되고 있는 것이 "Ajax"이다. Ajax는 JavaScript를 이용하여 서버와 비동기 통신을 하는 기능이다. 비동기(처리가 끝날 때까지 기다리지 않고 먼저 처리를 진행 방식)이기 때문에 통신하는 동안 다른 조작을 할 수 없게 되는 일도 없고, 백그라운드로 필요한 정보를 얻기 때문에 페이지를 이동하지도 않으면서 화면 표시를 업데이트 할 수 있다.

Ajax 자체는 따로 JSP가 없어도, HTML과 JavaScript만 있으면 사용할 수 있다. 즉, 보통의 HTML 파일로 가능하다. Ajax에서 액세스하는 "서버 측 프로그램"에 JSP를 사용하면 된다.

이것은 실제로 샘플을 보는 편이 빠를 거다. 아래에 간단한 예제를 보도록 하자.

index.html

<!DOCTYPE html>
<html>
<head>
   <meta http-equiv="content-type" content="text/html; charset=UTF-8">
   <title>Hello App Engine</title>
   <style>
       h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
   </style>
   <script type="text/javascript">
       function doAction(){
           var req =  createRequest();
           if (req == null){
               alert("실행이 되지 않는다!");
               return;
           }
           var s = document.getElementById('input').value;
           req.open("post", "hello.jsp?param=" + encodeURI(s));
           req.setRequestHeader("User-Agent","XMLHttpRequest");
           req.onreadystatechange = function() {
               if (this.readyState == 4 && this.status == 200) {
                   var msg = document.getElementById('msg');
                   msg.innerHTML = this.responseText;
               }
           }
           req.send();
       }

       function createRequest(){
           var httplist = [
               function(){ return new XMLHttpRequest(); },
               function(){ return new ActiveXObjct("Msxml2.XMLHTTP"); },
               function(){ return new ActiveXObject("Microsoft.XMLHTTP"); }
           ];
           for(var i = 0;i < httplist.length;i++){
               try {
                   var http = httplist[i]();
                   if (http != null) return http;
               } catch(e){
                   continue;
               }
           }
           return null;
       }
   </script>
</head>
<body>
   <h1>Hello App Engine!</h1>
   <p id="msg">무언가 써서 송신해 주세요.</p>
   <table>
       <tr>
           <td>입력</td>
           <td><input type="text" id="input" name="input"></td></tr>
       <tr>
           <td></td>
           <td><button onclick="doAction();">Send</button></td>
       </tr>
   </table>
</body>
</html>

hello.jsp

<%@ page language="java" contentType="text/plain; charset=utf-8"
   pageEncoding="utf-8"%>
<%
String method = request.getMethod();
if ("GET".equals(method)){
   out.println("can't access!");
} else {
   String inpt = request.getParameter("param");
   out.println("당신, '" + inpt + "' 라고 썼습니다.");
}
%>

여기에서는 index.html과 hello.jsp을 사용하고 있다. index.html에 액세스하여 입력 양식에 무엇인가 쓰고 버튼을 누르면 Ajax 통신으로 hello.jsp에 액세스하고 결과를 표시한다.

Ajax 통신에 대해서는 JSP와는 관계 없기 때문에 설명은 생략한다(관심있는 사람은 "Ajax로 비동기 서버 통신"에 대해서 알아보고 바란다). 여기에서는 Ajax으로 억세스하는 JSP 측의 처리를 확인한다.

이번에는 hello.jsp에 POST로 액세스하여, 결과를 받을 수 있도록 하고 있다. 직접 이 페이지에 액세스 (GET으로 액세스)하는 경우에는 "can not access!"가 표시되도록 되어 있다. 이러한 GET과 POST의 사용 구분은 request 메소드로 알아 낼 수 있다.

String method = request.getMethod();
if ("GET".equals(method)){……중략……}

request 객체의 "getMethod"는 액세스 메소드을 반환한다. 반환값은 "GET" 또는 "POST" 중 하나이다. JSP의 getParameter의 메소드는 GET이든 POST이든 동일하게 호출 값을 얻을 수 있다. 이용 상으로는 JSP는 GET/POST를 의식하지 않고 동일하게 취급할 수 있도록 설계되어 있다. 하지만 때로는 "GET의 경우 하는 처리, POST의 경우에 하는 처리"와 같이 처리를 나누지 않으면 안되는 것이 있다. 이러한 경우 getMethod가 사용된다.

다른 부분은 특별히 어려운 부분은 없을 것이다. out.println으로 출력한 것이 그대로 Ajax 통신으로 클라이언트로 전송되는 것이다. 또한 이번에는 Ajax으로 텍스트 값을 받을 뿐이다. 처음에 있는 page 지시문을 다음과 같이 작성되어 있다.

<%@ page language="java" contentType="text/plain; charset=utf-8"
    pageEncoding="utf-8"%>

잘 보면 contentType="text/plain; charset=utf-8"으로 되어 있다. text/html 대신에 text/plain으로 변경되어 있다. 이렇게 "서버에 요청하여 결과를 받을 뿐"인 경우에는 그냥 텍스트를 출력할 뿐이기에 text/plain으로 해야 한다.



받은 데이터 이스케이프 처리

클라이언트와 서버 사이에서 데이터를 교환하는 처리를 배울 때, 함께 배워야 하는 것이 있다. 그것은 "데이터 이스케이프"에 대해서이다.

사용자로부터 전송된 값을 사용하는 경우, "사용자는 어떤 값을 보내올지 모른다"라는 것을 염두에 둘 필요가 있다. 특히 생각하지 않으면 안되는 것이 JavaScript이다. 입력 필드에 <script> 태그를 사용한 스크립트를 작성하여 전송되면 어떻게 될까? 그 텍스트를 그대로 화면에 표시하도록 처리가 되어 있는 경우, 페이지가 나타날 때 스크립트가 실행될 것이다(최근의 브라우저들은 이를 막아줘서 실행이 되지 않는 경우가 많다). 예를 들면, 게시판이나 댓글 게시물과 같은 시스템의 경우, 이러한 스크립트가 게시되어 표시되도록 되어 버리면, 거기에 다른 사람이 접근할 때마다 의도하지 않은 스크립트가 실행될 것이다.

일반적으로 XSS(크로스 사이트 스크립팅)이라는 사이트 공격은 이런 취약점을 노린 것이다. 아무튼, 여기 샘플에서 하고 있는 정도라면 문제가 발생 수 없겠지만, 데이터베이스 등의 데이터를 축적하고 이를 표시하는 시스템이 되면 이런 종류의 트러블는 피해갈 수 없을 것이다. 지금 단계에서 "어떻게 대처하면 좋을지"정도는 배워 보도록 하자.

이런 종류의 공격에 대한 대책의 기본은 "텍스트를 출력하기 전에 이스케이프 처리한다"라는 것이다. 예를 들어, HTML 태그에서 사용하는 <,>와 같은 기호를 &lt;&gt;으로 대체하는 것만으로 그 태그는 무력화 할 수 있다. 이와 같이 특별한 기능을 가지는 기호 종류를 이스케이프 처리하는 것으로, 그 데이터에 포함되어 있는 기능을 무력화하는 것이다.

아래에 매우 간단한 예제를 보도록 하자.

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%
String inpt = request.getParameter("input");
inpt = inpt == null ? "" : inpt;
String chk = request.getParameter("check");
chk = chk == null ? "OFF" : "ON";
String rd = request.getParameter("radio");
rd = rd == null ? "" : rd;

String str = "INPUT:" + getEscapedString(inpt) + "<br>" +
        "CHECK: " + getEscapedString(chk) + "<br>" +
        "RADIO: " + getEscapedString(rd) + "<br>";
%>
<%!
public String getEscapedString(String s){
    String str = s;
    str = str.replace("&","&amp;");
    str = str.replace("<","&lt;");
    str = str.replace(">","&gt;");
    str = str.replace("\"","&quot;");
    return str;
}
%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <p><%=str%></p>
    <form method="post" action="hello.jsp">
    <table>
        <tr>
            <td>입력</td>
            <td><input type="text" id="input" name="input"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="checkbox" id="c1" name="check" value="Une"><label for="c1">체크박스</label></td>
        </tr>
        <tr>
            <td></td>
            <td>
                <input type="radio" name="radio" id="r1" value="first"><label for="r1">라디오버튼1</label><br>
                <input type="radio" name="radio" id="r2" value="Second"><label for="r2">라디오버튼2</label>
            </td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="송신"></td>
        </tr>
    </table>
    </form>
    </body>
</html>

여기에는 텍스트를 보낼 때에 보낸 텍스트에서 < > \ &와 같은 기호를 모두 이스케이프 처리하여 표시하고 있다. 이스케이프 처리는 getEscapedString이라는 메소드로 정의하고 있다. 텍스트를 출력할 때 쓰내는 텍스트를 getEscapedString으로 이스케이프 처리를 하여 표시하면 된다.

또한 이스케이프 처리를 할 때, 주의해야 할 것은 "텍스트 입력된 값만을 처리하면 된다라고는 생각하지 않는다"라는 점이다. 여기에서 체크 박스나 라디오 버튼의 값까지 getEscapedString으로 처리를 하고 있다. "그렇게 보내온 값은 정해져 있으니까 불필요하지?"라고 생각할 지도 모른다. 하지만 JSP는 GET이든 POST에서도 똑같이 getParameter에서 값을 얻을 수 있다. 이는 예를 들어, hello.jsp?check=hogehoge와 같이 URL을 지정하여 액세스하여 check 본래와는 다른 값을 전달될 수 있다. getMethod으로 POST만을 받아들이도록 처리를 하지 않은 경우, 그대로 그 값이 처리되어 버리는 것이다.

그래서, 프로그램 내에서 클라이언트 측에 텍스트를 출력할 때는 항상 이스케이프 처리하도록 해야 한다.



서버 사이드 Java의 기본 중의 기본이라고 할 수 있는 것은 "JSP"이다. 이는 HTML에 특수 태그를 사용하여 Java 코드를 포함할 수 있다. 우선 JSP를 사용하여 서버 사이드 Java를 움직여 보자.


Java Server Pages 생성

"서버에서 Java를 동작시킨다"라고 하면 아무래도 개념적으로는 모르다고 해도, 보통의 Java보다 월등히 어려울 것"이라고 생각할 수 있다. 뭐, 본격적으로 Java 서버 개발을 하려고 하면 나름대로 고급 지식이 필요하긴 하지만, "고급 지식이 없으면 서버 사이드 Java는 사용할 수 없다 "라는 것은 아니다.

Java는 "서버에서 쉽게 Java 코드를 실행하는 방법"이라는 것이 포함되어 있다. 그것이 "JSP(Java Server Pages)"라는 것이다.

JSP는 최근 서버 개발에 많이 사용되는 PHP와 같은 느낌으로 간편하게 Java 코드를 실행할 수 있는 기술이다. JSP는 "Java 소스 코드를 작성하여 컴파일하고 ......"와 같은 작업은 필요없다. 동작 원리는 브라우저에 표시되는 HTML 태그 내에 Java 코드를 작성하고 실행을 하는 것이다.

아무튼, 이는 우선 실제로 해보는 편이 훨씬 알기 쉬우므로 어쨌든 JSP 파일을 만들어 움직여 보기로 하자.

  1. [File] 메뉴의 [New]에서 [Other ...]라는 항목을 선택한다.

  2. 화면에 "Select a wizard"라는 대화 상자가 나타난다. 여기에서 만들 항목을 선택한다. 목록에서 "Web"라는 곳에 있는 "JSP File"항목을 선택하고 다음 진행한다.

  3. "JSP Create a new JSP File"화면으로 이동한다. 여기에서 만들 파일의 위치와 파일 이름을 입력한다. 배치 위치는 "MyGaeApp/war"를 입력한다 (아래에 있는 프로젝트 "MyGaeApp"아이콘을 확장하고 그 안에 있는 "war"폴더를 클릭하면 설정된다). 또한 파일 이름은 여기에서 "helo.jsp"라고 한다. JSP 파일은 이처럼 ".jsp"확장자를 지정한다.

  4. 그러고 "Next>" 버튼을 눌려서 다음 진행을 하면 "Select JSP Template" 화면이 나타난다. 여기에서 사용하는 템플릿을 선택한다. 여기에서 목록의 위에서 4 번째에있는 "New JSP File (html) '라는 항목을 선택한다. 이것이 가장 일반적인 JSP 작성 방법이다. (뭐 다른 것을 선택해도 나중에 소스 코드를 편집하면 어떻게든 되기 때문에, 사실 뭐든지 상관 없지만...)

이제 "Finish"버튼으로 종료하면 "war"폴더에 "helo.jsp"라고 JSP 파일이 생성된다. 만약 실수로 다른 곳에 생긴 경우에는 그대로 파일의 아이콘을 드래그하여 "war"폴더에 넣어 버리면 된다.

JSP 파일 편집

먼저 작성한 JSP를 편집하여 페이지를 만들어 보자. 먼저 페이지의 인코딩을 UTF-8로 변경해야 한다.

Project Explorer에서 "helo.jsp"를 선택하고 [File] 메뉴 중에서 [Properties] 를 선택한다. 화면에 helo.jsp 파일 설정 창이 나타난다.

이 윈도우는 왼쪽에 설정 항목이 나열되어 표시되고, 여기에서 항목을 선택하면 그 구체적인 구성 내용이 오른쪽에 표시되도록 되어 있다. 이 목록에서 "Resource"를 선택한다(아마도 기본적으로 상단에 이 메뉴가 선택되어 있을 것이다).

화면에 파일에 대한 속성 등의 설정이 표시된다. 여기에서 하단에 있는 'Text file encoding'라는 곳을 보면, "Default" 라디오 버튼이 선택되어 있을 것이다. 이를 "Other"로 변경하고 오른쪽의 콤보 박스에서 "UTF-8"을 선택한다. 이것으로 "OK" 버튼을 눌러 대화 상자를 닫으면 파일의 인코딩이 변경된다.



모든 소스 코드의 엔코딩의 "Default"를 변경할 수도 있다. 먼저 환경설정을 누르고 [Preferences] 화면이 나타나면, 왼쪽에 [Content Types]을 선택하고 [JSP]항목을 선택한다. 여기에 파일의 'Default encoding'을 UTF-8로 변경해주면 기본 엔코딩이 UTF-8로 변경된다.




JSP 태그를 사용해 보자

그럼 helo.jsp을 더블 클릭하여 열어서 소스 코드를 수정하자. 기본적으로 간단한 코드가 적혀 있지만, 이는 "UTF-8"으로 되어 있지 않으므로 곳곳 수정해야 한다. 또한 실제로 표시되는 내용은 아무것도 없기 때문에 뭔가 적당히 작성해 보도록 하자.

아래와 같이 간단히 예제 코드를 작성해 보자.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Sample jsp</title>
    <style>
    h1 {font-size:16pt; background:#AAFFAA; padding:5px; }
    </style>
</head>
<body>
    <h1>Sample jsp page</h1>
    <p>이 페이지는 샘플입니다.</p>
    <p><%= java.util.Calendar.getInstance().getTime() %></p>
</body>
</html>

우선은 이대로 수정하고 저장한다. 여기에서는 낯선 태그가 두개가 등장한다. 다음과 같다.

page 지시문

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

맨 처음에 있는 것은 <%@ ... %>형태의 태그이다. 이 <%@으로 시작하여 %>로 끝나는 태그는 "지시문(directive)"라고 한다. 이것 자체는 Java 코드가 아닌 페이지에 관한 세부적인 지정을 기술하는데 사용한다.

여기에서는 직후에 "page"가 있다. 이는 "page 지시어"라고 하는데, 페이지의 여러 설정을 기술하는 것이다. 여기에서는 language, contentType, pageEncoding와 같은 속성이 있는데, 이는 각각 "언어", "콘텐츠 형식", "페이지의 인코딩"을 지정한다. 아무튼, 이것은 "반드시 이 태그를 적는다"고 그대로 기억해 두자.

<%= %> 태그

<%= java.util.Calendar.getInstance().getTime() %>

이 부분이 실제로 Java 코드를 실행하는 부분이다. <%=으로 시작해서 %>로 끝나는 태그는 등호(=) 다음에 작성된 Java 코드를 실행하고 그 결과를 그 자리에 써서 내보내는 동작을 한다. 여기에서는 java.util.Calendar.getInstance().getTime()의 결과를 페이지에 출력하고 있다.

실제로 확인해 보면 알 수 있듯이, 이것은 Calendar.getInstance(). getTime()으로는 동작하지 않는다. java.util.Calendar ...... 와 같이 패키지로 제대로 지정하지 않으면 안된다. (물론 패키지를 지정하는 방법도 있다. 이것은 나중에 설명한다)

+ Recent posts