JSP 디버깅은 환경설정이 거의 반을 차지합니다. eclipse WTP 에서 JSP 개발환경을 구축하는 것은 따로 설명하겠습니다. 일단 이 환경만 구축이 되면 JSP디버깅은 java 디버깅과 크게 다르지 않습니다.
디버깅은 JVM내부를 들여다 볼 수 있어야 합니다. eclipse WTP에서 WAS를 구동하게 되면 WAS의 메모리를 들여다 볼 수 있습니다. 이것에 기반하여 JSP 가 돌아가는 WAS의 메모리 값을 확인하는 것이 기본입니다.

jsp project

jsp project

debugjsp 라는 프로젝트를 만들었습니다. 프로젝트 아이콘을 보시면 debugjava 프로젝트와 다른 것을 알 수 있는데, 조그마한 지구 모양과 J 모양은 웹 관련 java프로젝트라는 의미입니다. 웹 페이지를 돌리기 위해서 웹서버가 필요한데 그것은 Servers 프로젝트가 담당합니다. jsp프로젝트에서 만들 수 있습니다. 로컬 PC에 설치된 Tomcat 같은 WAS를 이용하는 프로젝트입니다.

debug할 jsp 페이지를 하나 만들어보겠습니다.
jsp page

jsp page


JSP 파일은 debugjsp project 내에 WebContent라고 자동 생성된 디렉토리 아래 놓아둡니다. project명을 따라서 context가 정해지는데 일단 /debugjsp 라는 것이 URL에 따라다닌다는 정도만 기억하십시오. debug.jsp 파일을 만듭니다.
<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>debug jsp</title>
</head>
<body>
<%
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= 9; j++) {
            out.println(i + " * " + j + " = " + i * j);
            out.println("<br>");
        }
        out.println("<hr/>");
    }
%>
</body>
</html>

똑같은 알고리즘 3번째 보니까 토나올 것 같지 않나요? ㅎㅎ;
일단 샘플이 재밌어야 공부할 맛 나는데, 제가 나이가 많이 들었나 봅니다. ^^;
다음과 같은 화면이 보이겠죠.
debug.jsp

debug.jsp


소스도 다 되었겠다 이제 디버그 모드로 움직여 봅시다. 아 출발하기 전에 중단점 하나 찍어줘야죠. 브레이크댄스 아니, 브레이크포인트. JSP에서는 scriptlet부분에 중단점이 찍힙니다. HTML태그나 javascript 라인에는 중단점을 찍을 수 없습니다. 다 되는 거 없냐구요? 만드세요. ^^;
아주 방법이 없는 것은 아닙니다. JSP와 javascript의 실행환경이 다르다고 전에 말씀을 드린 적이 있습니다. 때문에 디버그 모드가 따로 분리되어야 됩니다. 서버쪽에서 일어나는 행동은 eclipse의 debug를 사용하고 브라우저쪽의 디버깅은 firebug를 사용하면 양쪽 모두 디버깅을 할 수 있습니다.

적당한 곳에 중단점을 찍으셨으면 보일겁니다. 괜히 bookmark 찍어놓고 "아 조타" 이러지 마세요. 디버깅하고 북마크하고는 찍은 것 빼고는 다르니까요. debug.jsp파일명에서 메뉴를 불러냅니다. 컨텍스트 메뉴 하단에 Debug As 라고 보일 것이고, 확장을 하면 Debug on Server 가 보입니다. 아래 그림을 보시면 소심하게 찍혀있는 중단점 보이시죠? ^^; 전 찍었습니다. 휘릭~! 아, 도망가면 안 되지 강좌는 끝내야죠. ㅎㅎ
debug jsp menu

debug jsp menu



디버그 퍼스펙티브로 볼 것이냐고 이클립스가 물어올 겁니다. 가야죠. 다음과 같이 보일 겁니다. java debug랑 똑같습니다. 다른 게 있다면 Variables쪽에 보이는 것이 엄청 많다는 것과 Tomcat WAS의 Thread가 적나라하게 보입니다.
debug jsp perspective

debug jsp perspective


이후 step over, step into, step return 등은 java와 동일합니다. 이로써 jsp까지의 디버깅 방법은 간략하게 알아보았습니다. 어설픈 설명 보시느라 수고 많으셨습니다.

좋은 하루 되세요.


참고: 빵세님의 Eclipse 3.2 WTP + resin 환경에서 jsp디버그
http://bangse.tistory.com/5

효율적인 JSP 개발을 하기 위해서 어떠한 것들이 필요한지 강의를 할 예정입니다.

JSP 문법 기초 과정은 아닙니다.

JSP 개발하면서 삽질을 덜 하기 위한 JSP 개발 특성들을 정리해서 발표하게 됩니다.

제 강의가 굉장히 즉흥적인 단점이 있는데, 이번에는 딱딱하게 해볼랍니다.


팀 개발에 관한 내용은 지난 4년간 굉장히 강조를 해서 JSP 자체에 대한 내용은 많이 없었습니다.

SI보다는 쇼핑몰에서 레거시 시스템에 대한 변경,유지보수와 신규서비스 추가 개발 경력을 갖고 있습니다.

지난해에는 스프링프레임워크를 이용해서 프로젝트를 수행하기도 했고, 쇼핑몰 특성상 화려한 뷰를 위해서 flash와 flex를 많이 쓰게 되는데, 그에 관련된 서버쪽 data 작업도 많이 했습니다.

이에 대한 경험을 정리해서 발표할 계획입니다. 4월 초순에 자료만들고, 4월19일 목요일 3시간 정도 스케줄을 잡고 있습니다.


이렇게 삽니다. ^^;

Apache Tomcat 트레이아이콘에서 Configure... 를 선택하면 서비스에 등록된 Apache Tomcat의 세부 설정을 조정할 수 있습니다. 2001년 Tomcat 3.x에 비하면 많이 좋아졌죠.
Configure 의 내용을 살짝 알아보겠습니다.

1. General

Apache Tomcat service General

Apache Tomcat service General

Display name:에는 서비스에 등록된 이름입니다. Descriptiion:도 마찬가지로 서비스 창에서 확인할 수 있습니다. Startup type: 을 Automatic 으로 하시면 윈도우 재시동시 Apache Tomcat 서비스가 자동으로 시작됩니다.
Service Status: 에서 확인할 수 있는 정보는 아래 4개의 버튼 Start, Stop, Pause, Restart 와 관계가 있습니다.

