2025. 4. 21. 10:32ㆍSpring Framework/스프링 MVC
1. 서블릿
1) 서블릿 컨테이너 동작 방식
웹 브라우저에서 HTTP 요청 메시지가 전달됨
-> 서버에서 HTTP 요청 메시지를 기반으로 request, response 객체 생성
-> 서블릿 컨테이너에 요청 전달해서 요청 url에 맞는 서블릿 객체 받아서 실행
-> 서블릿에서 response 객체에 데이터 세팅
-> 서버에서 response 객체 정보로 HTTP 응답 메시지 생성
-> 웹 브라우저에 HTTP 응답 메시지 전달
2) 서블릿 클래스 기본 코드
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HelloServlet.service");
System.out.println("request = " + request);
System.out.println("response = " + response);
//HttpServletRequest로 요청 데이터 조회
String username = request.getParameter("username");
System.out.println("username = " + username);
//HttpServletResponse로 응답 데이터 세팅
response.setContentType("text/plain"); //헤더
response.setCharacterEncoding("utf-8");
response.getWriter().write("hello " + username); //메시지 바디
}
}
- extends HttpServlet 필수
- @WebServlet : 서블릿 애노테이션 (자바 표준 애노테이션)
- name: 서블릿 이름, urlPatterns: URL 매핑
- @WebServlet은 이 클래스가 서블릿이라는 걸 서버(톰캣) 에게 알려주는 애노테이션
그리고 동시에 이 서블릿을 어떤 URL 요청에 연결할지도 같이 설정하는 역할을 한다. - 즉, 서버가 프로젝트를 시작할 때, @WebServlet을 보고
이 HelloServlet 클래스는 서블릿 클래스이며 /hello 라는 URL 요청이 들어올 때 실행해야 한다고 매핑을 기억
- service() 오버라이드
- HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 service() 메서드를 실행한다.
- 따라서 해당 메서드를 오버라이딩 하여 내부에 코드를 작성해야 함.
- HttpServletRequest, HttpServletResponse 객체
- 톰캣에서 개발자가 HTTP 요청/응답 메시지를 편리하게 사용할 수 있도록 파싱해놓은 객체이다.
- 서블릿의 매개변수로 사용할 수 있다.
2. HTTP 요청/응답 메시지

