문제
먼저 문제 원본은 아래와 같다.
문제에서 실제로 구현해야 하는 조건만 다시 정리하면 이렇다.
webapp아래에clientexam폴더를 만들고visitorForm.html을 둔다.form태그의method는post로,action은Servlet매핑 경로에 맞게 수정한다.- 이름과 내용 입력값을
Servlet에서 받아야 한다.POST요청을 처리하는Servlet을 만들어야 한다.- 날짜는
java.time.LocalDate객체로 구한다.- 입력한 이름, 날짜, 내용을 브라우저 화면에 출력해야 한다.
- 문제 원본은
VisitorServlet,lab,/visitor기준이지만, 이번 실습에서는 연습용으로VisitorServletPractice,practiceLab,/visitorPractice로 맞춰 진행했다.
문제 해석
이 문제는 그냥
HTML폼 하나 만들고 끝나는 문제가 아니다.
사용자가 브라우저에서 이름과 내용을 입력하면, 그 값이POST방식으로Servlet으로 넘어가고,Servlet이 그 값을 받아서 다시 브라우저에 결과를 출력하는 전체 흐름을 구현하는 문제다.
즉 이 실습의 핵심은 화면 입력과 서버 처리를 연결해보는 데 있다.
form의method,action, 입력 태그의name,Servlet의URL mapping,request.getParameter(),PrintWriter출력까지 전부 이어져야 정상 동작한다.
그리고 이번 실습에서는 코드만 맞아도 끝이 아니었다.
처음에는 경로를 맞췄는데도404가 나와서, 매핑 문제만이 아니라 프로젝트가 실제로Tomcat에 연결되어 있는지도 같이 확인해야 한다는 점까지 같이 확인하게 됐다.
즉 이 실습의 흐름은 아래처럼 정리할 수 있다.
Servlet생성 →URL mapping설정 →visitorForm.html작성 →form속성 연결 → 입력값 전송 →doPost()에서 값 받기 → 날짜 구하기 → 브라우저에 출력 →404와 한글 처리 점검
풀이 순서
- 연습용
Servlet클래스VisitorServletPractice를 생성하고URL mapping을 설정한다.visitorForm.html을 작성하고form의method,action, 입력 요소의name속성을 맞춘다.action경로와@WebServlet매핑을 맞췄는데도404가 발생한 원인을 확인하고 수정한다.doPost()에서 이름과 내용을 받고,LocalDate로 오늘 날짜를 구한 뒤PrintWriter로 화면에 출력한다.- 한글 인코딩 처리와
printf()인자 개수를 정리해 최종 코드로 마무리한다.
자세한 풀이
1.
VisitorServletPractice생성과URL mapping설정먼저 서버에서
POST요청을 받아줄Servlet클래스를 만들었다.
이번 실습에서는 연습용으로 클래스 이름을VisitorServletPractice로 만들고, 브라우저 요청 경로는/visitorPractice로 맞췄다.
즉, 브라우저가 이 경로로 요청을 보내면 이 클래스가 그 요청을 처리하게 되는 구조다.package practiceLab; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/visitorPractice") // 브라우저 요청 경로와 연결 public class VisitorServletPractice extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // `POST` 요청이 들어오면 여기서 처리할 예정 } }이 코드는 아직 값을 받는 처리까지 들어간 상태는 아니고, 먼저 어떤 요청을 이 클래스가 담당할지 연결만 만들어 둔 상태다.
즉, 지금 단계의 역할은 브라우저 요청 경로와Servlet클래스를 먼저 연결해 두는 것이다.데이터 흐름 예상
<form method="post" action="/edu/visitorPractice"> <!-- 1. 브라우저가 `/edu/visitorPractice`로 `POST` 요청을 보냄 --> </form>@WebServlet("/visitorPractice") // 2. `Tomcat`이 이 매핑을 보고 처리할 `Servlet`을 찾음 public class VisitorServletPractice extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) { // 3. `POST` 요청이므로 `doPost()`가 호출됨 } }→ 사용자가
form을 제출하면 브라우저가/edu/visitorPractice로 요청을 보낸다.
→ 서버는@WebServlet("/visitorPractice")를 보고 어떤Servlet이 이 요청을 처리해야 하는지 찾는다.
→ 요청 방식이POST이기 때문에doPost()메서드가 실행된다.
2.
visitorForm.html작성과 입력값 전송 준비다음으로는 브라우저 화면에서 실제로 값을 입력할 수 있는
HTML폼을 작성했다.
여기서 중요한 것은method="post",action="/edu/visitorPractice", 그리고 각 입력 요소의name속성이다.
name속성이 있어야 서버가 어떤 키 이름으로 값을 받아올지 알 수 있다.<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Insert title here</title> </head> <body> <h1>글을 남겨주세요</h1> <hr/> <form method="post" action="/edu/visitorPractice"> <!-- `POST` 방식으로 `Servlet`에 전송 --> <input type="text" name="name" placeholder="이름을 입력하세요" /> <!-- 이름 값 전송 --> <br /> <textarea name="content" placeholder="내용을 입력하세요"></textarea> <!-- 내용 값 전송 --> <br /> <input type="submit" value="보내기" /> <input type="reset" value="리셋" /> </form> </body> </html>처음에는
input과textarea에name속성이 없으면 화면에서는 입력이 되더라도 서버는 그 값을 꺼낼 수 없다는 점을 놓치기 쉽다.
그래서 이번 단계에서는 입력 화면을 만드는 것보다, 서버가 읽을 수 있는 형태로 전송 준비를 하는 것이 더 중요했다.데이터 흐름
<form method="post" action="/edu/visitorPractice"> <!-- 1. 제출 버튼을 누르면 `/edu/visitorPractice`로 이동 --> <input type="text" name="name" placeholder="이름을 입력하세요" /> <!-- 2. 사용자가 입력한 이름 값은 `name`이라는 키로 전송 --> <textarea name="content" placeholder="내용을 입력하세요"></textarea> <!-- 3. 사용자가 입력한 내용 값은 `content`라는 키로 전송 --> <input type="submit" value="보내기" /> <!-- 4. 버튼을 누르면 위 두 값이 함께 서버로 전송 --> </form>→ 사용자가 이름을 입력하면 그 값은
name이라는 이름으로 서버에 전달된다.
→ 사용자가 내용을 입력하면 그 값은content라는 이름으로 서버에 전달된다.
→submit버튼을 누르면 두 값이POST방식으로Servlet으로 넘어간다.
3.
404오류 트러블 슈팅두 번째 수행까지 하고 나면 보통
action경로와@WebServlet매핑만 맞추면 바로 될 것 같다고 생각하기 쉽다.
나도 처음에는 그렇게 생각해서/edu/visitorPractice와/visitorPractice를 맞춰 놨는데도 계속404가 발생했다.
즉, 코드상 경로는 맞는데 브라우저가Servlet을 찾지 못하는 상황이었다.
처음에는 매핑을 다시 보면서 코드 문제라고 생각했지만, 원인은 코드가 아니라 프로젝트 설정 쪽에 있었다.
Properties → Targeted Runtimes에서Apache Tomcat v10.1체크가 해제되어 있었고, 이 상태에서는 프로젝트가 실제로Tomcat에 연결되지 않아서Servlet이 정상 배포되지 않았다.@WebServlet("/visitorPractice") // 매핑 자체는 맞게 작성된 상태 public class VisitorServletPractice extends HttpServlet { }<form method="post" action="/edu/visitorPractice"> <!-- `HTML` 경로도 맞게 연결된 상태 --> </form>코드만 보면 분명 맞는데도
404가 났기 때문에, 이번에는 매핑 문제만 보지 말고 프로젝트가 서버에 제대로 연결되어 있는지도 같이 확인해야 한다는 점을 확실히 확인했다.데이터 흐름
1. 브라우저가 `/edu/visitorPractice`로 요청을 보냄 2. 프로젝트가 `Tomcat Runtime`에 연결되지 않음 3. 서버가 이 웹 프로젝트를 정상적으로 배포하지 못함 4. 결과적으로 브라우저는 해당 경로를 찾지 못해서 `404`를 출력함→ 요청 경로가 틀려서 난
404가 아니었다.
→ 프로젝트가Tomcat과 연결되지 않아서 아예 요청을 처리할 준비가 안 된 상태였다.
→Targeted Runtimes에서Apache Tomcat v10.1을 다시 체크한 뒤에는 같은 코드로 정상 동작했다.
4.
doPost()에서 값 받기, 날짜 구하기, 브라우저에 출력하기이제 실제 처리 코드를
doPost()안에 작성했다.
여기서 하는 일은 크게 네 가지다.
먼저 브라우저에서 보낸 요청값을 받아오고, 오늘 날짜를 구하고, 응답 형식을 정하고, 마지막에 결과를 브라우저 화면에 출력하는 것이다.package practiceLab; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDate; @WebServlet("/visitorPractice") public class VisitorServletPractice extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 한글 깨짐 방지 response.setContentType("text/html;charset=utf-8"); // 브라우저 응답 형식 설정 PrintWriter out = response.getWriter(); // 브라우저에 글을 출력할 객체 String name = request.getParameter("name"); // 이름 값 받기 String content = request.getParameter("content"); // 내용 값 받기 LocalDate currentDate = LocalDate.now(); // 오늘 날짜 구하기 out.printf("<h1>%s님이 %tY년 %tm월 %td일에 남긴 글입니다. </h1>", name, currentDate, currentDate, currentDate); // 이름과 날짜 출력 out.print("<hr />"); out.print("<h2>" + content + "</h2>"); // 내용 출력 } }이 단계에서 중요한 것은
request.getParameter("name"),request.getParameter("content")가 앞에서HTML에 적어둔name속성과 정확히 연결된다는 점이다.
즉, 앞 단계에서 폼을 어떻게 작성했는지가 여기서 그대로 이어진다.데이터 흐름
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 1. 요청 데이터를 `UTF-8`로 읽도록 먼저 설정함 response.setContentType("text/html;charset=utf-8"); // 2. 응답도 `UTF-8` `HTML`로 브라우저에 보낼 준비를 함 PrintWriter out = response.getWriter(); // 3. 브라우저에 글을 출력할 도구를 가져옴 String name = request.getParameter("name"); // 4. `input` 태그의 `name="name"` 값이 여기로 들어옴 String content = request.getParameter("content"); // 5. `textarea`의 `name="content"` 값이 여기로 들어옴 LocalDate currentDate = LocalDate.now(); // 6. 오늘 날짜를 구함 out.printf("<h1>%s님이 %tY년 %tm월 %td일에 남긴 글입니다. </h1>", name, currentDate, currentDate, currentDate); // 7. 이름과 날짜를 화면에 출력함 out.print("<hr />"); out.print("<h2>" + content + "</h2>"); // 8. 내용도 이어서 화면에 출력함 }→ 브라우저에서 입력한 이름이
name변수로 들어온다.
→ 브라우저에서 입력한 내용이content변수로 들어온다.
→LocalDate.now()로 오늘 날짜를 구한다.
→ 마지막에PrintWriter가 이름, 날짜, 내용을 묶어서 브라우저 화면에HTML형태로 출력한다.
5. 출력 코드 정리하면서 같이 수정한 부분
세 번째 수행까지 작성하고 나서 코드를 다시 보니, 눈에 바로 안 띄지만 나중에 문제를 만들 수 있는 부분이 두 군데 있었다.
하나는 한글 인코딩 처리 순서이고, 다른 하나는printf()에 넘기는 값 개수였다.5-1.
POST한글 처리 순서처음에는 요청값을 아래처럼 바로 꺼내는 코드부터 작성했다.
String name = request.getParameter("name"); String content = request.getParameter("content");이렇게 해도 당장은 동작해 보일 수 있지만,
POST방식에서 한글이 들어오면 깨질 수 있다.
요청 파라미터를 읽기 전에 먼저 문자 인코딩을 지정해야 하기 때문이다.
그래서 아래 코드를getParameter()보다 먼저 추가했다.request.setCharacterEncoding("UTF-8"); // `POST` 한글 처리즉, 이번에 확인한 점은 한글 인코딩 설정은 값을 꺼내기 전에 먼저 해야 한다는 점이다.
5-2.
printf()인자 개수 정리처음에는 날짜를 출력하면서 아래처럼
currentDate를 하나 더 넘겼다.out.printf("<h1>%s님이 %tY년 %tm월 %td일에 남긴 글입니다. </h1>", name, currentDate, currentDate, currentDate, currentDate);그런데 형식 지정자는
%s,%tY,%tm,%td로 총 4개인데, 실제로는name1개와currentDate4개를 넘겨서 값이 하나 더 많은 상태였다.
바로 큰 오류가 나는 건 아니어도, 코드를 읽을 때 헷갈리고 불필요한 값이 들어가 있으니 정리하는 게 맞다.
그래서 아래처럼 날짜 값은 세 번만 넘기도록 수정했다.out.printf("<h1>%s님이 %tY년 %tm월 %td일에 남긴 글입니다. </h1>", name, currentDate, currentDate, currentDate);즉, 이번 단계에서는 형식 지정자 개수와 실제 전달하는 값 개수를 맞춰야 한다는 점도 같이 확인했다.
최종 코드
visitorForm.html<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Insert title here</title> </head> <body> <h1>글을 남겨주세요</h1> <hr/> <form method="post" action="/edu/visitorPractice"> <input type="text" name="name" placeholder="이름을 입력하세요" /> <br /> <textarea name="content" placeholder="내용을 입력하세요"></textarea> <br /> <input type="submit" value="보내기" /> <input type="reset" value="리셋" /> </form> </body> </html>
VisitorServletPractice.javapackage practiceLab; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDate; @WebServlet("/visitorPractice") public class VisitorServletPractice extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 한글 깨짐 방지 response.setContentType("text/html;charset=utf-8"); // 응답 형식 설정 PrintWriter out = response.getWriter(); // 브라우저 출력용 객체 String name = request.getParameter("name"); // 이름 받기 String content = request.getParameter("content"); // 내용 받기 LocalDate currentDate = LocalDate.now(); // 오늘 날짜 구하기 out.printf("<h1>%s님이 %tY년 %tm월 %td일에 남긴 글입니다. </h1>", name, currentDate, currentDate, currentDate); out.print("<hr />"); out.print("<h2>" + content + "</h2>"); } }실행 결과
실습하면서 확인한 점
form에서 서버로 값을 보내려면 입력 태그에 반드시name속성이 있어야 한다.form의action경로와@WebServlet매핑 이름은 서로 정확히 맞아야 한다.POST요청에서 한글을 안전하게 받으려면request.setCharacterEncoding("UTF-8")을getParameter()보다 먼저 작성해야 한다.LocalDate.now()를 사용하면 현재 날짜를 쉽게 구할 수 있고,printf()의%tY,%tm,%td형식 지정자로 연, 월, 일을 출력할 수 있다.404오류가 났을 때는 경로만 볼 게 아니라 프로젝트가Tomcat Runtime에 제대로 연결되어 있는지도 같이 확인해야 한다.- 브라우저 화면 출력은
PrintWriter로 만들고,response.setContentType("text/html;charset=utf-8")으로 응답 형식을 먼저 맞춰 주는 것이 중요하다.