2. LogOn
Apache Tomcat service LogOn

Apache Tomcat service LogOn

LogOn 탭은 그다지 많이 사용하지 않을 듯 합니다. 윈도우 XP 계정을 보통 하나로 사용하기 때문이죠. 그래서 skip 합니다. ^^;

3. Logging
가장 많이 신경이 쓰이는 부분이 서버의 로그입니다. 에러메시지도 로그에 상세히 남으니까 작업하면서 로그 파일이 어디에 어떻게 쌓이고 있는지 아는 것이 개발자의 필수사항이죠.
Apache Tomcat service Logging

Apache Tomcat service Logging

로그 레벨은 Error, Warning, Debug, Info 4가지입니다.
Log path: 에 지정된 폴더 경로는 로그 파일들이 쌓이는 곳입니다.
Log prefix:는 로그 파일명의 접두어를 지정하는 곳입니다.
Redirect Stdout:System.out.println(); 으로 찍히는 로그가 쌓이는 파일입니다. auto로 놔두시면 로그파일이 날짜별로 쌓이게 됩니다. 아마도 이 파일을 가장 많이 열어보실 것 같네요. 유닉스나 윈도우의 tail 명령으로 창을 띄어 놓아도 괜찮을 겁니다. 가장 원시적인 디버깅 방법이지만, 저에겐 아직도 꽤 유용합니다.
Redirect Stderr: 은 System.err.println(); 으로 찍히는 로그가 쌓이게 됩니다. 역시 그냥 auto로 놔두셔도 좋습니다.

4. Java
Apache Tomcat은 Java Application입니다. 때문에 JVM(Java Virtual Machine) 이 필요합니다. 꼭 jdk(Java Development Kit) 일 필요는 없고, jre(Java Runtime Environment)로 동작가능합니다. jdk와 jre의 차이점은 javac(java compiler; *.java 파일을 *.class 바이트코드로 컴파일해주는 프로그램)의 유무입니다. jre에는 없습니다. 단순히 class 바이트코드를 실행하는 환경만 제공하는 것이죠. 그렇다면 좀 예민하신 분들은 이런 의문이 생길 수 있습니다. "어, 그럼 jsp가 변환된 servlet 은 누가 컴파일하지?" 해답은 Apache Tomcat 서버에 포함된 라이브러리 중에서 eclipse 쪽에서 만들어진 컴파일러가 포함되어 있다는 것입니다.
하지만, 개발자라면 jdk를 깔아서 작업하시길 권장합니다.

사용자 삽입 이미지

Apache Tomcat service Java

Java Classpath: 에는 Apache Tocmat 이 기동할 때 필요한 *.jar 파일들 경로가 들어가 있습니다. bootstrap.jar 에는 Apache Tomcat 의 시작 어플리케이션, 즉 main() 메소드가 있는 클래스가 있습니다.
Java Options: 는 JVM이 뜰 때 필요한 각종 파라미터를 지정할 수 있습니다. -D... 로 시작하는데, JVM이 인식하는 환경변수라고 생각하시면 됩니다. System.getProperty("catalina.home"); 과 같은 코드로 여기에 설정된 변수의 값을 가져다 쓸 수 있습니다.
Initial memory pool:, Maximum memory pool:, Thread stack size: 정보는 톰캣 서비스시 OutOfMemoryError가 날 경우 적당히 조절해 주면 됩니다. 아무 값도 없을 경우 필자의 경험상 64 MB 정도를 Maximum 으로 사용하는데, 이 값을 128 MB이나 256 MB 등으로 적당히 올려주시면 됩니다. 메모리 크게 잡는다고 좋지는 않으니 튜닝하면서 적당한 것을 찾아보십시오.

5. Startup
톰캣 시작시 옵션을 줄 수 있는 곳입니다. 아까 Classpath:에 있는 bootstrap.jar 파일 내에 있는 org.apache.catalina.startup.Bootstrap 클래스 파일을 실행합니다.
Apache Tomcat service Startup

Apache Tomcat service Startup


6. Shutdown
Startup 과 마찬가지 클래스를 호출하는데, 인수만 start 대신 stop 으로 들어갑니다.
Apache Tomcat service Shutdown

Apache Tomcat service Shutdown


이상으로 Apache Tomcat service의 세부 설정에 대해서 알아봤습니다. 톰캣자체의 문서가 작지 않기 때문에 세세한 내용을 원하신다면 찾아보실 수 있습니다. http://tomcat.apache.org/ 주의하실 것은 버전(jdk, tomcat, os)을 무시할 수 없으니 잘 확인하고 문서를 보셔야 할 것입니다.
개발만하기도 벅찬데, JSP 문법 따라잡기도 벅찬데 톰캣까지, 아니 웹로직이나 OC4j, 웹스피어, 그리고 국산 WAS인 제우스까지 사용법을 아시려면 머리 뽀개지실 겁니다. ^^; 톰캣은 Servlet컨테이너만 있고, EJB컨테이너는 없으니 그나마 다행일 지도 모릅니다. 회사에서 구입하신 WAS라면 교육보내달라고 하십시오. 그리고 알차게 기술지원 받으시는 것이 개발에 집중하는데 도움이 될 것입니다.
JBoss라구요? 흐흐흐 알아서 하십시오. 문서많습니다. ^^; 영어문서는... ㅋㅋ

다음 글 부터는 다시 JSP 쪽의 글을 쓰도록 하겠습니다.

컴퓨터를 껐다 키면 톰캣은 종료가 되어있을 것입니다. 윈도우에서 개발할 때 주로 사용하게 되는 톰캣의 시작과 종료를 알아보겠습니다. apache tomcat 6.0.x 버전 설치할 때 옵션 변경 없이 default로 한 경우에 대한 설명입니다.

서비스에 등록된 Apache Tomcat

서비스에 등록된 Apache Tomcat


윈도우 XP에서 서비스 창을 열어보면 다음과 같이 Apache Tomcat이 등록된 것을 확인할 수 있습니다. 윈도우용 apache tomcat 메뉴에 보면 다음과 같이 이 서비스에 등록된 내용을 조정할 수 있는 메뉴가 있습니다.

