본문 바로가기
뒷북 정리 (국비 교육)/java

[java web] step01. Servlet (경로 요청, servlet, jsp, form 전송)

by 규글 2022. 5. 18.

step01. Servlet

경로 요청

	<h1>index page입니다.</h1>
	<ul>
		<li><a href="hello">hello 요청</a></li>
		<li><a href="/Step01_Servlet/hello">hello 요청</a></li>
		<li><a href="/Step01_Servlet/hello.html">hello.html 요청</a></li>
		<li><a href="study.html">study</a></li>
		<li><a href="/Step01_Servlet/study.html">study</a></li>
	</ul>

 첫 번째 링크인 hello와 두 번째 링크인 /Step01_Servlet/hello 는 같은 경로를 요청한 것이다. 전자는 현재 위치를 기준으로 하는 상대 경로 요청이고, 후자는 server에서의 절대 경로 요청이다. 이때 예시의 /Step01_Servlet/ 이 바로 최상위 경로(root)에 해당한다. 그런데 구분은 잘 해야한다. 세 번째 줄에서 볼 수 있듯 /hello.html 에서 hello.html 이 위치한 곳은 WebContent 가 맞다. 하지만 /hello 에서 hello 가 위치한 곳은 WebContent가 아니다.

 Web browser에서 특정 경로를 web server에 요청하면, 해당 경로에 있는 html file을 읽어서 응답한다. index.html 의 예를 들면 tomcat server가 WebContent의 index.html을 읽어서

응답하는 것이다. 만약 web browser에서의 요청에 응답할 수 없다면 404 에러를 보게 된다. 앞으로 404 나 500 에러를 많이 보게 될 것이라고 했다.(대략 2000번 정도) 500 에러는 server에서의 에러이다.

 

 

 사실 잘못된 경로 요청에 대해서 발견할 수 있는 404 에러 페이지 또한 html 페이지로 만들 수 있다. 이미지의 예시는 고의로 잘못된 경로 요청을 해서 나온 페이지인데, 이런 페이지 또한 따로 html로 작성할 수 있다.

 

 다시 원래 경로 요청으로 돌아와서 보면, hello 가 위치한 자리가 실제로 물리적으로 존재하는 어떤 file에 대한 요청일 수도 있다. 보이는 html file이 될 수도 있고, 이미지인 jpg나 png file이 될 수도 있고, 녹음된 mp3 file이 될 수도 있다. 이런 경우에는 해당 경로에 정확하게 file이 위치해있으면 된다. 물론 html을 전달한다고 해서 해당 file 그대로를 전달한 것이 아니라 html 문자열을 전달한 것이 된다.

 하지만 실제로 존재하는 file이 아니라 그저 hello 만 요청하는 경우라면 어떨까? 이것은 물리적인 경로가 아니라 논리적(가상)으로만 존재하는 경로에 불과하다. 그래서 조금 전에 hello 가 위치한 곳이 WebContent가 아니라고 한 것이다. 현재는 이러한 경로 요청에 응답할 준비가 안되어있다. 때문에 이 상태로 해당 경로 요청을 하게된다면 404 를 보게 된다. 하지만 이러한 경로 요청에 응답할 수 있도록 코딩할 수 있다. 이럴 때 작성하는 것이 Servlet(서블릿) 이다.

 

Servlet

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/*
 *  /hello 요청에 대해 응답을 할 서블릿 정의하기
 *  
 *  1. HttpServlet 클래스를 상속받는다.
 *  2. service() 메소드를 오버라이드 한다.
 *  3. 어떤 요청을 처리할 지 어노테이션(@WebServlet)을 클래스 위에 작성한다.
*/

@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
	
	// 테스트용 생성자
	public HelloServlet() {
		System.out.println("HelloServlet 생성자 호출됨");
	}
	
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) 
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		// super.service(req, resp); // 얘는 지워도 괜찮
		
		// 한글이 깨지지 않도록
		resp.setCharacterEncoding("utf-8");
		// client에게 내가 뭘 응답할지 미리 알리는 역할
		resp.setContentType("text/html;charset=utf-8");
		
		// 클라이언트에게 문자열을 출력할 수 있는 객체의 참조값 얻어오기
		PrintWriter pw=resp.getWriter();
		pw.println("<!doctype html>");
		pw.println("<html>");
		pw.println("<head>");
		pw.println("<meta charset='utf-8'/>");
		pw.println("<title>hello page</title>");
		pw.println("</head>");
		pw.println("<body>");
		for(int i=0; i<5; i++) {
			pw.println("<h1>why???</h1>");
		}
		Date d=new Date();
		String now=d.toString();
		pw.println("<p>현재 시각 : "+now+"</p>");
		pw.println("</body>");
		pw.println("</html>");
		//pw.println("why???");
		pw.close();		
	}
}

 우선 HttpServlet class를 extends 한다. 그리고 service method를 override 하고, 이 class가 어떤 경로 요청에 대한 응답을 처리할 것인지에 대한 annotation인 @WebServlet("xxx") 을 작성해준다.

 

