728x90
프로젝트 생성 tip
- Dependencies: Spring web, Lombok
- War
- 외장 톰컷 서버 별도 설치 및 jsp 사용시 선택
- Gradle : Gradle -> IntelliJ IDEA
- Lombok
- plugin -> lombok 설치 -> 재시작
- Annotation Processors -> Enable annotation processing 체크 후 재시작
1. 서블릿 기본 사용법
- 레거시 : tomcat(=sevlet was) 직접 설치 -> 서블릿 코드를 class file로 빌드
- 스프링부트: 톰캣 서버 내장(embedded) -> 서블릿 코드만 실행
- 서블릿은 스프링관 관련이 없다. 스프링은 서블릿을 더 편하게 사용할 수 있도록 스프링 web mvc를 가지고 있을 뿐이다.
1.1. @ServletComponentScan 등록
- @ServletCompenentScan 등록: 스프링 부트에서 서블릿 사용할 수 있도록 함
- servlet은 인터페이스로 spring뿐만 아니라 다른 web 관련된 것들이 해당 인터페이스를 구현 해놓았다.
- 그래서 servlet은 공통으로 사용이 가능하다.
@ServletComponentScan
@SpringBootApplication
public class ServletApplication{
public static void main(String[] args){
SpringApplication.run(ServletApplication.class, args);
}
}
1.2. 서블릿 등록하기
# application.properties
// 실제 서버가 받는 http 요청 메시지를 출력해준다.
// 추가설정의 debug는 개발단계에서만 적용 - 성능저하 발생 때문
logging.level.org.apache.coyote.http11=debug
// servlet 등록 : @WebServlet
// name과 urlPatterns는 이름이 동일하면 안된다.
// name: servlet container에 bean으로 등록시 bean명
// urlPatterns: 해당 url로 들어올 시, 해당 servlet container에 등록된 bean 동작
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
// protected service method를 오버라이딩 해야함
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("request = " + request); // 요청 data 객체 주소
System.out.println("response = " + response); // 미리 만든 응답 data 객체 주소
String username = request.getParameter("username");
System.out.println("username = " + username);
// http 응답에 보낼 header 정보
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8"); // 현대는 utf-8을 꼭 넣어주기
// http 응답에 보낼 message = body 정보
response.getWriter().write("hello " + username);
}
}
1.3. 정리
- @WebServlet: http 파싱을 대신해줌
- (name과 urlPatterns 값이 겹치면 error 발생)
- name = "helloServlet" : 싱글톤 서블릿 객체 이름
- urlPatterns = "/hello" : hello url 요청이 올때 해당 서블릿객체 작동
- extends HttpServlet: 서블릿은 이 인터페이스를 상속받아야 사용 가능
- request: client webBrowser가 만든 http 요청을 기반으로 생성한 객체
- response: 일단 미리 만든 http 응답 객체, 개발자가 추가할 정보를 추가할 수 있다.
- response.getWriter().write("hello")가 실제 web browser UI에 나타나는 이유
더보기
browser에서 http 응답을 해석할 때 렌더링 엔진 등의 자체 내장하고 있는 기능을 통해서 html, text, json은 그냥 출력해준다.
* 웹 렌더링: 실시간으로 웹사이트가 그려지는 과정
* 웹 2가지 엔진(크게 2가지로 구성): 1) 렌더링 엔진 2) JS 엔진
1.4. 서블릿 컨테이너 동작 방식
- spring boot가 내장 톰켓 서버 실행
- @ServletComponentScan를 이용해서 servlet class(@WebServlet class)를 서블릿 컨테이너에 bean으로 등록
- http 요청 메시지를 기반으로 request 객체 생성, 이때 response 객체도 생성 = prototype bean
- 서블릿 객체 실행하고 실행이 종료되면 실행과정에서 생성한 response data를 response 객체에 반환
- 해당 servlet Bean code에는 request, response 파싱을 지원해주는 기능들이 존재 extends HttpServlet
- client web browser에게 http 응답 메시지 보냄
- 서버에서 만드는 html page
- webapp 디렉토리 생성 후 안에 index.html 생성하면 localhost:8080(최상단 root) 호출시 해당 html 페이지 열림
- spring 레거시 일 때 webapp directory 사용
- webapp 디렉토리 구성
- static html : index.html(/)
- jsp : 동적 html
- WEB-INF - views : controller를 통해서만 접근이 가능한 jsp
- 참고
- html form - input의 name property : queryParameter의 key의 역할을 수행
2. HttpServletRequest
2.1. httpServletRequest 역할
- 개요
- http 요청 메시지를 개발자가 직접 파싱하지 않고 서블릿이 대신 파싱해서 개발자가 http 요청 메시지를 편리하게 사용
- 파싱한 결과를 HttpServletRequest request 객체에 담아서 제공
- http request
- start line : (http method, url, query,schema, protocol)
- header : 헤더 정보들
- body
- form 형식 조회 - queryParameter
- message body 데이터 직접 조회 -json
- 부가기능
- 임시저장소
- request.setAttribute(name, value)
- request.getAttribute(name)
- 세션관리
- request.getSession(create: true)
- 임시저장소
2.2. 기본 사용법 - start-line, header 정보 조회 방법
package hello.servlet.basic.request;
@WebServlet(name = "requestHeaderServlet", urlPatterns = "/request-header")
public class RequestHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
printStartLine(request);
printHeaders(request);
printHeaderUtils(request);
printEtc(request);
}
// start-line 정보
private void printStartLine(HttpServletRequest request) {
System.out.println("request.getMethod() = " + request.getMethod());
System.out.println("request.getProtocol() = " + request.getProtocol());
System.out.println("request.getScheme() = " + request.getScheme());
System.out.println("request.getRequestURL() = " + request.getRequestURL());
System.out.println("request.getQueryString() = " + request.getQueryString());
System.out.println("request.isSecure() = " + request.isSecure());
}
// Header 모든 정보
private void printHeaders(HttpServletRequest request) {
/* 과거 방법
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
System.out.println("headerName = " + headerName);
System.out.println("request.getHeader(headerName) = " + request.getHeader(headerName));
}
*/
request.getHeaderNames().asIterator()
.forEachRemaining(headerName -> System.out.println(headerName + ": " + request.getHeader(headerName)));
}
// Header 편리한 조회
private void printHeaderUtils(HttpServletRequest request) {
System.out.println("[Host 편의 조회]");
System.out.println("request.getServerName() = " + request.getServerName());
System.out.println("request.getServerPort() = " + request.getServerPort());
System.out.println("[Accept-Language 편의 조회]");
request.getLocales().asIterator().forEachRemaining(locale -> System.out.println("locale = " + locale));
System.out.println("request.getLocale() = " + request.getLocale());
System.out.println("[cookie 편의 조회]");
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}
}
System.out.println("[Content 편의 조회]");
System.out.println("request.getContentType() = " + request.getContentType());
System.out.println("request.getContentLength() = " + request.getContentLength());
System.out.println("request.getCharacterEncoding() = " + request.getCharacterEncoding());
}
// 기타 조회
private void printEtc(HttpServletRequest request) {
System.out.println("[Remote 정보]"); // client 요청이 온것에 관한 정보
System.out.println("request.getRemoteHost() = " + request.getRemoteHost());
System.out.println("request.getRemoteAddr() = " + request.getRemoteAddr());
System.out.println("request.getRemotePort() = " + request.getRemotePort());
System.out.println("[Local 정보]"); // 나의 server에 관한 정보
System.out.println("request.getLocalName() = " + request.getLocalName());
System.out.println("request.getLocalAddr() = " + request.getLocalAddr());
System.out.println("request.getLocalPort() = " + request.getLocalPort());
}
}
3. http 요청 데이터 - 이젠 껍데기 정보가 아니라 client가 직접 보낸 요청 data 추출 방법
요청 방식 | 요청 header 전송 형태 | data 위치 | 형태 |
get query / get form | params | http start-line | query |
post form | application/x-www-form-urlencoded | http body | query |
api text | text/plain | http body | string |
api json | application/json | http body | json |
3.1. summary - 3가지
- 1, 2 : queary params 형식이여서 둘 다 queary params 조회 메서드를 동일하게 사용 - request.getParameter();
- GET - queary params
- http body 없이 url 쿼리 파라미터에 데이터를 포함해서 전달
- http body 없이 url 쿼리 파라미터에 데이터를 포함해서 전달
- POST - HTML Form
- http body에 쿼리 파라미터 형식으로 전달
- html form으로 전달하는 방식은 get, post만 지원한다.
- HTTP message body
- http api에 주로 사용 - json 형식으로 데이터 전달 *text,xml,json 방식이 존재함
- post,put,patch 다 사용가능
3.2. GET - queary params 조회 메서드
- username=hello&username=kim
- request.getParameter() : 하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용
- hello만 출력 - request.getParameterValues() : 하나의 파라미터 이름에 대해서 값이 중복일 경우 사용
- hello, kim 출력
- request.getParameter() : 하나의 파라미터 이름에 대해서 단 하나의 값만 있을 때 사용
package hello.servlet.basic.request;
// public class RequestParamServlet extends HttpServlet{} 내용 요약
// http://localhost:8080/request-param?username=hello&age=20&username=bye
// key-value 형식
// 1. 단일 파라미터 조회
String username = request.getParameter("username"); // 중복된 key있을 시 1st 꺼냄
String username = request.getParameter("age");
// 2. 파라미터 이름들 모두 조회(중복 key는 첫번째 entity만 꺼냄)
Enumeration<String> parameterNames = request.getParameterNames(); //username,age 다꺼냄
request.getParameterNames()
.asIterator().forEachRemaining(System.out::println) // 전체 params 각각 꺼냄
// 3. 파라미터를 Map 으로 조회
Map<String, String[]> parameterMap = request.getParameterMap();
// 4. 복수 파라미터 조회
String[] usernames = request.getParameterValues("username"); // 중복된 key가 있을때만 사용가능
package hello.servlet.basic.request;
/**
* 1. 파라미터 전송 기능
* http://localhost:8080/request-param?username=hello&age=20
* <p>
* 2. 동일한 파라미터 전송 기능
* http://localhost:8080/request-param?username=hello&username=kim&age=20
*/
@WebServlet(name = "requestParamServlet", urlPatterns = "/request-param")
public class RequestParamServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("[전체 파라미터 조회] - start");
Enumeration<String> parameterNames = request.getParameterNames();
System.out.println("parameterNames1 = " + parameterNames); // 주소 나옴
request.getParameterNames().asIterator().forEachRemaining(paramName -> System.out.println(paramName + ": " + request.getParameter(paramName)));
System.out.println("[전체 파라미터 조회] - end");
System.out.println();
System.out.println("[map 형식 조회]");
Map<String, String[]> parameterMap = request.getParameterMap();
System.out.println("parameterMap = " + parameterMap); // 주소로 나옴
System.out.println("[단일 파라미터 조회]");
String username = request.getParameter("username");
System.out.println("username = " + username);
String age = request.getParameter("age");
System.out.println("age = " + age);
System.out.println();
System.out.println("[중복 파라미터 조회]");
String[] usernames = request.getParameterValues("username");
for (String name: usernames){
System.out.println("username = " + name);
}
response.getWriter().write("ok");
}
}
3.3. POST - HTML Form 조회 메서드
- http request header 형식
- 요청 URL: http://localhost:8080/request-param
- content-type: application/x-www-form-urlencoded
- message body: username=hello&age=20
더보기
GET-queary params와 달리 http body를 통해서 쿼리문을 보내는 것이므로 http body가 어떤 형식으로 된 data인지 알려주는 content-type이 필요
요청 메시지에서 나타날 때 : 1) html data: application/x-www-form-urlencoded
응답 메시지에서 나타날 때 : 1) text data : text/plain 2) html data: text/html 3) json data : application/json
- <input의 name="key"> : queryParameter의 key의 역할을 수행
- 해당 Post form 요청은 http 메시지 바디에 username=""&age="" 형식으로 보내진다.
- 조회 메서드는 3.2.GET-quary parameter와 동일
- http body에 있어도 queary문이기 때문에 동일 취급
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/request-param" method="post"> // post로 보냄
// 요청 url: http://localhost:8080/basic/hello-form.html
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
</body>
</html>
3.4.1. HTTP message body - API 메시지 바디_단순 텍스트
- http message body에 데이터를 직접 담아서 요청
- InputStream을 사용해서 직접 읽음
- 사용 method()
- request.getInputStream() : http message body의 내용을 byte type code로 읽어온다.
- form data도 위와 같이 가져올 수 있지만, getParameter()가 훨씬 유리하다.
- StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8) : byte type code를 string으로 변환
- StreanUtils : spring이 제공하는 class
- StandardCharsets.UTF_8: byte를 string으로 변환시 encoding 정보를 알려줘야 함
- request.getInputStream() : http message body의 내용을 byte type code로 읽어온다.
package hello.servlet.basic.request;
@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();
// http message body의 내용을 bytetypecode로 읽어온다.
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
// bytecode를 string으로 변환, 변환시 항상 encoding 정보를 알려줘야함
System.out.println("messageBody = " + messageBody);
}
}
3.4.2. HTTP message body - API 메시지 바디_JSON
- 방식
- request -> response = JSON -> Object
- response -> request = Object -> JSON
- json 형식으로 파싱할 수 있는 class 생성
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class HelloData {
private String username;
private int age;
}
- json -> object
- 사용 method()
- new ObjectMapper(): json data를 파싱해서 사용할 수 있는 java object로 변환하기 위한 Jackson library
- json <-> JAVA obejct (양방향 가능)
- request.getInputStream() : http message body의 내용을 byte type code로 읽어온다.
- StreamUtils.copyToString() : byte type code를 string으로 변환
- objectMapper.readValue(messageBody, HelloData.class) : json 형식을 java형식으로 담는다
- new ObjectMapper(): json data를 파싱해서 사용할 수 있는 java object로 변환하기 위한 Jackson library
@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
// 1. json data를 파싱해서 사용할 수 있는 java object로 변환하기 위한 Jackson library, ObjectMapper 제공
// json 변환 library: Jackson, Gson
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 2. http meassage body에서 data 꺼내는 작업
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);
// 3. 꺼낸 data(=json)을 java 객체에 담아서 파싱하기
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
// object로 변환된 data 출력
System.out.println("helloData.getUsername() = " + helloData.getUsername());
System.out.println("helloData.getAge() = " + helloData.getAge());
response.getWriter().write("ok");
}
}
4. HttpServletResponse
- 개요
- 개발자가 직접 모든 http 응답 메시지 만들기 번거롭다. 이를 해결해주기 위해 제공해주는 method
- HTTP 응답 메시지 생성
- HTTP 응답코드 지정
- 헤더 생성
- 바디 생성
- 응답 message 생성 편의기능 제공
- Content-Type
- Cookie
- Redirect
4.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); //http 응답코드
// response-headers
response.setHeader("Content-Type ", "text/plain;charset=utf-8");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("my-header", "woowee"); // 내가 만든 임의의 header
// header 편의 method
content(response);
cookie(response);
redirect(response);
PrintWriter writer = response.getWriter();
writer.println("hello");
}
// Content 편의 method
private void content(HttpServletResponse response) {
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
// response.setContentLength(2); // 생략시 자동 생성
}
// Cookie 편의 method
private void cookie(HttpServletResponse response) {
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600);
response.addCookie(cookie);
}
// redirect 편의 method
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");
}
// forwar method
}
5. http 응답 데이터 - http 응답 body에 들어가는 real data 집어넣기
응답 방식 | 응답 header 전송 형태 (ContentType) | data 위치 | 형태 |
text | text/plain | body | text |
html | text/html | body | html 형태의 text |
json | application/json | body | json 형태의 text |
5.1. 단순 텍스트 응답
package hello.servlet.basic;
@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
response.getWriter().write("hello " + username);
}
}
5.2. HTML 응답
- 헤더에 setContentType(text/html) 추가
- 'http 응답 body의 data'가 'client의 webBrowser'로 가서 렌더링 과정에서 html type인 것을 인지 시킴
- UI에 html 형식으로 나타날 수 있게 된다.
@WebServlet(name = "responseHtmlServlet",urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<body>");
writer.println("안녕");
writer.println("</body>");
writer.println("</html>");
// 그냥 writer.println("안녕")만 해도 html로 나타남. ContentType에서 html이라고 알려주기 때문
}
}
5.3. HTTP API -json 응답
- json -> object
- 사용 method()
- new ObjectMapper(): json data를 파싱해서 사용할 수 있는 java object로 변환하기 위한 Jackson library
- json <-> JAVA obejct (양방향 가능)
- objectMapper.writeValueAsString(data) : java 객체를 JSON 문자로 변경한다.
- new ObjectMapper(): json data를 파싱해서 사용할 수 있는 java object로 변환하기 위한 Jackson library
package hello.servlet.basic.response;
@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
// 1. objectMapper 생성
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
// java class 객체에 http 응답 메시지 body에 넣을 data 담기
HelloData data = new HelloData();
data.setUsername("woowee");
data.setAge(26);
// 2. java 객체를 json 형태의 string으로 담기
String result = objectMapper.writeValueAsString(data); // <-> objectMapper.readValue(messageBody, HelloData.class);
// http message body에 넣기
response.getWriter().write(result);
}
}
// request 할 때 code
// http meassage body에서 data 꺼내는 작업
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);
// 꺼낸 data(=json)을 java 객체에 담아서 파싱하기
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
System.out.println("helloData.getUsername() = " + helloData.getUsername());
System.out.println("helloData.getAge() = " + helloData.getAge());
이전 발행글 : 스프링 MVC 0 - 웹애플리케이션 이해
다음 발행글 : 스프링 MVC 2 - 서블릿, jsp, MVC 패턴
출처 인프런 김영한의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술