JSP/Servlet - doGet, Servelet LifeCycle, request, response, URL 패턴!

Servlet - doGet(), doPost(), service()

클라이언트가 서버에게 html 페이지를 요청할 때
http 프로토콜 요청종류는 여러가지 있지만 대표적으로 GET, POST 방식 요청이 있다.

<form action="MethodServlet" method="get">
<form action="MethodServlet" method="post">

서블릿 객체는 요청 종류에 따라 doGet(), doPost() 메서드를 호출한다.

/**
 * Servlet implementation class MethodServlet
 */
@WebServlet("/days02/MethodServlet")
public class MethodServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public MethodServlet() {
        super();
        // TODO Auto-generated constructor stub
        System.out.println("MethodServlet init....");
    }

	/**
	 * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response)
	 */
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// TODO Auto-generated method stub
		super.service(req, resp);
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html; charset=UTF-8");
		System.out.println("called doGet()...");
		PrintWriter out = response.getWriter();
		String msg = request.getParameter("msg");
		out.append(msg);
	}
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding("UTF-8"); //응답받은 문자열을 UTF-8로 디코딩
		response.setContentType("text/html; charset=UTF-8"); //응답할 html페이지를 UTF-8로 설정
		doGet(request, response);
		System.out.println("called doPost()...");
	}
}

service 오버라이드 메서드는 GET방식, POST방식 상관없이 호출되는 메서드로
service메서드가 서블릿 객체에 오버라이딩 되어있다면 doGet(), doPost()가 호출되기 전에 service 메서드가 호출된다.
get, post 방식 상관없이 공통적인 작업을 해야 한다면 service메서드를 사용토록 하자.

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	// TODO Auto-generated method stub
	super.service(req, resp);
	System.out.println("Something Service for GET, POST");
}

부모클래스의 super.service 메서드를 호출하면 GET, POST방식에 맞추어 doGet(), doPost()메서드가 호출된다.

Servlet - init, destroy

서블릿 객체의 생성, 삭제과정은 다음과 같이 이루어진다.

  1. 클라이언트 요청시 서블릿 객체 생성
  2. 서버 종료전 서블릿 객체 삭제

한번 서블릿 객체가 생성되어 메모리에 올라가면 서버가 내려갈 때 까지 남아 있기 때문에 다음 클라이언트의 요청시에는 다시 생성할 필요 없다.

@Override
public void init() throws ServletException {
    super.init();
	System.out.println("called init()...");
}

@Override
public void destroy() {
    super.destroy();
	System.out.println("called destroy()...");
}

init(), destory() 메서드를 오버라이딩 해서 서블릿 객체 생성, 삭제 시에 실행할 코드를 작성할 수 있다.

서버 시작시 Servlet 객체 올리기

객체를 생성해서 메모리에 올리는 과정이 제일 오랜 시간이 필요한 작업이다.
DB Connection 같은 경우 이 과정에서 매우 많은 시간을 필요로 한다.

클라이언트 요청이 들어올 때마다 DB Connection 객체를 생성해 메모리에 올리려면 답답함으로
서버 시작과 동시에 필요한 객체는 메모리에 생성될 수 있도록 설정할 수 있다.

public class DBCPInit extends HttpServlet{

    private static Connection connection = null;

    @Override
    public void init() throws ServletException {
        super.init();
        loadJDBCDriver();
        initConnectionPool(); // connection = new Connection();
    }
    ...
    ...
}

위와 같은 서블릿 객체가 있을 때 서버 시작과 동시에 생성하려면 web.xml 에 다음과 같이 설정을 추가해야 한다.

<servlet>
	<servlet-name>DBCPInit</servlet-name>
	<servlet-class>days02.DBCPInit</servlet-class>
	<load-on-startup>1</load-on-startup>
</servlet>

load-on-startup 태그안의 숫자는 객체 생성 순번이다.

DBCPInit 서블릿 객체를 서버 시작과 동시에 1번째로 메모리에 생성한다.

DB연결 서블릿 객체 생성

java 에서 DB와 연결하려면 JDBC때 했던 과정이 필요하다.

https://kouzie.github.io/jdbc/JDBC.-1일차/#jdbc-driver

다른점은 ojdbc6.jar 파일을 build path에 추가하는 것이 아닌 \WebContent\WEB-INF\lib 위치에 추가해야 한다는것.
이제 DB 서블릿 객체에 db url, user명, password와 로드한 jdbc driver를 사용해 Connection 객체를 만들면 된다.