HttpServlet ser = new HttpServlet( );
ser.service(HttpServletRequest, HttpServletResponse);

 완전히 똑같은 것은 아니지만 /hello 요청을 했을 때 위와 같은 과정을 거쳐서 method에 해당하는 작업을 수행하는 것이라고 생각하면 일단 이해가 될 것이라고 강사님이 말했다. 그런데 이렇게 method를 통해서 작성하는 html이 몇 백 줄, 몇 천 줄이라고 한다면 상당히 곤란하고 번거롭지 않겠나? 상당한 단점이라고 할 수 있다.

 하지만 Servlet을 활용한다면 for문과 같은 programming 적인 요소를 가미할 수 있다. 더해서 html file에서는 oracleDB의 data를 출력하는 것이 불가능하지만, servlet에서는 java code를 이용하기 때문에 가능하다. 이것은 상당한 장점이다.

 

 하지만 그럼에도 여전히 작성하는 방법은 지옥이다. 그래서 사용하는 것이 'jsp' 라는 친구이다. 이 jsp는 java server page의 줄임말로, java code도 편하게 작성할 수 있으면서 html 형식도 편하게 작성할 수 있다. 이 jsp 친구를 사용하기에 앞서서 중요한 것은 servlet을 익히고 이해해야 jsp를 이해할 수 있다고 한다.

 

 Project의 상단에 있는 Deployment Descriptor: Step01_Servlet > Servlet Mappings 에 들어가보면 어떤 경로 요청에 대해서 Servlet이 동작하고 있는지를 살펴볼 수 있다. 그리고 이 servlet은 new를 통해 객체를 만드는데, 테스트 용도로 생성자를 만들어서 확인해보면 최초 요청 한 번만 객체를 만드는 것을 확인할 수 있다. 새로 고침을 하거나, 다시 해당 경로 요청을 하더라도 최초의 단 한 번만 객체를 만드는 것을 확인할 수 있다.

 더해서 해당 내용이 언제 web browser에 출력되는지를 break point를 걸어서 확인해보면 모든 내용을 읽고난 후에 web 상에 출력되는 것을 볼 수 있었다.

 

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/sub/fortune")
public class FortuneServlet extends HttpServlet{
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		//super.service(req, resp);
		
		// 한글이 깨지지 않도록
		resp.setCharacterEncoding("utf-8");
		// client에게 내가 뭘 응답할지 미리 알리는 역할
		resp.setContentType("text/html;charset=utf-8");
		
		// 클라이언트에게 문자열을 출력할 수 있는 객체의 참조값 얻어오기
		PrintWriter pw=resp.getWriter();
		pw.println("<!doctype html>");
		pw.println("<html>");
		pw.println("<head>");
		pw.println("<meta charset='utf-8'/>");
		pw.println("<title>오늘의 운세 페이지</title>");
		pw.println("</head>");
		pw.println("<body>");
		pw.println("<h1>오늘의 운세</h1>");
		String[] fortunes={
				"동쪽으로 가면 귀인을 만나요",
				"복권을 사면 당첨될 거에요",
				"코인을 사면 폭락할지도 몰라요",
				"열심히 공부하면 좋은 결과가 있어요",
				"오늘의 행운숫자는 3"
		};
		double ran=Math.floor(Math.random()*5);
		// 오늘의 운수를 랜덤하게 출력
		pw.println("<p>"+fortunes[(int)ran]+"</p>");
		pw.println("<br>");
		pw.println("<a href='../index.html'>index로 가기</a>");
		pw.println("</body>");
		pw.println("</html>");
		//pw.println("잘못된 경로입니다.");
		pw.close();
	}
}

 이번 /sub/fortune 에 대한 요청도 앞선 /hello 요청과 마찬가지이다. 하지만 이렇게 새로 servlet을 만들었다면 이미 실행중인 server를 껐다가 다시 실행해야 servlet이 정상적으로 동작한다.

 

 Servlet의 annotation을 작성할 때 주의해야 하는 것은 링크의 것과는 구분해야 한다는 것이다. 만약 annotation에 이미지의 것이 아니라 링크와 마찬가지로 sub/fortune을 작성한다면, 수많은 오류를 뱉어내면서 server가 시작하지도 못한다.

 

  fortune.html 로 이동했을 때, 해당 페이지에서 다시 index로 이동하고 싶다면 위와 같이 경로를 작성해주면 된다. 현재 fortune.html 을 기준으로 index.html 은 상위 경로에 있기 때문에 앞에 '..' 을 붙여준다. 폴더 이동의 개념과 같다. 이를 servlet에서 작성하려고 한다면 큰 따옴표 안에 큰 따옴표를 작성해야 하는 상황에 놓여 당황하지 않는다. 큰 따옴표 " " 안에 작은 따옴표 ' '를 사용하거나, 안쪽 큰 따옴표 앞에 역슬래쉬 \ 를 작성해서 글자 그대로의 " 를 표현하게 하자.

 하지만 이렇게 작성하는 방식은 너무도 번거롭기 때문에 jsp라는 친구를 이용한다고 했다.

 