윈도우용 Apache Tomcat 메뉴

윈도우용 Apache Tomcat 메뉴


그림에 있는 메뉴 "Monitor Tomcat" 을 클릭하면 트레이아이콘에 깃털모양의 아이콘이 하나 보입니다.
Apache Tomcat Tray Icon

Apache Tomcat Tray Icon

오른 버튼으로 클릭하면 사용할 수 있는 메뉴가 나옵니다. 일단 톰캣을 시작해보겠습니다.
start Apache Tomcat service

start Apache Tomcat service


종료해 볼까요? ^^; 싱겁다고요? ......... <-- 소금. ^^;
stop Apache Tomcat service

stop Apache Tomcat service


그리고, Exit 보이시죠? 이거 클릭한다고 Apache Tomcat 서비스 죽지않습니다. 그냥 Monitor 프로그램만 종료됩니다. ㅎㅎ; 싱겁죠. 이번엔 간장, ______ <- 이크 쏟았네요. ^^;

윈도우용 Apache Tomcat의 시작과 종료를 알아보았습니다. 다음에는 Configure... 를 통해서 설정할 수 있는 내용들을 간략하게 알아보겠습니다. 탭 수가 7개인데, 그리 가볍지는 않을 것 같기도 합니다. ^^;

리눅스 용이나 기타의 톰캣은 매뉴얼 보시기 바랍니다.
지난 번 request 글에 이어지는 내용입니다. 이 글에서는 좀 더 상세하게 알아보기 위해서 fiddler 라는 툴을 사용해보겠습니다. request 와 response 내용은 브라우저에서 볼 수 없지만 이 도구를 이용해서 그 내용을 볼 수 있기 때문입니다. (닷넷프레임워크1.1이 설치되어야 동작합니다.)

fiddlertool 1.2 download
dotnetframework1.1 download

HTTP(HyperText Transfer Protocol HTML전송규약)의 형식에 따라서 서버와 브라우저간의 전송되는 내용을 보면서 작업을 하는 것은 웹 프로그래밍을 보다 더 정확하게 짤 수 있도록 도와줄 것입니다. 아, 툴은 Microsoft사에서 무료로 배포하고 있습니다.

설치를 마치고 나면 브라우저의 메뉴바에 생긴 Fiddler 아이콘을 볼 수 있습니다. 자동으로 프락시역할을 하면서 브라우저서 일어나는 모든 정보들을 들여다 볼 수 있도록 도와줍니다.

본론으로 돌아가서, 이번 글에서는 입력폼을 통해서 데이터를 처리하는 방법을 알아볼 것입니다. 브라우저를 통해서 데이터를 전달하는 방식에는 규약에 여러가지가 있지만 대표적으로 사용되는 방식이 "GET"과 "POST" 입니다.
get방식은 질의어를 보내서 데이터를 조회한다는 성격을 갖고 있고, post는 브라우저에서 서버쪽으로 데이터를 보내서 등록이나 갱신과 같이 서버에 변경을 가한다는 의미를 갖고 있습니다.
표면적으로 get방식의 데이터 전송은 브라우저 주소줄에 전송된 데이터가 표시됩니다. 북마크도 가능합니다. post방식은 요청(request)의 헤더부분에 보이지 않게 데이터가 전송됩니다. 브라우저에서는 post방식으로 전송된 데이터를 처리한 페이지에서 새로고침을 할 경우에 같은 데이터가 서버쪽으로 다시 전송된 것이라는 경고창을 띄워줍니다. 만료된 페이지라는 의미는 이와 같은 의미로 나오게 된 것입니다.

간단하게 로그인 하는 페이지를 만들어 보겠습니다.
id 와 password가 필요하겠죠. 일단 간단한 소스를 보시죠.
<소스 1> loginForm.jsp
<form name="loginForm" action="loginProcess.jsp" method="post">
  id: <input type="text" name="id">
  password: <input type="password" name="password">
  <input type="submit" name="submitButton" value="login">
</form>


form의 이름은 loginForm 으로 주었습니다. action은 폼 값을 전송해서 처리할 페이지주소를 정해줍니다. loginProcess.jsp 라는 파일이 필요하겠지요. 지난 번 글에서 본 대로 request를 이용하면 값을 가져올 수 있을 것입니다. method는 앞서 말한 데이터 전송 방식을 지정합니다. 생략하면 get방식으로 전달이 되는데, id와 password 가 주소줄에 노출되는 황당한 경우를 조심하시기 바랍니다. 이런 경우 post방식을 쓰는 것이 좋습니다. 물론 암호화 패킷을 보내는 SSL을 이용하는 방법이 최고의 방식이지만 지금부터 그걸 알아볼 필요는 없습니다.

입력 항목으로 input태그가 있습니다. type은 text로 지정을 했습니다. input태그의 이름은 id로 정했습니다. 다음 줄에 나오는 password 타입의 input은 말 그대로 암호스럽게 보여줍니다. 입력받는 문자에 대해서 * 이나 까만 동그라미 등으로 문자를 가려서 비밀번호가 노출도지 않도록 해줍니다. 그리고 브라우저 앞 뒤로 이동할 경우 password 타입의 input값은 비워지게 됩니다.

submitButton 이름을 가진 submit버튼은 말 그대로 제출용 버튼입니다. submit 버튼을 클릭하면 action에 지정된 페이지로 데이터를 전송하게 됩니다.

windows XP의 ie6 브라우저에서 본 모습입니다.

login form

login form


버튼을 클릭하면 받아서 데이터를 처리할 페이지를 만들어 보겠습니다.
<소스 2> loginProcess.jsp
<%
  String masterId       = "okadmin";
  String masterPassword = "okpass";
  String id       = request.getParameter("id");
  String password = request.getParameter("password");
  String message = ""; // 결과 메시지
  String script = "";  // 결과 후 실행할 javascript
  boolean isLogin = masterId.equals(id)
                     && masterPassword.equals(password);
  if (isLogin) {
       // 세션에 loginId의 키로 로그인한 id를 넣어둔다.
       session.setAttribute("loginId", id);
       message = "성공적으로 로그인되었습니다.";
       script = "document.location.replace('./loginForm.jsp')";
  } else {
       message = "로그인되지 않았습니다.";
       script = "history.back();";
  }
