Servlet Method
1. HTTP 데이터 전송 방식
(1) 데이터 전송 구조
1. 브라우저에서 요청 정보를 HTTP 객체에 담아 전송한다.
- 요청과 응답을 주고받는 패킷은, 요청 정보와 응답 정보를 직렬화한 byte 단위의 문자열 데이터로, 인코딩과 디코딩이 필요하다.
2. 전달받은 HTTP 객체를 서버(Tomcat)가 해석하여 요청을 처리할 서블릿을 호출하고,
서블릿의 service() 메소드에서는 req, res 요청 정보를 가지고 처리 로직을 거쳐 응답한다.
- service() 메소드는 ServletRequest와 ServletResponse 객체를 매개변수로 받고,
실제로는 HttpServletRequest와 HttpServiceResponse(HTTP 응답 생성) 객체가 전달된다.
- HttpServletRequest는 ServletRequest, HttpServletResponse는 ServletResponse 타입을 상속받아 구현했으며,
HTTP 프로토콜의 정보를 담고 있기 때문에
실제 사용 시엔는 HttpServletRequest, HttpServletResponse 타입으로 다운캐스팅해서 사용해야 한다.
(2) 데이터 전송 방식
1) get 방식
- URL창에 "?" 뒤에 데이터를 입력하는 방법(= 쿼리스트링)으로 전송한다.
- ex) http://localhost:8080/sendMessage?message=abc&code=20
- 데이터를 header에 포함하여 전송한다.
- 데이터 크기 한계가 있으며 보안에 취약하다.
- 데이터 조회/검색 시 사용된다.
2) post 방식
- 데이터를 body에 포함하여 전송한다.
- 데이터가 URL에 노출되지 않으므로 GET 방식보다 상대적으로 보안적이다.
- 전송 데이터 크기에는 제한이 없지만, 요청 시간의 제한은 있다.
- 데이터 생성/수정 시에 사용된다.
2. 데이터 전송 방식에 따른 Servlet Method
(1) Servlet Method
- 서블릿이 get/post 중 하나로 요청 정보를 전달받으면,
request와 response를 전달하면서 해당하는 처리 메소드(doGet()/doPost())를 호출한다.
-> 즉, 톰캣 서블릿 컨테이너가 요청 url로 매핑된 서블릿 클래스의 인스턴스를 생성하여 service()를 호출하고
HttpServlet을 상속받아 오버라이딩한 현재 클래스의 doGet() 또는 doPost() 메소드가 동적 바인딩에 의해 호출된다.
-> 이때, 서블릿 메소드에 대해 반드시 ServletException 처리를 해줘야 한다.
- HTML에서 method 속성을 이용해 방식을 결정하며, default는 get 방식이다.
(2) doGet() 메소드
: 클라이언트에서 데이터 전송 방식을 get으로 전송하면 호출되는 메소드
- get 요청은 보통 서버의 리소스를 가져오는 행위를 요청하는 http 요청 방식이기에 별도의 데이터가 필요 없어 요청 본문(=페이로드)는 해석하지 않는다.
- get 방식의 데이터는 HTML charset에 기술된 인코딩 방식으로 브라우저가 한글을 이해하고,
'%' 문자로 URLEncoder를 이용해 변환 후 url 요청으로 전송한다.
이 때 header의 내용은 ASCII 코드로 전송되므로,
어떤 언어든 서버의 설정 인코딩 방식과 맞기만 하면 해석하는데 문제가 없으므로 한글이 깨지지 않는다.
- 쿼리스트링으로 보내는 데이터가 노출되므로 보안에 취약하지만, 속도 면에서는 더 빠르므로 검색 기능에 적합하다.
(3) doPost() 메소드
: 클라이언트에서 데이터 전송 방식을 post로 전송하면 호출되는 메소드
- post 요청은 서버의 리소스에 내용을 추가하는 요청이기 때문에 보통 요청하는 데이터가 필요하다.
서버의 리소스에 추가해야 하는 정보를 페이로드에 key, value 방식으로 담아 전송하는데,
header와는 별개로 URLEncoder를 이용하지 않고
페이지 meta에 기술된 charset에 따라 UTF-8로 해석된 데이터를 서버로 전송한다.
- request의 body 영역에 데이터를 담아 보내므로 데이터를 많이 보낼 수 있지만 비교적 속도가 느리다.
- 개발자 도구를 통해 담은 데이터를 볼 수 있으므로 보안이 엄청 뛰어나진 않다.
@WebServlet(value = "/request-service")
public class ServiceMethodTestServlet extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
/* Http 프로토콜의 정보를 담고 있는 타입으로 다운 캐스팅 한다. */
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String httpMethod = request.getMethod(); // 요청 받은 http 메소드가 어떤 메소드인지 판단한다.
System.out.println("httpMethod: " + httpMethod);
if("GET".equals(httpMethod)) {
doGet(request, response);
} else if("POST".equals(httpMethod)) {
doPost(request, response);
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("GET 요청을 처리할 메소드 호출");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("POST 요청을 처리할 메소드 호출");
}
}
<!--index.jsp-->
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Service Method</title>
</head>
<body>
<h1>Service Method의 역할</h1>
<h3>GET 방식의 요청</h3>
<h4>a 태그의 href 속성 값 요청</h4>
<a href="request-service">서비스 메소드 요청하기</a>
<h4>form 태그의 method 속성을 get으로 설정</h4>
<form action="request-service" method="get">
<input type="submit" value="get 방식 요청 전송">
</form>
<h4>form 태그의 method 속성을 post으로 설정</h4>
<form action="request-service" method="post">
<input type="submit" value="post 방식 요청 전송">
</form>
</body>
</html>
Servlet Method Parameter
1. HttpServletRequest
(1) HttpServletRequest의 역할
- Http Servlet을 위한 요청 정보를 제공하는 메소드를 지정한다.
- 인터페이스 구현은 컨테이너가 알아서 설정하므로 메소드만 이용한다.
* HttpServletRequest 객체의 주요 메소드
method 명 | 내용 |
getParameter(String) | client가 전송한 값의 명칭이 매개변수와 같은 값 가져옴 |
getParameterNames() | client가 전송한 값의 명칭 가져옴 |
getParameterValues(String) | client가 전송한 값이 여러개면 배열로 가져옴 |
getParameterMap() | client가 전송한 값 전체를 Map 방식으로 가져옴 |
setAttribute(String, object) | request 객체로 전달할 값을 String 이름-Object 값으로 설정 |
getAttribute(String) | 매개변수와 동일한 객체 속성 값 가져옴 |
removeAttribute(String) | request 객체에 저장된 매개변수와 동일한 속성 값 삭제 |
setCharacterEncoding(String) | 전송받은 request 객체 값들의 Character Set 설정 |
getRequestDispatcher(String) | 컨테이너 내에서 request, response 객체를 전송하여 처리한 컴포넌트(jsp 파일 등)를 가져옴(forward() method와 같이 사용) |
(2) Request Header
1) General header: 요청 및 응답 모두에 적용되지만 최종적으로는 body에 전송되는 것과 관련이 없는 헤더
2) Request header: fetch될 리소스나 클라이언트 자체에 대한 상세 정보를 포함하는 헤더
3) Response header: 위치나 서버 자체와 같은 응답에 대한 부가적인 정보를 갖는 헤저
4) Entity header: 컨텐츠 길이나 MIME 차입과 같은 엔티티 body에 대한 상세 정보를 포함하는 헤더 (요청, 응답 모두 사용되며, 메시지 body의 컨텐츠를 나타내기에 get 요청에서는 일반적으로 사용하지 않는다 - get 요청에서는 본문이 없기 때문)
* getHeader() 메소드로 확인 가능한 값
header 속성 | 값 |
accept | 요청을 보낼 때 서버에게 요청할 응답 타입 |
accept-encoding | 응답 시 원하는 인코딩 방식 |
accept-language | 웅답 시 원하는 언어 |
connection | HTTP 통신이 완료된 후에 네트워크 접속을 유지할지 결정 (기본값: keep-alive = 연결을 열린 상태로 유지) |
host | 서버의 도메인 네임과 서버가 현재 Listening 중인 TCP포트 지정 (반드시 하나가 존재. 없거나 둘 이상이면 404) |
referer | 이 페이지 이전에 대한 주소 |
sec-fetch-dest | 요청 대상 |
sec-fetch-mode | 요청 모드 |
sec-fetch-site | 출처(origin)와 요청된 resource 사이의 관계 |
sec-fetch-user | 사용자가 시작한 요청일 때만 보내짐 (항상 ?1 값 가짐) |
cache-control | 캐시 설정 |
upgrade-insecure-requests | HTTP 메시지 전송 시 보안 적용 |
user-agent | 현재 사용자가 어떤 클라이언트(OS, browser 포함)을 이용해 보낸 요청인지 명시 |
@WebServlet("/headers")
public class RequestHeaderPrintServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Enumeration<String> headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()) {
System.out.println(headerNames.nextElement());
}
System.out.println("accept : " + req.getHeader("accept"));
System.out.println("accept-encoding : " + req.getHeader("accept-encoding"));
System.out.println("accept-language : " + req.getHeader("accept-language"));
System.out.println("connection : " + req.getHeader("connection"));
System.out.println("host : " + req.getHeader("host"));
System.out.println("referer : " + req.getHeader("referer"));
System.out.println("sec-fetch-dest : " + req.getHeader("sec-fetch-dest"));
System.out.println("sec-fetch-mode : " + req.getHeader("sec-fetch-mode"));
System.out.println("sec-fetch-site : " + req.getHeader("sec-fetch-site"));
System.out.println("sec-fetch-user : " + req.getHeader("sec-fetch-user"));
System.out.println("cache-control : " + req.getHeader("cache-control"));
System.out.println("upgrade-insecure-requests : " + req.getHeader("upgrade-insecure-requests"));
System.out.println("user-agent : " + req.getHeader("user-agent"));
}
}
2. HttpServletResponse
(1) 요청에 대한 servlet의 역할
1) HTTP method get/post 요청에 따라 파라미터로 전달받은 데이터를 꺼내 활용할 수 있다. (요청 처리)
2) 서비스 메소드를 호출하여 처리 로직(DB 접속, CRUD에 대한 로직 처리)을 수행한다. (비즈니스 로직 처리)
3) 문자열로 동적인 웹(HTML 태그)페이지를 만들고 스트림을 이용해 내보내거나 응답 데이터를 담아 결과 페이지를 호출하여 응답한다.(응답 처리)
(2) HttpServletResponse의 역할
- 요청에 대한 처리 결과를 작성하기 위해 사용하는 객체
- 인터페이스 구현은 컨테이너가 알아서 설정하므로 메소드만 이용한다.
* HttpServletResponse 객체의 주요 메소드
method 명 | 내용 |
setContentType(String) | 응답으로 작성하는 페이지의 MIME type을 설정 |
setCharacterEncoding(String) | 응답하는 데이터의 CharacterSet을 지정 |
getWriter() | 페이지에 문자 전송을 위한 스트림을 가져옴 |
getOutputStream() | 페이지에 byte 단위의 전송을 위한 스트림을 가져옴 |
sendRedirect(String) | client가 매개변수의 페이지를 다시 서버에 요청함 |
* 실습 1
<!--index.jsp-->
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Parameter</title>
</head>
<body>
<h1>Request Parameter</h1>
<h3>GET 방식의 요청</h3>
<h4>form태그를 이용한 get 방식 요청</h4>
<form action="querystring" method="get">
<label>이름 : </label><input type="text" name="name">
<br>
<label>나이 : </label><input type="number" name="age">
<br>
<label>생일 : </label><input type="date" name="birthday">
<br>
<label>성별 : </label>
<input type="radio" name="gender" id="male" value="M"><label for="male">남자</label>
<input type="radio" name="gender" id="female" value="F"><label for="female">여자</label>
<br>
<label>국적 : </label>
<select name="national">
<option value="ko">한국</option>
<option value="ch">중국</option>
<option value="jp">일본</option>
<option value="etc">기타</option>
</select>
<br>
<label>취미 : </label>
<input type="checkbox" name="hobbies" id="movie" value="movie"><label for="movie">영화</label>
<input type="checkbox" name="hobbies" id="music" value="music"><label for="music">음악</label>
<input type="checkbox" name="hobbies" id="sleep" value="sleep"><label for="sleep">취침</label>
<br>
<input type="submit" value="GET 요청">
</form>
<h4>a태그의 href 속성에 직접 파라미터를 쿼리스트링 형태로 작성하여 get 방식 요청</h4>
<a href="querystring?name=홍길동&age=20&birthday=2021-01-08&gender=M&national=ko&hobbies=movie&hobbies=music&hobbies=sleep">쿼리스트링을
이용한 값 전달</a>
<h4>form태그를 이용한 post 방식 요청</h4>
<form action="formdata" method="post">
<label>이름 : </label><input type="text" name="name">
<br>
<label>나이 : </label><input type="number" name="age">
<br>
<label>생일 : </label><input type="date" name="birthday">
<br>
<label>성별 : </label>
<input type="radio" name="gender" id="male2" value="M"><label for="male2">남자</label>
<input type="radio" name="gender" id="female2" value="F"><label for="female2">여자</label>
<br>
<label>국적 : </label>
<select name="national">
<option value="ko">한국</option>
<option value="ch">중국</option>
<option value="jp">일본</option>
<option value="etc">기타</option>
</select>
<br>
<label>취미 : </label>
<input type="checkbox" name="hobbies" id="movie2" value="movie"><label for="movie2">영화</label>
<input type="checkbox" name="hobbies" id="music2" value="music"><label for="music2">음악</label>
<input type="checkbox" name="hobbies" id="sleep2" value="sleep"><label for="sleep2">취침</label>
<br>
<input type="submit" value="POST 요청">
</form>
</body>
</html>
@WebServlet("/querystring")
public class QueryStringTestServlet extends HttpServlet {
/* 서블릿이 매핑 된 주소로 GET 방식의 요청이 발생하면 doGet 메소드가 호출 된다. */
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/* HttpServletRequest 객체로부터 요청 시 전달한 값을 getParameter 메소드로 추출할 수 있다.
* 인자로 input 태그의 name 속성 값을 문자열 형태로 전달한다.
* (모든 input 태그의 값을 hashmap으로 관리하고 있으므로 원하는 값을 찾기 위해 key 역할 문자열이 필요)*/
String name = req.getParameter("name");
// getParameter의 반환 타입은 String이므로 다른 형식은 parsing이 필요하다.
int age = Integer.parseInt(req.getParameter("age"));
java.sql.Date birthday = java.sql.Date.valueOf(req.getParameter("birthday"));
String gender = req.getParameter("gender");
String national = req.getParameter("national");
// checkbox는 다중으로 값을 입력 받을 수 있으므로 문자열 배열로 전달 된다.
// getParameterValues 메소드를 이용한다.
String[] hobbies = req.getParameterValues("hobbies");
System.out.println("name: " + name);
System.out.println("age: " + age);
System.out.println("birthday: " + birthday);
System.out.println("gender: " + gender);
System.out.println("national: " + national);
System.out.println("hobbies: " + Arrays.toString(hobbies));
}
}
@WebServlet("/formdata")
public class FormDataTestServlet extends HttpServlet {
/* 서블릿이 매핑 된 주소로 POST 방식의 요청이 발생하면 doPost 메소드가 호출 된다. */
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/* HttpServletRequest 객체로부터 요청 시 전달한 값을 getParameter 메소드로 추출할 수 있다.
* 인자로 input 태그의 name 속성 값을 문자열 형태로 전달한다.
* (모든 input 태그의 값을 hashmap으로 관리하고 있으므로 원하는 값을 찾기 위해 key 역할 문자열이 필요)*/
/* Tomcat 9 버전 이하의 경우 POST 요청일 시 인코딩 설정을 하지 않으면 한글 값이 깨진다. */
req.setCharacterEncoding("UTF-8"); // 인코딩 설정을 한 뒤 한글 값을 사용해야 한다.
String name = req.getParameter("name");
// getParameter의 반환 타입은 String이므로 다른 형식은 parsing이 필요하다.
int age = Integer.parseInt(req.getParameter("age"));
java.sql.Date birthday = java.sql.Date.valueOf(req.getParameter("birthday"));
String gender = req.getParameter("gender");
String national = req.getParameter("national");
// checkbox는 다중으로 값을 입력 받을 수 있으므로 문자열 배열로 전달 된다.
// getParameterValues 메소드를 이용한다.
String[] hobbies = req.getParameterValues("hobbies");
System.out.println("name: " + name);
System.out.println("age: " + age);
System.out.println("birthday: " + birthday);
System.out.println("gender: " + gender);
System.out.println("national: " + national);
System.out.println("hobbies: " + Arrays.toString(hobbies));
}
}
* 실습 2
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Response</title>
</head>
<body>
<h1>Response</h1>
<ul>
<li><a href="response">응답 확인하기</a></li>
<li><a href="headers">응답 헤더 확인하기</a></li>
<li><a href="status">응답 상태 코드 확인하기</a></li>
</ul>
</body>
</html>
@WebServlet("/response")
public class ResponseTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/* 1. 요청을 받아서 처리한다. -> req.getParameter 로 전달 받은 데이터 꺼내기
* 2. 비즈니스 로직을 처리한다. -> DB 접속 및 CRUD 로직 처리
* 3. 응답을 처리한다. -> resp 이용해서 동적인 웹(HTML 문서) 페이지를 만들고 스트림을 이용해 내보낸다.
* */
/* 문자열을 이용해 사용자에게 내보내질 페이지를 작성한다. */
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<html>")
.append("<head>")
.append("<title>응답 페이지</title>")
.append("</head>")
.append("<body>")
.append("<h1>안녕 Servlet Response</h1>")
.append("</body>")
.append("</html>");
//resp.setContentType("text/html"); // 응답 데이터의 MIME type 설정
//resp.setCharacterEncoding("UTF-8"); // 응답 데이터의 문자 셋 설정
//resp.setContentType("text/html; charset=UTF-8"); // 2가지 설정을 한 번에 처리 할 수 있다
/* tomcat 10 버전부터는 MIME 타입만 기재해도 자동으로 문자셋이 설정 된다. */
resp.setContentType("text/html");
// 사용자 브라우저에 응답하기 위한 출력 스트림을 response 객체에서 반환 받는다.
PrintWriter writer = resp.getWriter();
writer.print(stringBuilder);
writer.flush(); // 버퍼에 잔류한 데이터 내보내기
writer.close(); // 스트림 반환
}
}
@WebServlet("/headers")
public class ResponseHeaderPrintServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
// resp.setHeader("Refresh", "1");
PrintWriter writer = resp.getWriter();
long currentTime = System.currentTimeMillis();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<html>")
.append("<head>")
.append("<title>응답 페이지</title>")
.append("</head>")
.append("<body>")
.append("<h1>")
.append(currentTime)
.append("</h1>")
.append("</body>")
.append("</html>");
writer.print(stringBuilder);
writer.flush();
writer.close();
/* response header 정보 */
Collection<String> responseHeaders = resp.getHeaderNames();
for (String headerName : responseHeaders) {
System.out.println(headerName + " : " + resp.getHeader(headerName));
}
}
}
@WebServlet("/status")
public class StatusCodeTestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// resp.sendError(404, "없는 페이지입니다. 경로를 확인해주세요.");
resp.sendError(500, "서버 내부 오류입니다. 서버 오류는 개발자의 잘못이고, 개발자는 여러분입니다.");
}
}
* 실습 3
<!--index.jsp-->
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Exception Handler</title>
</head>
<body>
<h1>Exception Handler</h1>
<ul>
<li><a href="show404error">404 에러 확인</a></li>
<li><a href="show500error">500 에러 확인</a></li>
</ul>
</body>
</html>
<!--web.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!-- 프로젝트 내에서 404 또는 500 error가 발생하면 /showErrorPage 요청하도록 설정 -->
<error-page>
<error-code>404</error-code>
<location>/showErrorPage</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/showErrorPage</location>
</error-page>
</web-app>
/* Custom Error Page 작성 */
@WebServlet("/showErrorPage")
public class ExceptionHandlerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Enumeration<String> attrs = req.getAttributeNames();
while (attrs.hasMoreElements()) {
String attr = attrs.nextElement();
System.out.println(attr + " : " + req.getAttribute(attr));
}
int statusCode = (int) req.getAttribute("jakarta.servlet.error.status_code");
String message = (String) req.getAttribute("jakarta.servlet.error.message");
String servletName = (String) req.getAttribute("jakarta.servlet.error.servlet_name");
StringBuilder errorPage = new StringBuilder();
errorPage.append("<h1>")
.append(statusCode)
.append("-")
.append(message)
.append("</h1>")
.append("<p>에러 발생 서블릿 명 : ")
.append(servletName)
.append("</p>");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.print(errorPage);
out.flush();
out.close();
}
}
@WebServlet("/show404error")
public class Show404ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendError(404,"페이지를 찾을 수 없습니당~");
}
}
@WebServlet("/show500error")
public class Show500ErrorServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendError(500,"500 에러는 누구 잘못? 개발자! 개발자는 누구? 여러분!");
}
}
'Spring Framework > Servlet' 카테고리의 다른 글
[Servlet] 5. Cookie & Session (2) | 2024.09.02 |
---|---|
[Servlet] 4. Forward와 Redirect (0) | 2024.08.30 |
[Servlet] 2. Servlet LifeCycle (1) | 2024.08.28 |
[Servlet] 1. Servlet 개요 (1) | 2024.08.28 |