JSP

 Eclipse에서는 우클릭 > New > JSP FIle 을 눌러 jsp file을 만들 수 있다. 만들고 보면 가장 윗 줄을 제외하고는 html format과 같은 것을 볼 수 있다.

 

 가장 윗 줄에서 볼 수 있는 '<%' 를 작성하면 '%>' 까지 자동으로 완성되는 것을 볼 수 있을텐데, 이 친구는 맨 윗 줄의 친구와 더불어 web에서 출력되지 않고 페이지 소스 보기에서도 보이지 않는 친구이다. 대신 개행기호 하나만 출력된다. 이 <% %> 사이에 java code를 작성하면 된다.

 

그리고 처음 이 file을 만들어 작성하려고 하면 tab을 통해 자동 완성이 되지 않는 것을 알 수 있을 것이다. 이것은 상단 tab의 Window > Preferences > Emmet 검색 을 해서 extensions 에 jsp를 추가해주면 된다. 이를 통해 html 출력이 불편했던 servlet의 단점을 극복할 수 있게 되었다.

 

<%@page import="java.util.Random"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/sub/fortune.jsp</title>
</head>
<body>
	<h1>오늘의 운세</h1>
	<% 
		String fortune="jsp를  배울거에요!!";
	%>
	<p>jsp를 배울거에요!!</p>
	<p><%out.print(fortune);%></p>
	<p>fortune</p>	
	<%
		String[] fortunes={
				"동쪽으로 가면 귀인을 만나요",
				"복권을 사면 당첨될 거에요",
				"코인을 사면 폭락할지도 몰라요",
				"열심히 공부하면 좋은 결과가 있어요",
				"오늘의 행운숫자는 3"
		};
		Random ran=new Random();
		int ranNum=ran.nextInt(5);		
	%>
	<p><%out.print(fortunes[ranNum]); %></p>
	<p><%=fortunes[ranNum] %></p>
	<p><%=999 %></p>