%><script type="text/javascript">
  // 결과 메시지를 javascript 경고창으로 표시
  alert('<%= message %>');
  <%= script %>
</script>

간략히 설명하면 서버에 있는 masterId와 masterPassword가 입력받은 id와 password와 같은지 확인 후 성공했다면 세션에 로그인 정보를 입력하고 성공 메시지를 보여주고, 실패한 경우 안내 메시지와 이전 페이지로 돌아가서 다시 로그인 하게 하는 프로그램입니다.

파라미터로 "id", "password" 키를 사용했습니다. <소스 1>의 input 태그에 있는 name 속성과 대소문자가 같은 키를 통해서 id 파라미터 값을 String변수 id 에 할당합니다. 마찬가지로 password 값도 할당합니다.

로그인 여부를 true 또는 false 값을 갖는 boolean 변수에 할당하고 이에 따라서 결과 메시지와 실행할 자바스크립트 명령을 각각 message와 script 에 넣었습니다. 페이지 제일 하단에 <script> 태그로 둘러 쌓인 영역은 브라우저에서 실행할 자바스크립트 코드들입니다.

중요한 것은 검은색 코드 부분은 서버에서 실행되는 것이고, 그 결과로 만들어진 파란색 부분은 클라이언트의 브라우저에서 해석되고 실행되는 것을 기억하셔야 됩니다. 웹 프로그래밍을 하면서 이 부분이 자칫 혼동을 일으키기 쉬운 개념입니다. 어느 곳에서 어떤 프로그램이 실행되는지 헷갈리면 javascript의 변수가 왜 jsp에서 못 읽는지 이해가 안 가는 상황에 봉착하게 됩니다. 요점은 jsp는 서버에서 실행되고, 그 결과 만들어진 html과 javascript는 클라이언트에서 실행된다는 것입니다.

alert()로 메시지를 브라우저에서 보여준 후에 실행되는 스크립트는 두 가지입니다.
성공했을 경우는 현재 post로 데이터를 받아서 처리하는 주소를 브라우저 history에서 대치(replace)하는 명령어입니다. document.location.replace("주소") 명령외에 document.location.href="주소"; 명령이 있는데, 차이점은 전자는 설명한대로 현재 자바스크립트가 실행하는 페이지의 브라우저 히스토리를 남기지 않고, 후자는 남겨놓는다는 것입니다.
실패했을 경우는 브라우저 뒤로(back) 버튼과 동일한 역할을 하는 history.back(); 명령을 실행합니다.

<소스1>은 다음과 같이 로그인 여부를 알 수 있도록 수정해보겠습니다.
<소스 1a> loginForm.jsp
<%
  // 세션에서 id를 가져옵니다.
  String loginId = (String)session.getAttribute("loginId");
  if (loginId != null) {
       out.println("loginId:"+loginId);
       return; // jsp 페이지의 실행을 종료합니다.
  }
%>
<form name="loginForm" action="loginProcess.jsp" method="post">
  id: <input type="text" name="id">
  password: <input type="password" name="password">
  <input type="submit" name="submitButton" value="login">
</form>


많이 길어졌네요. 주석을 친절하게 잘 달았기 때문에 설명하지 않아도 되겠죠. ^^;
한 가지 포인트, jsp 실행 중간에 멈추기를 원한다면 return;을 쓰면 됩니다. 원래 return은 메소드에서 결과값을 반환할 때 사용하는 것인데, 이전 글 JSP의 비밀에서 _JspService() 메소드를 기억하신다면 중간에 불쑥 나타난 return; 이 왜 종료하는지 이해가 갈 것입니다. 단 return;을 만나면 뒤에 있는 명령어들은 실행되지 않기 때문에 return; 뒤에 명령어가 있다면 컴파일 에러가 발생합니다. 조건문 안에 있거나 메소드의 맨 마지막에 위치해야 됩니다. 개발하면서 중간에 강제로 return을 쓰기 원하시면 if (true) return; 처럼 돌아가는 방법도 있습니다.

이제 맨 처음에 깔았던 fiddler를 실행하고 데이터 전달 모습을 확인해 보시기 바랍니다.
브라우저 툴바의 도구들을 보면 fiddler 아이콘이 보일 것입니다.
fiddler icon

fiddler icon


이어 나온 창에서 좌측에 보면 호출된 주소들이 보이고 우측에는 선택한 주소에 대한 자세한 정보들을 볼 수 있습니다.

입력 form에 로그인 정보를 입력하고 login 버튼을 클릭해봅니다.
alert 창이 떴을 때 한 줄이 추가된 것을 볼 수 있을 겁니다. 그 alert 창을 닫고 다음 페이지로 이동하면 또 한 줄 정보가 나옴을 알 수 있습니다.
loginProcess.jsp 라인을 선택하면 좌측에서 얻을 수 있는 정보들은 간략 정보입니다. 우측은 좌측 요청주소를 선택하면 그에 대한 응답시간, 내용, 헤더 정보들을 볼 수 있습니다.
header와 html의 비율을 보여주는 Chart 보기는 우측 하단의 파란 링크(Show Chart)를 클릭하면 보입니다.
fiddler data chart

fiddler data chart


우측 두번째 탭을 누르면 보낸 데이터에 대한 form 정보들을 볼 수 있습니다. get방식의 데이터는 = QUERYSTRING ==== 아래 나오고, post방식의 데이터는 = BODY ==== 아래 나옵니다.
fiddler data form

fiddler data form


독립 어플리케이션과 달리 서버와 클라이언트를 오가는 프로그램을 짤 때 이렇게 중간에서 전달되는 값들을 보면서 작업하는 것은 굉장히 유용합니다. 특히 flash나 ajax와 같이 페이지 변동없이 서버와 통신을 하면서 데이터를 주고 받을 때는 이러한 도구 없이 개발하는 것은 많은 시간을 낭비하게 됩니다.

지금까지 request 내장객체와 session 내장객체를 살짝 보았습니다. 다음 글에서는 java beans를 사용해서 데이터를 주고받는 방식을 알아보겠습니다.
이번 글에서는 JSP의 키워드들을 이용해서 프로그램을 짜는 법을 알아보겠습니다.
전체 키워드들 나열해봤자 머리에 남는 것은 별로 없을 테니, 어떤 경우에 어떤 키워드가 사용되는지 예를 들어가면서 설명하겠습니다.