1) 시작 라인 (start-line)
- 요청 시작 라인, request-line
HTTP메서드 공백 요청대상(절대경로) 공백 HTTP버전 엔터
예: GET /search?q=hello HTTP/1.1- method: HTTP 메서드로 서버가 수행해야 하는 동작 지정, GET/POST
- request-target, 요청 대상: /로 시작하는 절대경로[?쿼리]
- 응답 시작 라인, status-line
HTTP버전 공백 HTTP상태코드 공백 이유문구 엔터
예: HTTP/1.1 200 OK- status-code: HTTP 상태코드로 요청 성공, 실패를 나타냄
200-성공, 400-클라이언트 요청 오류, 500-서버 내부 오류 - reason-phrase, 이유 문구: 상태코드에 대한 짧은 설명 글 (보는 사람이 이해할 수 있게)
- status-code: HTTP 상태코드로 요청 성공, 실패를 나타냄
2) 헤더 (header)
필드네임 ":" OWS(띄어쓰기 허용) 필드값 OWS
- field-name은 대소문자 구분X, field value는 당연히 구분O
- http 전송에 필요한 모든 부가 정보가 다 들어가 있음
- 표준 헤더 필더가 너무 많음
3) 메시지 바디 (message body)
- 실제로 전송할 데이터
- HTML 문서, 이미지, 영상, JSON 등등 byte로 표현할 수 있는 모든 데이터를 전송할 수 있다.
3. 클라이언트가 서버로 요청 데이터를 전달하는 방식
1. URL에 쿼리 파라미터 붙이기
2. 메시지 바디에 담기
2-1. HTTP Form 형식
2-2. JSON 형식 (HTTP API 방식)
2-3. 파일, 이미지 등등
1) URL에 쿼리 파라미터 붙이기
- HTTP 요청 메시지의 요청 시작 라인에 있는 요청 대상(절대 경로) 뒤에 쿼리 파라미터가 붙어 서버로 전달된다.
- 쿼리 파라미터 형식: /url?key=value&key2=value2
- 검색, 필터, 페이징 등에서 많이 사용하는 방식
- 주로 GET, DELETE 요청에 필요한 데이터를 전달할 때 이렇게 많이 사용한다.
POST, PUT에서도 이론상 URL에 쿼리 파라미터를 붙일 수 있지만 잘 안 쓰고, 보통 GET, DELETE에서만 사용
2) HTTP 요청 메시지의 메시지 바디에 담기
2-0) 공통적인 특징
- 데이터를 직접 메시지 바디에 담아서 요청하는 방식
- 메시지 바디에 데이터를 담을 때는 반드시 헤더의 Content-Type을 명시해야 한다.
- Content-Type은 메시지 바디에 들어간 데이터의 형식을 설명하는 헤더
- 데이터를 어떤 형식으로 담았는지 알려줘야 서버가 바디를 읽을 수 있기 때문에 반드시 명시해야 한다.
URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없음
- 폼 데이터: application/x-www-form-urlencoded
JSON 데이터: application/json
파일 전송: multipart/form-data
- 주로 POST, PUT 요청에 필요한 데이터를 전송할 때 많이 사용하는 방식이다.
2-1) HTML Form 형식
- 사용자가 HTML의 Form에 데이터를 입력하여 제출(submit)하는 경우 메시지 바디에 쿼리 파라미터 형식으로 데이터가 담긴다.
예) username=hello&age=20 - 전통적인 웹 폼 제출으로 회원 가입, 상품 주문 등의 경우에서 많이 사용됨
- Content-Type으로 application/x-www-form-urlencoded 명
2-2) JSON 형식 (HTTP API)
- 시스템 간에 HTML 화면이 아닌 JSON 데이터를 주고받는 통신을 통상적으로 HTTP API라고 부른다.
프론트엔드 ↔ 백엔드, 서버 ↔ 서버 등 - JSON, XML, TEXT 등의 데이터 형식이 있으나 요즘은 대부분 JSON을 사용한다.
2-3) 파일, 이미지 등
- 그 외 파일, 이미지 등 바이너리 데이터를 전송할 때는 multipart/form-data를 사용하여 메시지 바디를 여러 파트로 나누어 전송한다.
- 대용량 파일은 application/octet-stream 형식으로 메시지 바디 전체에 담아 스트리밍 방식으로 전송할 수도 있다.
4. HttpServletResponse 객체 다루기
1) 요청 시작 라인 조회
@WebServlet(name="requestHeaderServlet",urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getMethod(); //GET
request.getProtocol(); //HTTP/1.1
request.getScheme(); //http
request.getRequestURL(); //http://localhost:8080/request-header
request.getRequestURI(); //request-header
request.getQueryString(); //username=hi
request.isSecure(); //https 사용 유무, false
}
}
2) 헤더 조회
@WebServlet(name="requestHeaderServlet",urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//Header 전체 조회
request.getHeaderNames().asIterator()
.forEachRemaining(headerName
-> System.out.println(headerName + ": " + request.getHeader(headerName)));
//Host 편의 조회
request.getServerName(); //Host 헤더
request.getServerPort(); //Host 헤더
//Accept-Language 편의 조회
request.getLocales().asIterator()
.forEachRemaining(locale -> System.out.println("locale = " + locale));
request.getLocale();
//Cookie 편의 조회
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}
}
//Content 편의 조회
request.getContentType();
request.getContentLength();
request.getCharacterEncoding();
//Remote 정보
request.getRemoteHost();
request.getRemoteAddr();
request.getRemotePort();
//Local 정보
request.getLocalName();
request.getLocalAddr();
request.getLocalPort();
}
}
3) HTTP 요청 데이터 조회
3-1) 쿼리 파라미터, HTML Form 형식
@WebServlet(name="requestParamServlet",urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//전체 파라미터 조회
request.getParameterNames().asIterator()
.forEachRemaining(paramName -> System.out.println(paramName + " = " + request.getParameter(paramName)));
//단일 파라미터 조회
String username = request.getParameter("username");
String age = request.getParameter("age");
System.out.println("username = " + username);
System.out.println("age = " + age);
//이름이 같은 복수 파라미터 조회 (흔하지는 않음)
//그냥 getParameter 사용하면 첫번째 파라미터만 조회됨
//해당 이름을 갖는 파라미터 전체 조회하기 위해서는 getParameterValues 사용해야함
String[] usernames = request.getParameterValues("username");
for (String name : usernames) {
System.out.println("username: "+ name);
}
}
}
- 쿼리 파라미터와 HTML Form의 데이터 형식: username=hello&age=20
둘은 데이터 형식이 같기 때문에 조회 메서드를 request.getParameter()로 동일하게 사용한다. - 클라이언트(웹 브라우저) 입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 두 방식 보며 전달받는 데이터의 형식이 동일하므로, request.getParameter() 로 편리하게 구분없이 조회할 수 있다.
- 정리하면 request.getParameter()는 GET URL 쿼리 파라미터 형식도 지원하고, POST HTML Form 형식도 둘 다 지원한다.
- 단, HTML Form 데이터는 POST 방식으로 전달하고 content-type을 x-www-form-urlencoded로 설정해야한다.
3-2) 단순 텍스트, JSON (HTTP API)
@WebServlet(name="requestBodyStringServlet", urlPatterns = "/request-body-string")
public class RequestBodyStringServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);
}
}
- 단순 텍스트 형식 요청
- POST http://localhost:8080/request-body-string content-type: text/plain
- message body: hello
- 결과: messageBody = hello
- inputStream은 byte 코드를 반환한다. byte 코드를 우리가 읽을 수 있는 문자(String)로 보려면 문자표 (Charset)를 지정해주어야 한다. 여기서는 UTF_8 Charset을 지정해주었다.
- 참고로HTML form 데이터도 메시지 바디를 통해 전송되므로 직접 읽을 수 있다. 하지만 편리한 파리미터 조회 기능request.getParameter()을 이미 제공하기 때문에 파라미터 조회 기능을 사용하면 된다.
@WebServlet(name="requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
System.out.println("helloData.getUsername() = " + helloData.getUsername());
System.out.println("helloData.getAge() = " + helloData.getAge());
}
}
- JSON 형식 요청
- POST http://localhost:8080/request-body-json
- content-type: application/json
- message body: {"username": "hello", "age": 20}
- 결과: messageBody = {"username": "hello", "age": 20}
data.username=hello
data.age=20
-
JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환 라이브러리를 추가해서 사용해야 한다. 스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson 라이브러리인 ObjectMapper를 함께 제공한다.
5. HttpServletResponse 객체 다루기
5-1. 응답 시작 라인, 헤더 세팅
@WebServlet(name="responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//status-line
response.setStatus(HttpServletResponse.SC_OK);
//response-header
response.setHeader("Content-Type", "text/plain;charset=utf-8");
//content 편의 메서드 제공됨
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
//response.setContentLength(2); //(생략시 자동 생성)
response.setHeader("Cache-Control", "no-cache, no-store, mustrevalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("my-header","hello");
//기타 Header 편의 메서드
cookie(response);
redirect(response);
}
private void cookie(HttpServletResponse response) {
//Set-Cookie: myCookie=good; Max-Age=600;
//response.setHeader("Set-Cookie", "myCookie=good; Max-Age=600");
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600); //600초
response.addCookie(cookie);
}
private void redirect(HttpServletResponse response) throws IOException {
//Status Code 302
//Location: /basic/hello-form.html
//response.setStatus(HttpServletResponse.SC_FOUND); //302
//response.setHeader("Location", "/basic/hello-form.html");
response.sendRedirect("/basic/hello-form.html");
}
}
5-2. 메시지 바디 세팅
[HTTP 응답 메시지의 메시지 바디에 들어가는 데이터 종류]
1. 단순 텍스트
2. HTML 코드
3. JSON (HTTP API 방식)
1) 단순 텍스트
//message body에 단순 텍스트 데이터 입력
PrintWriter writer = response.getWriter();
writer.print("ok");
2) HTML
@WebServlet(name = "responseHtmlServlet", urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//Content-Type: text/html;charset=utf-8
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<body>");
writer.println(" <div>안녕?</div>");
writer.println("</body>");
writer.println("</html>");
}
}
- HTTP 응답으로 HTML을 반환할 때는 content-type을 text/html로 지정해야 한다.
3) API JSON
@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//Content-Type: application/json
response.setHeader("content-type", "application/json");
response.setCharacterEncoding("utf-8");
HelloData data = new HelloData();
data.setUsername("kim");
data.setAge(20);
//{"username":"kim","age":20}
String result = objectMapper.writeValueAsString(data);
response.getWriter().write(result);
}
}
- HTTP 응답으로 JSON을 반환할 때는 content-type을 application/json 로 지정해야 한다.
- Jackson 라이브러리가 제공하는 objectMapper.writeValueAsString()를 사용하면 객체를 JSON 문자로 변경할 수 있다.