
이 글은 2026년 04월 20일 작성된 글입니다.
오늘은 폼 처리와 자바스크립트 유효성 검사, REST API와 HTTP 요청 흐름,
그리고 게시물 작성부터 상세페이지까지의 웹 흐름을 정리했다.
폼은 사용자가 입력한 값을 서버로 보내기 위한 기본적인 수단이다.
이번에는 폼 자체를 익히고, 자바스크립트로 입력값을 미리 검사하는 흐름까지 함께 학습했다.
<form>
<input type="text" name="title" placeholder="제목을 입력해주세요">
<button type="submit">등록</button>
</form>
자바스크립트 폼 체크를 적용하면
제목이나 내용이 비어 있는 상태에서 바로 제출되는 상황을 미리 막을 수 있다.
게시물 작성폼에 자바스크립트 검사를 붙여서
제목이나 내용이 비어 있으면 제출되지 않도록 만들었다.
<script>
function checkForm(form) {
if (form.title.value.trim() === "") {
alert("제목을 입력해주세요.");
form.title.focus();
return false;
}
if (form.content.value.trim() === "") {
alert("내용을 입력해주세요.");
form.content.focus();
return false;
}
return true;
}
</script>
<form onsubmit="return checkForm(this);">
이렇게 하면 서버에 요청이 가기 전에 1차 검증이 가능하다.
물론 최종 검증은 서버에서도 다시 해야 한다.
REST는 자원을 URL로 표현하고,
HTTP 메서드로 어떤 작업을 할지 구분하는 방식이다.
예를 들면 다음과 같다.
GET /articles
POST /articles
PUT /articles/1
PATCH /articles/1
DELETE /articles/1
각 메서드의 의미는 보통 이렇게 나눈다.
같은 URL이라도 메서드가 다르면 역할이 달라진다.
GET /usr/article/write
POST /usr/article/write
브라우저에 URL을 입력하면 단순히 페이지가 바로 뜨는 것이 아니라
여러 단계를 거쳐 요청과 응답이 오간다.
예를 들어 /usr/article/list를 입력하면 다음과 같은 흐름이 진행된다.
이 흐름을 이해하면
웹 개발에서 클라이언트와 서버가 어떻게 연결되는지 훨씬 잘 보인다.
디스패처 서블릿에서 요청 메서드와 URI에 따라
적절한 컨트롤러 메서드로 분기하도록 구성했다.
@WebServlet("/usr/*")
public class DispatcherServlet extends HttpServlet {
private MemberController memberController;
private ArticleController articleController;
@Override
public void init(ServletConfig config) throws ServletException {
this.memberController = Container.memberController;
this.articleController = Container.articleController;
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Rq rq = new Rq(req, resp);
switch (rq.getMethod()) {
case "GET" -> {
switch (rq.getURIPath()) {
case "/usr/article/list" -> articleController.showList(rq);
case "/usr/article/write" -> articleController.showWrite(rq);
case "/usr/member/join" -> memberController.showJoin(rq);
}
}
case "POST" -> {
switch (rq.getURIPath()) {
case "/usr/article/write" -> articleController.doWrite(rq);
}
}
}
}
}
이 구조를 적용하면 요청이 어디로 들어왔고,
어떤 메서드로 처리해야 하는지가 명확해진다.
게시물 작성을 실제로 처리하는 기능을 만들고,
서비스와 리포지터리 계층도 함께 도입했다.
이렇게 역할을 나누면
기능이 늘어나도 구조가 덜 무너지고,
수정 범위도 명확해진다.
게시물 작성 기능은 GET과 POST 둘 다 필요하다.
GET /usr/article/write
POST /usr/article/write
GET은 작성 폼을 보여주는 역할이고,
POST는 사용자가 입력한 데이터를 실제로 저장하는 역할이다.
웹에서는 같은 기능처럼 보여도
화면 노출과 데이터 처리는 분리해서 보는 것이 일반적이다.
전체 URI에서 공통 경로를 제외하고
실제로 어떤 동작을 해야 하는지 더 쉽게 꺼내기 위해 getActionPath를 도입했다.
예를 들어 /usr/article/write 같은 경로가 있을 때
액션 경로를 따로 다루면 라우팅 코드가 더 간결해질 수 있다.
게시물 목록만 보는 것이 아니라
각 게시물 하나를 자세히 볼 수 있는 상세페이지를 만들었다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<h1>게시물 상세</h1>
<div>
<h1>제목 1</h1>
<div>
<span>번호 : 1</span>
</div>
<div>
<span>내용 : 내용 1</span>
</div>
<div>
<span>작성일 : 2025-00-00 00:00</span>
</div>
<div>
<a href="/usr/article/list">목록</a>
</div>
</div>
상세페이지가 생기면서
목록 → 상세 → 다시 목록으로 돌아가는 기본 흐름이 만들어졌다.
상세페이지는 단순히 보여주기만 하면 끝나는 것이 아니라
잘못된 요청이 들어왔을 때도 처리할 수 있어야 한다.
예를 들면 이런 경우를 검증해야 한다.
id가 없는 경우이런 검증이 있어야 잘못된 URL 요청에도
프로그램이 안전하게 동작한다.
게시물 목록에서 각 항목을 클릭하면
상세페이지로 이동할 수 있도록 링크를 연결했다.
<a href="/usr/article/detail?id=1">상세보기</a>
이렇게 연결하면
단순한 목록 출력이 아니라 실제 웹 서비스처럼 흐름이 이어지게 된다.
기존에는 쿼리스트링 방식으로 값을 전달했다.
/usr/article/detail?id=1
여기서 더 나아가 path variable 방식도 다루기 위해
getLongPathValueByIndex를 도입했다.
/usr/article/detail/1
이 방식은 URL만 봐도 어떤 자원을 가리키는지 더 직관적으로 보인다.