브라우저에 정보를 보내는 방법에 대해서 알아보고 그때 사용되는 명령어들을 알아보겠습니다.
브라우저 주소줄을 통해서 질의문을 보낼 수 있습니다. 이것을 쿼리스트링(query string) 이라고 합니다. 형식은 페이지 주소 뒤에 ? 를 쓰고 그 이후에는 키=값&키=값& 의 쌍으로 데이터를 보낼 수 있습니다.
이렇게 보낸 정보는 jsp 에서 request 객체를 통해서 얻을 수 있는데 다음과 같은 형식입니다.

String name = request.getParameter("name");

쿼리스트링에 있는 키를 파라미터라고도 부릅니다. 파라미터의 값을 불러와서 스트링 변수인 name에 할당하는 것이죠.
이것을 이용해서 프로그램을 하나 짜보도록 합니다.

<소스 1> param.jsp 파라미터를 받아서 출력
안녕하세요. <%= request.getParameter("name") %> 님

저장할 때 UTF-8로 저장하는 것은 잊지 않으셨죠.
브라우저 주소에 다음과 같이 입력해서 호출합니다.
http://localhost:8080/param.jsp?name=kenu

결과 화면은 다음과 같을 것입니다.
parameter 표시

parameter 표시


만약 쿼리스트링이 빠져있다면 어떻게 될까요.
http://localhost:8080/param.jsp
이렇게 말이죠.
아마도 결과는 다음과 같이 나올 것입니다.

안녕하세요. null 님

안녕하세요. null 님


서버가 널널해서 null이 나온 것은 아닐 겁니다.
null의 의미는 없다라는 뜻입니다. 4글자의 단어로 출력이 되었지만
메모리 할당도 없고, 아무 것도 없다는 것입니다.

그럼 다음 주소로 요청을 보내면 어떻게 될까요?
http://localhost:8080/param.jsp?name=
결과는 이렇게 나오죠?

안녕하세요. 님

안녕하세요. 님



헛, 결과가 다르네요. 모두 값이 없는데.
더 정확히 말하면 빈문자열은 값이 있습니다.
null은 파라미터 존재 자체가 없는 것이구요.
프로그램을 좀 다듬어 보겠습니다.


<소스 2> paramNull.jsp
<%
   String name = request.getParameter("name");
   if (name == null) {
       name = "";
   }
%>
안녕하세요 <%= name %> 님

아주 가볍게 파라미터를 처리하는 방법을 보았습니다.
다음 글은 조금 길어질텐데 로그인이나 회원가입 같은 입력 폼을 통해서
데이터를 받는 방법을 설명하겠습니다.

좋은 하루 되세요.
JSP가 어떻게 실행이 되는지 살펴보기로 하겠습니다.

서버에서 html 과 프로그램이 뒤섞여 있는 프로그램을 서버 측 스크립트(server side script) 라고 합니다. 유사한 형태로 PHP(PHP Hypertext Preprocessor) 와 ASP(Active Server Pages), 그리고 ASP.net 이 있습니다.

실행방식에는 크게 두 가지가 있는데, 한 줄 한 줄 실행되는 인터프리터 방식과 일단 파일을 컴파일해서 결과로 나온 파일(자바에서는 class라는 바이트코드형태)로 서비스하는 방식이 있습니다. ASP와 PHP는 전자이고, JSP와 ASP.net은 후자입니다.

JSP를 처음 호출할 때 느리게 느껴지는 이유가 바로 이 때문인데, 최초 호출시 컴파일되면 그 다음부터는 빠르게 실행됩니다.

컴파일은 두 가지 단계를 거칩니다.
1. JSP 파일을 서블릿(servlet)으로 변환(Translation) 하고
2. 이 서블릿을 class 파일로 컴파일

한 번 확인해 보겠습니다.
우리가 만들었던 파일의 흔적을 찾아보면 될 것입니다. 이 디렉토리에 가 보세요.

C:\Program Files\Apache Software Foundation\Tomcat 6.0\work\Catalina\localhost\_\org\apache\jsp

이 중에서 마지막에 만들었던 multiTableImport.jsp의 변환된 서블릿 multiTableImport_jsp.java 파일을 볼 수 있죠. 그리고 그 옆에는 고이 컴파일된 multiTableImport_jsp.class 가 있습니다.
multiTableImport_jsp.java 파일을 한 번 열어보겠습니다.

<소스 1> multiTableImport_jsp.java 내용
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import net.okjsp.study.Math;
public final class multiTableImport_jsp extends org.apache.jasper.runtime.HttpJspBase
  implements org.apache.jasper.runtime.JspSourceDependent {
  private static java.util.List _jspx_dependants;
  private javax.el.ExpressionFactory _el_expressionfactory;
  private org.apache.AnnotationProcessor _jsp_annotationprocessor;
  public Object getDependants() {
  return _jspx_dependants;
  }
  public void _jspInit() {
  _el_expressionfactory = JspFactory.getDefaultFactory().getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
  _jsp_annotationprocessor = (org.apache.AnnotationProcessor) getServletConfig().getServletContext().getAttribute(org.apache.AnnotationProcessor.class.getName());
  }
  public void _jspDestroy() {
  }
  public void _jspService(HttpServletRequest request, HttpServletResponse response)
       throws java.io.IOException, ServletException {
  JspFactory _jspxFactory = null;
  PageContext pageContext = null;
  HttpSession session = null;
  ServletContext application = null;
  ServletConfig config = null;
  JspWriter out = null;
  Object page = this;
  JspWriter _jspx_out = null;
  PageContext _jspx_page_context = null;
  try {
     _jspxFactory = JspFactory.getDefaultFactory();
     response.setContentType("text/html;charset=UTF-8");
     pageContext = _jspxFactory.getPageContext(this, request, response,
        null, true, 8192, true);
     _jspx_page_context = pageContext;
     application = pageContext.getServletContext();
     config = pageContext.getServletConfig();
     session = pageContext.getSession();
     out = pageContext.getOut();
     _jspx_out = out;
     out.write("\r\n");
     out.write("구구단입니다. <br />\r\n");
  // i: 단 변수
  for (int i = 2; i <= 9; i++) {
      // j: 곱수
      for (int j = 2; j <= 9; j++) {
     out.write('\r');
     out.write('\n');
     out.print( Math.getRow(i, j) );
     out.write('\r');
     out.write('\n');
      }  // end for j
     out.write("<hr />\r\n");
  } // end for i
     out.write('\r');
     out.write('\n');
  } catch (Throwable t) {
     if (!(t instanceof SkipPageException)){
       out = _jspx_out;
       if (out != null && out.getBufferSize() != 0)
         try { out.clearBuffer(); } catch (java.io.IOException e) {}
       if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
     }
  } finally {
     if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
  }
  }
}


