서블릿에 대해 알고 싶다면 --> https://velog.io/@yoon_bly/Servlet%EA%B3%BC-JSP
서블릿은 톰캣 같은 웹 어플리케이션 서버를 직접 설치하고, 서블릿 코드를 클래스 파일로 빌드해서 올린 후, 톰캣 서버를 실행해야 한다.
하지만 방법이 복잡하기 때문에 우리는 좀더 발전된 기술 "스프링 부트"를 사용하여 서버 설치 없이 실행할 것이다.
@ServletComponentScan
스프링 부트는 서블릿을 직접 등록해 사용할 수 있도록 어노테이션을 지원한다.
해당 어노테이션은 main메서드에 추가하면 된다.
가장 기본이 되는 파일들이다.
실제 동작하는 서블릿 코드를 등록해보자.
package hello.servlet.basic;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@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);
String username = request.getParameter("username");
System.out.println("username = " + username);
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
response.getWriter().write("hello" + username);
}
}
@WebServlet
protected void service(HttpServletRequest request, HttpServletResponse response)
HTTP 요청을 통해 매핑된 URL이 호출되면 서블릿 컨테이너는 다음 메서드를 실행한다.
HTTP 요청을 받을 시 HTTP 요청 메시지를 기반으로 request와 response 생성
서블릿 파일을 확인 한 후 클라이언트에게 response를 객체정보로 HTTP 응답 메시지를 던짐
HTTP 요청 메시지는 여러가지 분류로 나누어져 있다. 이 메시지를 개발자가 직접 파싱해서 해석한 후 응답 메시지를 보내도 되지만, 불편한 일이 한두가지가 아니다.
그래서 서블릿이라는 것이 탄생한 것인데, 서블릿은 개발자가 HTTP 요청 메시지를 편리하게 사용할 수 있도록 개발자 대신 파싱해주는 역할 이다.
그리고 서블릿으로 파싱된 HTTP 요청 메시지의 결과를 HttpServletRequest 객체에 담아서 제공한다.
HttpServletRequest를 사용하면 다음과 같은 HTTP 요청 메시지를 편리하게 조회할 수 있다.
POST /save HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
username=kim&age=20
HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자.
주로 다음 3가지 방법을 사용한다.
GET - 쿼리 파라미터
POST - HTML Form
HTTP message body에 직접 데이터를 담아 요청
HTTP에도 여러가지 메서드들이 있다. 그 중 GET 방식을 이용한다.
이 방법은 메시지 바디 없이, URL의 쿼리 파라미터를 사용해서 데이터를 전달한다.
ex) 검색, 필터, 페이징등에서 많이 사용하는 방식
쿼리 파라미터는 URL 주소뒤에 ?
를 시작으로 파라미터를 보낼 수 있다. 추가 파라미터는 &
로 구분해서 보낸다.
http://localhost:8080/request-param?username=hello&age=20
서버에서는 HttpServletRequest
가 제공하는 다음 메서드를 통해 쿼리 파라미터를 편리하게 조회할 수 있다.
package hello.servlet.basic.request;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
/**
* 1. 파라미터 전송기능
* http://localhost:8080/request-param?username=hello&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");
request.getParameterNames().asIterator() //요청받은 파라미터를 다 꺼낸다.
.forEachRemaining(paraName -> System.out.println(paraName + " = " + request.getParameter(paraName)));
System.out.println("[전체 파라미터 조회] - end");
System.out.println();
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");
}
}
실행결과
[전체 파라미터 조회] - start
username=hello
age=20
[전체 파라미터 조회] - end
[단일 파라미터 조회]
request.getParameter(username) = hello
request.getParameter(age) = 20
[이름이 같은 복수 파라미터 조회]
request.getParameterValues(username)
username=hello
username=hello&username=kim
과 같이 파라미터 이름은 하나인데, 값이 중복이면 어떻게 될까?
중복되는 파라미터일 때는 request.getParameterValues()
를 사용해야 각각의 파라미터를 조회할 수 있다.
이 방법은 HTML의 Form태그를 사용해 클라이언트에서 서버로 데이터를 전송하는 방법이다.
ex) 주로 회원 가입, 상품 주문 등에서 사용한다.
특징
POST의 HTML Form형식으로 전송하면 다음 형식으로 HTTP 메시지를 만든다.
컨텐츠 타입은 GET방식의 쿼리파라미터 형식과 같다.
클라이언트(웹 브라우저)입장에서는 두 방식에 차이가 있지만, 서버 입장에서는 둘의 형식이 동일하기 때문에, 서블릿의 request.getParameter() 메서드로 편리하게 조회할 수 있다.
참고
content-type은 HTTP 메시지 바디의 데이터 형식을 지정한다.
- GET URL 쿼리 파라미터 형식으로 클라이언트에서 서버로 데이터를 전달할 때는 HTTP 메시지 바디를 사용하지 않기 때문에 content-type이 없다.
- POST HTML Form 형식으로 데이터를 전달하면 HTTP 메시지 바디에 해당 데이터를 포함해서 보내기 때문에 바디에 포함된 데이터가 어떤 형식인지 content-type을 꼭 지정해야 한다. 이렇게 폼으로 데이터를 전송하는 형식을 application/x-www-form-urlencoded 라 한다.
특징
package hello.servlet.basic.request;
import org.springframework.util.StreamUtils;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@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);
response.getWriter().write("Ok");
}
}
Postman을 이용해서 테스트 해보자.
postman에서 보내는 형식을 text로 변경 후, hello!라는 문자열을 보냈더니 결과 값이 잘 나왔다.
참고
inputStream은 byte 코드를 반환한다. byte 코드를 우리가 읽을 수 있는 문자(String)로 보려면 문자표 (Charset)를 지정해주어야 한다. 여기서는 UTF_8 Charset을 지정해주었다.
이번에는 JSON형식으로 데이터를 전달해보자.
JSON 형식 전송
Json 형식 파싱 추가
JSON 형식으로 파싱할 수 있게 객체를 하나 생성한다.
package hello.servlet.basic;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class HelloData {
private String username;
private int age;
}
이 객체는 메시지 바디를 가져와 해당 변수들을 setter로 넣어주고 getter로 출력하는 역할이다.
package hello.servlet.basic.request;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.HelloData;
import org.springframework.util.StreamUtils;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@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.Username = " + helloData.getUsername());
System.out.println("helloData.Age = " + helloData.getAge());
response.getWriter().write("ok");
}
}
출력결과
messageBody={"username": "hello", "age": 20}
data.username=hello
data.age=20
참고
JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환
라이브러리를 추가해서 사용해야 한다. 스프링 부트로 Spring MVC를 선택하면 기본으로 Jackson
라이브러리( ObjectMapper )를 함께 제공한다.
참고
HTML form 데이터도 메시지 바디를 통해 전송되므로 직접 읽을 수 있다. 하지만 편리한 파리미터 조회 기능( request.getParameter(...) )을 이미 제공하기 때문에 파라미터 조회 기능을 사용하면 된다.
HTTP 응답 메시지
등으로 이루어져 있다.
package hello.servlet.basic.response;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@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>");
}
}
HTML로 응답을 할 때 서블릿은 전 포스트에서 말했듯 자바 코드 안에 html 코드가 들어간다.
HTML코드가 길어지면 확실히 어지러울거 같긴 하다.
package hello.servlet.basic.response;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.servlet.basic.HelloData;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//Content-Type: application/json
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
HelloData helloData = new HelloData();
helloData.setUsername("yoon");
helloData.setAge(20);
//{"username" : "yoon", "age" : 20}처럼 json타입으로 변경
String result = objectMapper.writeValueAsString(helloData);
response.getWriter().write(result);
}
}
HTTP 응답으로 JSON으로 반환하려면 꼭 content-type을 applicatioin/json으로 지정해야 json형식으로 보내진다.(content-type 잘 지키기!!)
서블릿으로 처음부터 공부하고 있다. 확실히 서블릿을 공부해야 하긴 하는 것을 느끼지만, 한편으로는 지금 서블릿을 대신하는 새로운 언어 및 프레임워크가 있다는 것에 감사해야 할것 같다. 정말 이 전 세대의 개발자님들은 불편하고 어려웠을 것 같다.
HTTP 요청 및 응답을 할 때의 스펙들을 알수 있던 시간 이었다. content-type이 어느 용도 인지 등등을 알게 된 강의 였다. 나중에 조금 더 자세히 공부 할 수 있도록 해야겠다.
References(참고자료)
https://www.inflearn.com/