</body>
</html>

 <% %> 사이에 java code를 작성하면 되는데, 이 사이에서 ctrl + space 를 해보면 이곳에서 사용할 수 있는 local variable이나 기본 객체를 볼 수 있다. 작성하다가 import가 필요한 경우 코드에 빨간 밑줄이 생기던 것을 기억하는가? 똑같이 해당 밑줄에서 ctrl + space를 누르면 가장 상단에 import가 자동으로 작성된다.

 이 안에서 사용 가능한 기본 객체 중 out.print( ) 는 printwriter와 유사하다. 혹은 equal =을 추가한 <%= %> 이것은 out을 작성한 것과 같은 결과를 낸다. 이 위치에 참조되는 내용을 그대로 출력하고 싶을 때 equal = 을 사용한다.

 

 나중에는 servlet과 jsp가 쌍으로 함께 힘을 합쳐서 응답하기도 한다. 이것이 수업 중에 servlet을 통해 jsp가 응답하게끔 해야 의미가 있다고 생각했던 내용이다. 이 시점은 아니고 나중에 할 수 있을 것이라고 말했고, 실제로 주로 그런 구조로 응답하게끔 작업한다고 답해줬다.

 

 Eclipse 작업 폴더에서 필자의 경우는 위 이미지의 경로에서 위의 file들을 볼 수 있었다. 작성했던 jsp file의 정체는 무엇일까? 바로 text file이다. 이 jsp file을 tomcat이 알아서 servlet으로 바꾼다. 그 바꾼 servlet source code가 바로 이미지에서 보이는 java file 이다. 이 java file이 class file을 만들고, 이것으로 응답하게끔 하는 것이다.

 

 이 java source file을 열어보면 중간에 jspService method가 있고, 그 아래에 servlet에 각 줄을 작성했던 삽질을 tomcat이 대신 만들어준 부분을 발견할 수 있다. 이 변환된 file을 수정하거나 하면 오류가 발생한다고 하니 주의한다. 왜냐하면 필자는 수업 중에 여기에 @WebServlet("xxx") 을 추가하는 상상을 했어서 질문했었다.

 이렇게 tomcat이 대신 해주니, jsp의 경우는 따로 mapping 해줄 필요 없이 html file 경로를 위치시키는 것처럼 jsp file 경로를 위치시키면 된다.

 

 정리하면 다음과 같다.

  1. 물리적인 경로에 파일을 요청하는 경우라면 해당 경로에 파일을 위치시키면 된다.
  2. 물리적인 경로를 요청하는 것이 아닌 경우라면 해당 경로 요청에 대한 mapping을 해서 servlet을 만들면 된다.
  3. 그리고 jsp 파일을 만들면 해당 경로의 jsp file을 요청할 때 tomcat이 알아서 servlet으로 변환해주기 때문에 따로 mapping이나 class가 필요 없다.

그런데 직접적으로 파일을 요청하는 경우에는 사실 servlet이 필요하다. 그런데도 따로 servlet을 만들지 않는 이유는 tomcat에 이런 것들을 처리해주는 기본 servlet이 존재해서 알아서 응답해주기 때문에 따로 필요하지 않은 것이다. 그래서 물리적으로 존재하는 파일은 요청 경로만 맞으면 된다.

 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test.html</title>
<!-- javascript loading -->
<script src="js/selector.js"></script>
</head>
<body>
	<h1>test page</h1>
	<!-- image loading -->
	<img src="images/kim1.png" alt="" />
	<!-- audio loading -->
	<audio src="effectSound/birddie.mp3" controls></audio>
</body>
</html>

 Web browser의 입장에서는 어떨까? Web browser는 사실 어떤 file이 날아올 지 알지 못한다. 그런데 어떤 file이 날아올 지 알고 화면에 보여주고 녹음 file의 경우는 play까지 해줄까? 이것은 web browser가 어떻게 동작하는지 알 필요가 있다.

 위 예시는 test.html 이다. 해당 경로를 tomcat server에 요청하면 일단 html 문자열을 응답한다. 그 내용에는 src 가 3개 존재한다. 그렇다면 내용을 받아야하니까 이 경로도 다시 요청해야하지 않을까? 그래서 추가 요청이 필요하다.

 

 해당 page에서 우클릭 > 검사 > Newwork tab 을 열고 새로고침해서 확인해보면, 처음부터 file을 읽어들이는 것이 아니라 처음에는 html 문자열에만 응답한다. 이 html 문자열을 다 읽고 난 후에 file 경로에 대한 내용을 요청하는 것을 볼 수 있다.

 