오~, 뭔가 굉장히 복잡해 보이지 않습니까?
서블릿으로 변환된 JSP입니다. 스크립틀릿에 짜놓은 프로그램 부분도 보이고, 그것을 둘러싼 복잡한 코드들이 보입니다. 표현식으로 된 부분은 out.print() 라는 메소드의 인자로 들어가 있고, 엔터를 친 곳마다 out.write("\r"); out.write("\n"); 이 들어가 있습니다.

상단부터 살펴보면 package는 이전에 설명했기 때문에 넘어갑니다.
import 하는 부분이 나오는데, import javax.servlet.*; 는 javax.servlet 패키지 안에 있는 파일들을 참조하겠다는 것입니다. 파일에는 클래스와 인터페이스(interface) 들이 있습니다. 패키지는 디렉토리마다 고유성을 갖고 있습니다. "하위 디렉토리 모두 다 나와" 처럼 사용할 수는 없습니다. 콕 찝어서 import에 명시해주어야 합니다. 같은 디렉토리의 각각의 클래스를 명시할 수도 있고, 그 수가 많거나 귀찮을 경우 *를 써서 대표할 수도 있습니다.
import net.okjsp.study.Math; ㅎㅎ 우리가 import 한 것도 포함되어 있군요.

주욱 내려가서 try { ... } catch() {} finally {} 로 감싸져 있는 것이 보입니다.
때문에 JSP에서는 일반 자바처럼 예외처리를 하지 않아도 됩니다.

중요한 키워드들이 나옵니다. pageContext, application,config,session,out. 이것들은 JSP 안에서 사용되는 키워드들이기도 합니다. 일단 이런 것들이 있다 정도만 기억하십시오.

public void _jspService(HttpServletRequest request, HttpServletResponse response) 부분을 보겠습니다. 우리가 만든 JSP는 이 안에 모두 포함되어 있습니다. 한 메소드 안에 있다는 것이죠. 따라서 스크립틀릿에서 선언된 변수는 모두 지역변수(local variable)이 될 수 밖에 없는 운명입니다. 변수의 영향범위(scope)에 대해서는 후에 더 자세히 설명하겠습니다.
선언문에서 선언하는 변수나 메소드는 _jspService() 밖에 위치하게 됩니다.
앞으로 수도 없이 만나게 될 두 키워드가 나옵니다. request, response 요청과 응답.
request는 브라우저에서 보내온 정보들을 갖고 있고, response는 브라우저쪽에 보낼 결과정보를 담고 있습니다.

response.setContentType("text/html;charset=UTF-8"); 과 같이 결과로 브라우저에 보내지는 정보의 타입을 지정해서 보내줄 수 있습니다. text 타입이면서 html 형태를 가지고 있고, 문자셋은 UTF-8 이다 라는 것을 알 수 있습니다.

읽느라고 수고 많으셨습니다.
아직 끝이 아닙니다.
됐다구요. 어, 남은 거 아직 많은데...
일단 여기서 마치고, 다음은 JSP의 키워드들을 이용한 프로그램을 짜 보겠습니다.
안녕하세요.
이번 글은 조금 어려울 수도 있습니다. 미리 겁주는 것이지요.
클래스 파일을 이용해서 JSP를 조금 가볍게 만들어 주는 것이 이 글의 목표입니다.

마지막에 사용했던 소스를 가져와서 예를 들겠습니다.
getRow(int i, int j) 메소드를 Math.java 클래스의 메소드로 만들겠습니다.
Math.java의 패키지명은 net.okjsp.study 로 하겠습니다.
마침표(.)로 구분된 각각의 단어는 디렉토리를 의미합니다.
관습적으로 도메인과 연관지어서 큰 단위부터 작은 업무단위로 명명합니다.

메소드는 static 으로 만들겠습니다.
static 메소드란 것에 대해서 일단 공용으로 쓰는 메소드라고
쉽게 생각하시면 좋겠습니다. JSP 소스코드는 다음과 같이 변합니다.

<소스 1> multiTableImport.jsp 내용
<%@page import="net.okjsp.study.Math" %>
구구단입니다. <br />
<%
  // i: 단 변수
  for (int i = 2; i <= 9; i++) {
       // j: 곱수
       for (int j = 2; j <= 9; j++) {
%>
<%= Math.getRow(i, j) %>
<%
       }  // end for j
%><hr />
<%
  } // end for i
%>

JSP에 사용되는 java 파일은 다음과 같습니다. import 당했죠.

<소스 2> Math.java 내용
package net.okjsp.study;

public class Math {
  public static String getRow(int i, int j) {
       return i + " x " + j + " = " + (i * j) + "<br />";
  }
}



이제 문제의 시작입니다. 이 java 파일을 컴파일해서 class로 만들어야 됩니다.
JSP파일은 톰캣이 알아서 서블릿으로 만들고 컴파일했지만, 헉, 언제 그런 일이 생겼냐구요?
이 다음 글에 JSP의 비밀에서 얘기해드릴께요. 일단 class 일병 구하기를 진행하죠.

java파일을 컴파일하기 위해서는 javac파일이 필요합니다. 이 컴파일러는 jdk가 깔려있는 디렉토리 하위에 있는 bin (빈 디렉토리가 아니고 binary파일들이 모여있어서 bin입니다.) 아래에 javac.exe 파일을 확인할 수 있습니다. JRE에는 없습니다. 그래서 제가 설치하기에서 JRE 보다 JDK를 권했습니다.

그럼 Math.java 파일은 어디에 놓을까요. 관례상 ROOT/WEB-INF/src 디렉토리를 만들고,
여기 아래 패키지에 따라 디렉토리를 추가한 후 java 파일을 놓아둡니다.
그럼 컴파일은 어떻게 할까요?
다음과 같은 순서로 컴파일 할 수 있습니다.