url, user명, password는 수시로 바뀔수 있기 때문에 서블릿 클래스의 .java파일 안에 문자열로 하드코딩하지 않고
web.xml(배포서술자)에서 전달해주는 값을 사용한다.

web.xml에서 <init-param>태그를 사용하면 DB연결용 서블릿 객체에게 데이터를 전달할 수 있다.

<servlet>
	<servlet-name>DBCPInit</servlet-name>
	<servlet-class>days02.DBCPInit</servlet-class>
	<init-param>
		<param-name>url</param-name>
		<param-value>jdbc:oracle:thin:@172.17.107.68:1521:xe</param-value>
	</init-param>
	<init-param>
		<param-name>user</param-name>
		<param-value>scott</param-value>
	</init-param>
	<init-param>
		<param-name>password</param-name>
		<param-value>tiger</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup> <!-- 마지막에 위치해야 한다.   -->
</servlet>

web.xml로부터 전달받은 파라미터는 ServletConfiggetInitParameter()메서드를 사용해 가져올 수 있다.

HttpServletServletConfig interface를 구현한다.

public class DBCPInit extends HttpServlet{
	private static Connection connection = null;
	@Override
	public void init() throws ServletException {
		super.init();
		loadJDBCDriver();
		initConnectionPool();
	}

	private void initConnectionPool() {
		String url = this.getInitParameter("url");
		String user = this.getInitParameter("user"); 
		String password = this.getInitParameter("password"); 	
		try {
			connection = DriverManager.getConnection(url, user, password);
			System.out.println("create Connection success");
		} catch (SQLException e) { 
			System.out.println("create Connection error");
			e.printStackTrace();
		}
	}

	private void loadJDBCDriver() {
		String className = "oracle.jdbc.driver.OracleDriver";
		try {
			Class.forName(className);
			System.out.println("JDBC Driver loading complete!");
		} catch (ClassNotFoundException e) { 
			e.printStackTrace();
		}
	}
}

만약 getInitParameter매개변수로 없는 parameter이름을 넣으면 null을 반환한다.

Servlet - Http 요청 반환객체

HttpServletRequest

GET 방식에선 URL, POST 방식에선 Http Body 에 각종 파라미터들이 포함되어 서버로 전달되는데
서버에서 해당 파라미터를 읽어오려면 request객체의 getParameter...()메서드를 사용해야 한다.

request.getParameter(String name) - name에 해당하는 파라미터의 value를 반환

request.getParameterNames() - 클라이언트가 보낸 파라미터의 모든 name을 반환, 반환값은 Enumeration<String>이다.

request.getParameterValues(String name) - name에 해당하는 파라미터의 모든 value를 반환,
다중선택이 가능한 checkbox, select 등의 HTML 태그 사용시 getParameterValues 메서드를 사용한다.
당연히 반환값은 String[]이다.

request.getParameterMap() - namevalue가 매칭되는 Map<String,String[]> 컬렉션으로 반환
checkbox, select 가 포함되어 있을 수 도 있음으로 value 타입은 String[] 이다.

JSP 에선 아래처럼 사용

<% 
	Enumeration<String> en = request.getParameterNames();
	while(en.hasMoreElements())
	{
		String pname = en.nextElement();
%>
	<li><%= pname %></li>
<% 
	}
%>

다음과 같이 사용할 수 있다.

<% 
    Map<String,String[]> map = request.getParameterMap();
    Set<Entry<String, String[]>> set = map.entrySet();
    Iterator<Entry<String, String[]>> ir = set.iterator();
	while(ir.hasNext())
	{
		Entry<String, String[]> entry = ir.next();
%>
	<li><%= entry.getKey() %> - <%= String.join(", ", entry.getValue()) %></li>
<% 
	}
%>

파라미터 외에도 request객체는 클라이언트(브라우저)요청에 관한 여러 정보를 가져온다.

<%= request.getRemoteAddr() %>
<!-- 0:0:0:0:0:0:0:1 클라이언트의 IP주소를 가져온다. (ipv6)  -->

<%= request.getContentLength() %>
<!--요청한 정보의 길이를 반환한다, 
-1이 의미하는 것은 전송된 데이터의 길이를 알 수 없다는 뜻 -->

<%= request.getCharacterEncoding() %>
<!-- 요청한 정보의 인코딩 타입을 반환
null의 의미하는 것은 기본값, 설정 된것이 없다는 뜻 -->

<%= request.getContentType() %>
<!-- 요청한 정보의 컨텐츠 타입을 반환 -->