Form 전송

	<!-- 
		- form 사용법
		1. action 속성의 값은 form을 제출했을 때 요청하는 경로가 된다.
		2. method 속성의 값은 전송 방식을 지정한다. 생략하면 get이 default이다.
		3. form의 자손 요소 중에 type="submit" 버튼을 누르면 form이 제출된다.
		4. 전송 방식에는 get과 post가 있는데,
		   get은 입력한 정보를 주소창에 달고 가는 방식이고
		   post는 요청의 몸통에 달고 가는 방식이기 때문에 주소창에 보이지 않는다.
	 -->
     
	<form action="/Step01_Servlet/send" method="get">
		<label for="msg">메세지</label>
		<input type="text" id="msg" name="msg" />
		<input type="text" id="gender" name="gender"/>
		<button type="submit">전송</button>
	</form>

 가장 처음 html 정리를 할 때 form 이라는 친구를 만난 적이 있다. 이번에는 이 form 이라는 친구를 가지고 server와 어떻게 티키타카를 하는지 정리해보려고 한다. 이 html의 form은 client에게 입력 양식을 제공하고, 그것을 server로 넘겨서 무엇인가 작업하고 싶어서 사용한다. action 속성에는 요청하고자 하는 경로를 작성하고, method 속성에는 get/post 를 작성해준다. 해당 속성을 작성하지 않을 경우 default는 get이다. 두 개의 차이는 입력한 정보를 주소창에 달고 가는 방식이 get이라면, post는 요청 자체에 달고 가는 방식이기 때문에 주소창에 정보가 드러나지 않는다. 만약 submit button이 form 바깥에 있으면 내용이 server로 전송되지 않기때문에 반드시 form 안에 button이 위치하도록 작성한다. 작성된 경로에 따라 servlet을 만들어 주거나 jsp file을 만들어준다.

 

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/send")
public class SendServlet extends HttpServlet{
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) 
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		//super.service(req, resp);
		// client가 전송한 문자열 추출하기
		String str=req.getParameter("msg");
		System.out.println("전송된 문자열 : "+str);
		
		// 간단한 임시 응답
		PrintWriter pw=resp.getWriter();
		pw.println("okay~");
		pw.close();
	}
}

 먼저 servlet을 만드는 경우이다. Server에서는 client에서 전송한 정보를 읽어내야하는데, 그 역할을 servlet이 담당하는 것이다. Override 한 service method에 인자로 전달한 HttpServletRequest 객체의 getParameter("xxx") method를 통해 정보를 읽어낼 수 있다. Method 안에 작성할 내용은 form 의 name 에 작성한 value와 일치시킨다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	// msg라는 parameter name으로 전송된 문자열 추출
	String str=request.getParameter("msg");
	// console 창에 출력
	System.out.println("전송된 문자열 : "+str);
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/send.jsp</title>
</head>
<body>
	okay!
</body>
</html>

 다음은 jsp file을 만드는 경우이다. <% %> 안쪽을 servlet의 service method 안이라고 생각하고 작성하면 된다. Servlet이든 jsp file이든 이렇게 작성하고 client와 티키타카 하는 것을 보면 server의 console 창에 client에서 입력한 값이 출력되는 것을 볼 수 있다.

 

		// 문자열을 추출하기 전에 한글이 깨지지 않도록 encoding 설정
		req.setCharacterEncoding("utf-8");

 Post 방식은 다 좋은데 한글이 깨지기 때문에 위와 같은 코드가 더 들어가야 한다. Post 방식의 경우는 id나 password 입력 시 해당 내용이 주소창에 보이지 않기 때문에 좋다. Get/post 는 각각 가져오기/보내기 의 의미로, 이름 자체가 method의 주 목적인 것이다. 그래서 form을 통해 server로 보내는 것이 주 목적일 것이므로 대부분은 post일 것이라고 했다.

 

		<li><a href="/Step01_Servlet/send?msg=hello">get 방식 전송 test</a></li>
		<li><a href="/Step01_Servlet/send.jsp?msg=bye">get 방식 전송 test</a></li>

 하지만 server의 입장에서는 get 방식으로 주소창에 직접 전송하나, form을 통해 전송하나 그것을 구분할 수 없다. 단지 client가 해당 값을 직접 입력하는가 아닌가의 차이일 뿐이다.

 

		<li><a href="/Step01_Servlet/shop/buy.jsp?num=999&type=A">999번 물품 구입하기</a></li>
	<br>
	<form action="/Step01_Servlet/shop/buy.jsp" method="get">
		<label for="msg">메세지</label>
		<input type="text" id="num" name="num" />
		<input type="text" id="type" name="type"/>
		<button type="submit">전송</button>
	</form>

 또한 '&' 를 이용해서 여러 개의 정보를 전송할 수도 있다. 이는 form에 여러 내용을 입력해서 전송하는 것과 같다.

 

 아주 쉬운 예시 페이지가 있다. 링크를 눌러도 정보가 전송이 된다는 것은 이미 해당 링크에 전송될 내용을 미리 적어둔 것이라고 할 수 있다. 이외에도 비로그인 상태에서 로그인이 필요한 경우 로그인 페이지로 redirect 되는 것도, 새로 창이 열리는 것도 나중에 다 배울 예정이라고 말했다.

'뒷북 정리 (국비 교육) > java' 카테고리의 다른 글

Tomcat server 다운로드 / 몇 가지 설정  (0) 2022.05.18
[Java] step20. String  (0) 2022.05.11
[Java] step19. JDBC  (0) 2022.05.03
[Java] step18. Socket  (0) 2022.04.28
[Java] step17. InputOutput  (0) 2022.04.25

댓글