시작 > 실행(R)... > cmd

까만 커맨드 창이 뜰 것입니다.

<실행 1> Math.java 파일 컴파일

C:\Documents and Settings\HeoGwangNam>cd "C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\ROOT\WEB-INF\src\net\okjsp\study"

C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\ROOT\WEB-INF\src\net\okjsp\study>"C:\Program Files\Java\jdk1.5.0_10\bin\javac.exe" -d "C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\ROOT\WEB-INF\classes" Math.java

경로를 이동합니다.
javac Math.java
컴파일 되었나요? 조마조마. 탐색기로 classes 하위 디렉토리로 가보면 Math.class 파일이 보입니다.

클래스 컴파일 결과

클래스 컴파일 결과


이제 브라우저에서 확인해 봅시다.
답 나왔나요?

한 가지 더 설명하려고 합니다. java.lang.Math 라는 기본 클래스가 있습니다.
파일명이 같은 클래스인 net.okjsp.study.Math 파일은 패키지명으로 구분이 됩니다.
java1.4 부터 패키지 없는 클래스에 대해서 왕따시키기 시작했습니다.
package 라인이 없는 java 파일은 여러분의 작업에 누를 끼칠 수도 있습니다.

조금 어려우셨을 겁니다. 그러나 걱정마세요. 자꾸 반복해서 나오는 내용입니다.
반복하다 보면 이해도 가고, 머리 속에 새겨질 겁니다.
힘들여 배운 지식은 잘 잊어먹지도 않습니다.

그럼 다음 글에서는 JSP의 숨겨진 비밀, 그 탄생 신화에 대해 알아보겠습니다.

처음으로 JSP프로그램을 짜본 기분이 어떠신가요? 일단 환경 구축하고 hello.jsp와 date.jsp 파일을 만들어서 브라우저에서 결과를 보셨으면 잘 따라오셨습니다.

질문 하나, 프로그램이 뭘까요? What is program?

구글신에게 질문해보니 다음과 같은 해석을 보여주네요.

define:program
(computer science) a sequence of instructions that a computer can interpret and execute;
컴퓨터가 이해하고 실행할 수 있는 일련의 지시사항
대략 감이 오시나요? 프로그램의 기본은 세 가지입니다.
  1. 변수 (Variable)
  2. 제어 (Control)
  3. 반복 (Loop)

변수란 값을 담아 둘 수 있도록 알아보기 쉽게 이름을 붙인 메모리 공간 이름입니다.
제어란 프로그램의 흐름을 조절하는 것인데, if then else 와 같이 만약 ~라면 ~해라 라고 지정할 수 있습니다.
반복이란 같은 동작을 지정한 조건만큼 수행하도록 합니다.
너무 단순화 시켜서 얘기한 것 같지만 이 세 가지만 터득하면 어떤 언어든 간에 기초는 따고 들어갑니다.

앞서 hello.jsp 파일을 복사해서 hello2.jsp 파일을 만들고, 여기에 변수를 사용해보겠습니다.
<소스 1> hello2.jsp 내용

처음 만든 JSP입니다.
<% int c = 123 * 7 ; %>
<%= c %>

조금 복잡해졌습니다. 결과는 아까와 동일합니다. 표현식에 있던 것이 스크립틀릿(scriptlet)사이로 옮겨가면서 c라는 정수형 변수를 사용했습니다. 모두 소문자입니다. 자바가 대소왕자, 아니, ^^; 대소문자에 예민하다는 것 잊지 마세요.
스크립틀릿 안에는 자바 문장이 들어갑니다. 자바 문장은 항상 세미콜론(;)으로 끝납니다.
그래서 7 뒤에 ; 이 붙어있는 것이죠. 스크립틀릿과 표현식의 차이는 <% %> 사이의 = 입니다. =<%= 처럼 딱 붙어 있어야 됩니다. 스크립틀릿은 <% %>로 구분될 수 있습니다.

만만한 구구단 프로그램을 짜 볼까요?
<소스 2> multiTable.jsp 내용

구구단입니다. <br />
<%
   // i: 단 변수
   for (int i = 2; i <= 9; i++) {
       // j: 곱수
       for (int j = 2; j <= 9; j++) {
           int k = i * j;
%>
<%= i + " x " + j + " = " + k %><br />
<%
       }  // end for j
%><hr />
<%
   } // end for i
%>

결과는 여러분들이 옛날에 책받침에서 보았던 구구단 표가 나올 것입니다.

사용자 삽입 이미지

구구단 결과


for 문장은 i라는 int 변수를 선언하고 i가 9보다 작거나 같은 동안 내부 문장을 실행하고 1씩 증가합니다. 곱수인 j 도 비슷한 과정을 겪고 i와j의 곱한 결과를 k에 넣습니다. 꺽쇠와 퍼센트가 많아서 눈이 요란하지만 표현식을 써서 브라우저에 결과를 출력합니다.
이 부분은 문자열 더하기 연산이 일어나는 부분입니다.
표현식 뒤에 <br />을 넣은 이유는 html은 엔터값(줄바꿈문자)을 무시하기 때문이라고 앞서 포인트로 얘기했었습니다. break row의 의미인 것으로 알고 있습니다.
아, 파일명을 multiTable.jsp 이라고 했는데, 가운데 T를 대문자로 쓴 이유가 있습니다. 헝가리안 명명규칙이라고 하는데, 단어와 단어를 붙일 때는 연결부분의 다음 단어 첫자를 대문자로 쓴다는 것입니다. 눈에 더 잘 들어오기 때문이죠. 마치 낙타 등과 같은 표기법이라 하여 camel case 라고도 합니다.


프로그램을 조금 변형해보겠습니다.
<소스 3a> multiTable2.jsp 내용

구구단입니다. <br />
<%
   // i: 단 변수
   for (int i = 2; i <= 9; i++) {
       // j: 곱수
       for (int j = 2; j <= 9; j++) {
           int k = i * j;
           String row = i + " x " + j + " = " + k + "<br />";
%>
<%= row %>
<%
       }  // end for j
%><hr />
<%
   } // end for i
%>

표현식 부분을 String 변수로 바꾸었습니다. 소스를 보니 k 변수가 없어져도 될 것 같습니다.
다음과 같이 또 바꿉니다.