<%= request.getProtocol() %>
<!-- 요청한 정보의 인코딩 타입을 반환, HTTP/1.1 출력
http프로토콜에 1.1버전으로 요청-->

<%= request.getRequestURL() %>
<!-- 요청한 정보의 인코딩 타입을 반환, //jspPro/days02/ex04.jsp-->
<%= request.getRequestURI() %>
<!-- 요청한 정보의 인코딩 타입을 반환 -->

<!-- URI(Uniform Resource Identifier)는 존재하는 자원을 식별하기 위한 
일반적인 식별자를 규정하기 위한 것으로 URL에서 HTTP프로토콜,호스트명,port 번호를 제외한 것이다. -->

<%= request.getContextPath() %>
<!-- ContextPath 반환, /jspPro-->

<%= request.getServerName() %>
<!-- 연결할 때 사용한 서버이름(도메인)반환, localhost-->

<%= request.getServerPort() %>
<!-- 요청한 정보의 인코딩 타입을 반환, 80-->

http 프로토콜의 헤더정보를 읽어올 수 있다.

내장 객체 설명 리턴 타입
getHeader() 지정한 이름의 헤더값을 얻어옴 String
getHeaders() 지정한 이름의 헤더 목록을 얻어옴 Enumeration<String>
getHeaderNames() 모든 헤더 이름을 얻어옴 Enumeration<String>
getIntHeader() 지정한 해더값을 정수로 얻어옴 int
getDateHeader() 지정한 헤더값을 시간값으로 얻어옴 long
<% 
	Enumeration<String> en = request.getHeaderNames(); 
	en.toString();
	while(en.hasMoreElements())
	{
		String hname = en.nextElement();
		String hvalue = request.getHeader(hname);
%>
<li><%= hname %> - <%= hvalue %></li>
<%
	}
%>

출력값

host - localhost
connection - keep-alive
upgrade-insecure-requests - 1
user-agent - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
accept - text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
accept-encoding - gzip, deflate, br
accept-language - ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
cookie - JSESSIONID=DBFB435E266CEEA73EB004BBF0F7F6D7; ID_save=admin

헤더에는 referer이라는 전에 접속했던 url 값을 가지고 있는 속성이 있다.

자신의 사이트로 접속해오기전 클라이언트가 있던 사이트 주수로를 request.getHeader("referer")로 얻어올 수 있다.

위의 getHeaderNames에서 출력 안된 이유는 이전사이트 접속없이 바로 접근했기 때문.

HttpServletResponse

response 객체는 서버에서 클라이언트에게 응답할때 HTTP Response 를 객체로 표현하기 위해 사용되는 객체이다.

  1. 헤더에 추가정보 입력
  2. 리다이렉트(redirect) 기능

클라이언트를 리다이렉트 시키기 위해 response.sendRedirect 메서드를 사용한는데 url 에 한글이 포함되는경우 인코딩을 해야한다.

String url = URLEncoder.encode("한글이 포함된 url", "UTF-8");
response.sendRedirect(url);
//한글 문자열을 `encode()`메서드로 변환시킨 후 url에 붙여놓도록 하자.  

request 디코딩, response 인코딩

클라이언트가 input태그에 데이터를 담아 서버로 submit 하면 서버는 request 객체를 사용해 클라이언트로부터 받은 데이터에 접근한다.

그리고 reponse 객체를 통해 클라이언트에게 html 페이지를 제공한다.

웹브라우저가 서버에게 파라미터를 보낼때 인코딩하면
서버는 받은 파라미터를 디코딩해서 읽어온다.

request로부터 데이터를 받을때 받은 파라미터를 디코딩하고,
response로 데이터를 전달할 때 다시 인코딩하여 웹브라우저에게 html 페이지를 전달한다.

디코딩, 인코딩 과정에서 한글이 다음과 같이 깨질 수 있다.

<form action="ex05_ok.jsp" method="post">
	이름: <input type="text" name="name" autofocus="autofocus" value="홍길동"/><br><br>
	성별: <input type="radio" name="gender" value="m" checked="checked"/>남자
			<input type="radio" name="gender" value="w"/>여자<br><br>
	좋아하는 동물: 
	<input type="checkbox" name="pet" value="dog" checked="checked"/><input type="checkbox" name="pet" value="cat" checked="checked"/>고양이
	<input type="checkbox" name="pet" value="pig" />돼지<br><br>
	
	기타: <textarea name="etc" id="etc" cols="30" rows="10">기타사항 없음....</textarea><br>
	<input type="submit" />
	<input type="reset" />