<소스 3b> multiTable2.jsp 내용
구구단입니다. <br />
<%
   // i: 단 변수
   for (int i = 2; i <= 9; i++) {
       // j: 곱수
       for (int j = 2; j <= 9; j++) {
           String row = i + " x " + j + " = " + (i * j) + "<br />";
%>
<%= row %>
<%
       }  // end for j
%><hr />
<%
   } // end for i
%>

k라는 변수를 없앴고, (i * j) 와 같이 대치했습니다.
문자열 연산에서는 숫자의 결과 때문에 괄호로 순서를 명시해주는 것이 필요합니다.

row를 메소드로 뽑아보겠습니다. 메소드(method)? 자바에서는 함수를 메소드라고 합니다.
메소드 이름은 getRow() 이고 인자는 i,j 입니다. 여기서 선언문의 예를 보여드리겠습니다.

<소스 3c> multiTable2.jsp 내용
구구단입니다. <br />
<%
   // i: 단 변수
   for (int i = 2; i <= 9; i++) {
       // j: 곱수
       for (int j = 2; j <= 9; j++) {
           String row = getRow(i, j);
%>
<%= row %>
<%
       }  // end for j
%><hr />
<%
   } // end for i
%>
<%!
   String getRow(int i, int j) {
       return i + " x " + j + " = " + (i * j) + "<br />";
   }
%>

선언문은 <%! 로 시작합니다. 메소드나 멤버변수를 선언할 수 있는데, 가능하면 메소드는 클래스로 보내버리고 멤버변수는 공유되기 때문에 함부로 사용하지 않는 것이 좋습니다. 즉 멤버변수의 남발은 원하지 않는 결과를 초래할 수 있습니다. 일단 이 정도만 기억해 두세요.

소스를 보니 row 변수도 for문 안에서 한 번만 쓰이네요. 없애보겠습니다.
<소스 3d> multiTable2.jsp 내용
구구단입니다. <br />
<%
   // i: 단 변수
   for (int i = 2; i <= 9; i++) {
       // j: 곱수
       for (int j = 2; j <= 9; j++) {
%>
<%= getRow(i, j) %>
<%
       }  // end for j
%><hr />
<%
   } // end for i
%>
<%!
   String getRow(int i, int j) {
       return i + " x " + j + " = " + (i * j) + "<br />";
   }
%>

결과는 동일합니다. 같은 것을 표현하기 위해서 다양한 프로그래밍 방법이 있다는 것을 보여드렸습니다.
이 글에서는 표현식, 스크립틀릿, 선언문을 보았습니다.

다음 번에는 jsp 에서 클래스를 사용하는 법을 알아보겠습니다.

설치가 잘 끝나고 톰캣이 정상적으로 실행되었으면 트레이아이콘 중에 톰캣의 것이 보일 겁니다.

사용자 삽입 이미지

톰캣 트레이아이콘






이제 처음으로 JSP 파일을 만들어보죠. 파일이 놓이는 위치는 정해진 곳이 있습니다.
사용자 삽입 이미지

Tomcat 6.0 Program Directory


시작 > 모든 프로그램(P) > Apache Tomcat 6.0 > Tomcat 6.0 Program Directory 메뉴를 선택하면 창이 하나 뜹니다.
이 창에서 webapps 라는 디렉토리 안에 ROOT 디렉토리로 갑니다. 여기에 파일을 만들 것입니다.
경로는 C:\Program Files\Apache Software Foundation\Tomcat 6.0\webapps\ROOT 입니다.
파일명은 hello.jsp 로 합니다. 아, 자바는 영문 대소문자를 구분합니다. hello.jsp는 모두 소문자입니다.
사용자 삽입 이미지

첫 번째 프로그램 내용입니다.
<소스 1> hello.jsp 내용

처음 만든 JSP입니다.
<%= 123 * 7 %>


파일을 저장할 때 주의할 점이 있습니다.
2가지입니다. 첫 번째는 메모장의 한계인데, hello.jsp 앞뒤로 큰따옴표를 붙여줍니다.
"hello.jsp" 이렇게 하지 않으면 hello.jsp.txt 로 저장이 됩니다.
두 번째는 UTF-8 형식으로 저장합니다.
UTF-8 저장

UTF-8 저장


헉, 그게 뭐냐구요?
인터넷에서 주로 통용되는 코드 형식으로 한글을 처리한다는 정도로만 알아두세요.
메모장에서 JSP파일을 저장할 때 파일명 아래에 인코딩을 "UTF-8"로 해주면 됩니다.


이제 브라우저에서 확인해 볼까요?
브라우저 창의 주소에 http://localhost:8080/hello.jsp 라고 입력해 봅니다. http://127.0.0.1:8080/hello.jsp 라고 입력해도 결과는 같이 나옵니다.
사용자 삽입 이미지

hello.jsp 브라우저 결과


123 곱하기 7 이라는 제법 어려운 계산을 빨리 해 내죠. 컴퓨터가 빠르긴 빠릅니다.

여기서 포인트,
1. JSP의 결과값은 <%= 연산 %>을 통해서 출력된다.
2. 엔터(줄바꿈)의 의미는 없다. html 특성

연산 결과를 출력하는 것을 표현식(expression)이라고 합니다. <%=%> 사이에 수식이 옵니다.
현재의 시간을 출력해볼까요? 조금 자바스러워지는데 코드는 다음과 같습니다. 파일명은 date.jsp 라고 하고, 다음의 코드 내용입니다. 저장은 아까처럼 해주세요.

<소스 2> date.jsp 내용

현재 시간은 <br />
<%= new java.util.Date() %> 입니다.

new 와 java.util.Date() 사이에는 공백이 하나 이상 있어야 됩니다.
결과는 다음과 같습니다.
사용자 삽입 이미지

date.jsp 결과

헛, 영어가 나와서 조금 당황하셨죠. 12/11 03시42분 Korea Standard Time(KST) 라고 나옵니다. 현재 톰캣 서버가 돌아가는 시간이 나옵니다. F5 나 ctrl-R 키로 새로고침 해보세요. 시간 잘 갑니다. ㅎㅎ

다음 글에서는 jsp의 문법적인 요소들을 알아보겠습니다.
일단 메모장으로 고고 합니다.

+ Recent posts