</form>

value 속성에 한글을 막 집어넣고 해당 데이터를 출력하는 jsp 페이지를 요청해보자.


<h3>request.getParameterMap</h3>
<% 
    Map<String,String[]> map = request.getParameterMap();
    Set<Entry<String, String[]>> set = map.entrySet();
    Iterator<Entry<String, String[]>> ir = set.iterator();
	while(ir.hasNext())
	{
		Entry<String, String[]> entry = ir.next();
%>
	<li><%= entry.getKey() %> - <%= String.join(", ", entry.getValue()) %></li>
<% 
	}
%>

출력값

request.getParameterMap
name - 홍길동
gender - m
pet - dog, cat
etc - 기타사항 없음....

영어 m, dog, cat을 제외하곤 모두 깨졌다.

request.getParameterMap()메서드를 통해 받은 파라미터를 디코딩하는데 여기서 문제가 생긴다.

request 객체의 기본 캐릭터 셋은 ISO-8859-1이다!
클라이언트가 한글이 포함된 UTF-8 형식의 Http Request 를 보냈는데 저런 서유럽 방식으로 디코딩 하니 한글이 깨질 수 밖에 없다!

따라서 request.setCharacterEncoding("UTF-8");을 통해 디코딩 방법을 UTF-8로 설정해야한다.

GET 방식은 파라미터를 URL 에 포함시켜 보내기에 Base64 인코딩 방법을 사용해 아스키 코드로 변경되어 보내진다.

아스키 코드를 디코딩 하는 방법은 was 마다 다르며 우리가 사용하는 tomcat 8.5 의 경우 UTF-8 방식을 사용하기 때문에 GET 방식을 사용할 때에는 setCharacterEncoding으로 디코딩 방식을 바꿀 필요가 없다

또한 setCharacterEncoding 방식으로 캐릭터 셋이 바뀌지도 않기 때문에 만약 GET 인코딩 방식을 바꾸려면
apache-tomcat-8.5.39\conf\server.xml 파일에서 다음과 같이 속성을 추가해주어야 한다.

<Connector port="80" protocol="HTTP/1.1"
	connectionTimeout="20000"
	redirectPort="8443" 
	useBodyEncodingForURI="true"/><!-- 추가된 속성 -->

변경시에 request.setCharacterEncoding() 메서드로 디코딩 방법 변경 가능하다.

마음 편하게 JSP 파일의 경우 html 태그 위에, 서블릿의 경우 doPost, doGet 메서드 시작하자 마자 request.setCharacterEncoding("UTF-8") 호출하는 것이 좋다.

request.setCharacterEncoding("UTF-8"); 
//응답받은 파라미터를 UTF-8로 디코딩

response.setContentType("text/html; charset=UTF-8"); 
//응답할 html페이지를 UTF-8로 설정

URL 패턴

서블릿 객체를 사용하기 위해 web.xml에서 다음과 같이 Servlet객체와 url을 매핑해주었다.

<servlet>
	<servlet-name>now</servlet-name>
	<servlet-class>days01.NowServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>now</servlet-name>
	<url-pattern>/days01/now</url-pattern>
</servlet-mapping>
<servlet>

<url-pattern>태그 안에서 여러 방식으로 패턴을 지정할 수 있다.

1. / 로 시작해서 /* 로 끝나는 경우

ex: <url-pattern> /foo/bar/* </url-pattern> url시작이 contextPath뒤에 /foo/bar/로 시작하면 매핑된 서블릿을 반환한다.

2. *.확장자 형식을 사용하는 경우

ex: <url-pattern> *.do </url-pattern>
.do확장자로오는 url 요청이 들어오면 매핑된 서블릿을 반환한다.

참고: /foo/bar/*.do 이런형식으로 사용하지 못한다.

3. 오직 / 하나 사용

ex: <url-pattern> / </url-pattern> 기본 서블릿을 매핑시킨다.

4. 그외 패턴

ex: <url-pattern> /days02/Test </url-pattern> 우리가 지금까지 사용했던 경로가 완변히 일치해야하는 매핑방식이다.

클라이언트 입장에서 루트주소는 http://localhost/이기 때문에 url패턴으로 이동시 /Context root명/을 붙여주어야 한다.

반면 서버입장에서 루트주소는 http://localhost/Context root명/이다.

서버의 경우 기본적으로 Context root위에서 시작되기 때문에 둘의 구분을 잘 해야 404error를 쉽게 피할 수 있다.

카테고리:

업데이트: