1. 서블릿 시작하기
1.1 서블릿이란
- JVM 기반에서 웹 개발을 위한 명세, API
- 서블릿 실행을 위해 웹 애플리케이션 컨테이너가 필요
- Java EE에 포함된 스펙 중 하나
- 자바에서 HTTP 요청 및 응답을 처리하기 위한 내용 담고 있음
1.2 서블릿 설정
- build.gradle 파일에 JAR 파일 추가해서 사용
- example
buildscript{
repositories {
jcenter()
}
dependencies {
classpath 'org.akhikhl.gretty:gretty:+'
}
}
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'org.akhikhl.gretty'
apply plugin: 'eclipse'
apply plugin: 'idea'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
jcenter()
}
compileJava.options.encoding = 'UTF-8'
dependencies {
compile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'junit:junit:4.12'
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}
gretty{
httpPort = 8080
contextPath = '/'
servletContainer = 'jetty9'
}
def webappDir = "$rootDir/src/main/webapp"
eclipse{
classpath{
downloadSources = true
defaultOutputDir = file("${buildDir}/classes/main")
}
}
2. 서블릿 내부 동작
2.1 서브릿의 생명 주기
- start → initialize → service → destory
2.1.1 초기화
로드한 서브릿의 인스턴스를 생성하고 리소스 로드 등 클래스 생성자의 초기화 작업과 동일한 역할 수행
- 한 번만 호출됨
- annotation : WebServlet(name="Init", urlPatterns={"/init"}) 으로 맵핑
- 다수의 url로 맵핑 가능 { .., .. ...}
- HttpServlet을 상속 받아서 서블릿 생성 (일반적 방법)
- 서블릿을 상속 받아서 init 메소드 작성
- 초기화 할 때 파라미터 전달하고 싶은 경우 → servletConfig.getInitParameter 사용 → web.xml, WebInitParam 어노테이션 정보 전달
- example
package info.thecodinglive.basic;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// url mapping + 초기화 시 전달할 파라미터 설정
@WebServlet(
name = "initServlet", urlPatterns = {"/init"},
initParams = {@WebInitParam(name = "siteName", value = "jpub")}
)
// 서블릿 사용을 위해 상속해야 할 클래스 HttpServlet
public class InitServlet extends HttpServlet{
private String myParam = "";
public void init(ServletConfig servletConfig) throws ServletException{
System.out.println("init call");
this.myParam = servletConfig.getInitParameter("siteName");
System.out.println("입력받은 사이트 명은" + myParam + "입니다.");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("hello");
}
}
2.1.2 서비스
클라이언트 요청에 따라 호출할 메소드 결정
2.1.3 소멸
서브릿 언로드
3. 서블릿 활용
3.1 GET 요청 처리
- get 메소드 : 데이터를 url에 쿼리 스트링 형식으로 전송하면 브라우저로 respone 표현
- doGet 메소드 사용
- HttpServletRequest(요청 정보), HttpServletResponse(브라우저에 표현할 정보)를 파라미터로 전달 받음
@WebServlet(name="HelloWervlet", urlPatterns = {"/helloget"} )
public class HelloServlet extends HttpServelet {
protected void doGet (HttpServletRequest, request, HttpServletResponse response) throws ServeletException, IOException{
System.out.println("doGet 메소드 호출");
response.setCharaterEncoding("uft-8");
PrintWriter writer = response.getWriter();
response.setContentType("text/html");
writer.println("<html>");
writer.println("<head>hello doGet</head>");
writer.println("<body>do get request</body>");
writer.println("</html>");
}
}
3.2 POST 요청 처리
- post 요청에 대해서만 처리할 수 있어서 url이 /hellopost로 일치해도 405 에러 발생
- POST: 폼에서 데이터 입력 후 전송하는데 사용하는 방식
@WebServlet(name="HelloWervlet2", urlPatterns = {"/hellopost"} )
public class HelloServlet2 extends HttpServlet {
protected void doGet (HttpServletRequest, request, HttpServletResponse response) throws ServeletException, IOException{
System.out.println("doPost 메소드 호출");
}
}
- src/main/webapp/...html 만들기
- <form method="post" action="postsend"> <input type="text" name="user" /> ... </form>
- action : 요청을 보낼 경로 (submit 타입의 input 버튼을 클릭하면 해당 경로로 요청 전송)
- form 값을 보낼 action 경로와 서블릿의 urlpatterns를 일치시켜야 함
- doPost 메소드를 정의한 클래스의 어노테이션 설정 → @WebServlet(name="LoginServlet", urlPatterns={"/postsend"})
- urlpattern을 form 의 action 값과 동일하게 설정
- doPost 메소드 내부에 getparameter메소드로 파라미터 전달 받음
- ex) String user = request.getParameter("user");
- 여기서 user는 form 내부의 input 요소의 name으로 설정한 것
- 즉, POST 처리 메소드의 urlpattern 값은 input의 action과 같아야 하고, 요청에 대한 getParameter메소드의 값은 input의 name과 같아야 한다.
3.3 멀티 파트
- 바이너리 데이터 전송을 위해 사용 (파일을 바이너리로 전송)
- 서블릿 3.0 이후부터 스펙으로 추가됨 (별도의 라이브러리 필요 없음)
- <form method="post" action="upload" enctype="multipart/form-data"> ..
- Multipart annotation @
- @fileSizeThreshold : 파일 업로드 시 메모리에 저장되는 임시 파일 크기 정의 (int)
- @location : 파일 업로드시 임시 저장 디렉터리 지정 (String)
- @maxFileSize : 업로드할 파일 초대 크기 지정 (long)
- @maxRequestSize : request 최대 크기 지정 (long)
- 실제 파일 정보 request.getPart로 참조
- final Part filePart = request.getPart("file");
- part객체의 getHeader로 파일 정보 얻어옴
package info.thecodinglive.upload;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.*;
@WebServlet(urlPatterns = "/upload", name = "uploadServlet")
@MultipartConfig(
fileSizeThreshold = 1024 * 1024 * 2, // 2mb
maxFileSize = 1024 * 1024 * 10, // 10mb
maxRequestSize = 1024 * 1024 * 50, //50mb
location = "c:/upload" //파일저장위치
)
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//경로 form 내부에 input의 name이 destination인 값을 path로 가져옴
final String path = request.getParameter("destination");
//파일
final Part filePart = request.getPart("file");
//파일이름
final String fileName = getFileName(filePart);
final PrintWriter writer = response.getWriter();
try (OutputStream out = new FileOutputStream(new File(path + File.separator + fileName)); InputStream filecontent = filePart.getInputStream()) {
int read = 0;
final byte[] bytes = new byte[1024];
while ((read = filecontent.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
writer.print("new File: " + fileName + path + "에 생성되었습니다.");
} catch (FileNotFoundException fne) {
System.out.println(fne.getMessage());
}
}
private String getFileName(final Part part) {
final String partHeader = part.getHeader("content-disposition");
System.out.println("Part Header = {0}" + partHeader);
for (String content: part.getHeader("content-disposition").split(";")) {
if (content.trim().startsWith("filename")) {
return content.substring(
content.indexOf('=') + 1).trim().replace("\"", "");
}
}
return null;
}
}
4. 서블릿 관련 객체
4.1 웹 필터
- 클라이언트 요청에 대해 필요한 사전 작업이 있는 경우 필터 사용
- 필터 인터페이스 상송해서 생성
- init, destroy 메소드 가짐
- doFilter()
package info.thecodinglive.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
// url과 상관없이 모든 jsp파일에 대해 적용
@WebFilter("*.jsp")
public class FilterEx implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
res.setContentType("text/html");
res.setCharacterEncoding("UTF-8");
PrintWriter out = res.getWriter();
out.println("필터 동작 전");
// FilterChain chain : 여러 개의 필터를 맵핑해서 처리하기 위한 객체
chain.doFilter(req, res);
out.println("필터 동작 후");
}
@Override
public void destroy() {
}
}
- url 맵핑을 위해 WebFilter 어노테이션 사용
- @WelFilter("*jsp") : jsp 파일에 적용
4.2 쿠키
사용자가 방무한 사이트에 사용자의 컴퓨터에 저장되는 정보
- HTTP 헤더 정보에 포함되어 전달됨
4.2.1 쿠키 구성요소
- 이름 : 쿠키 식별키
- 값 : 특정 이름의 쿠키에 지정된 값
- 유효시간 : 쿠키 유지 시간
- 도메인 : 쿠키를 전송할 도메인
- 경로 : 쿠키를 전송할 요청 경로
4.2.2 쿠키 생성
- Cookie jcookie = new Cookie(name, value);
- import javax.servlet.http.Cookie;
package info.thecodinglive.cookie;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(urlPatterns = "/newcookie")
public class CookieCreateServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException,
IOException {
// 요청 정보 셋팅
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
// PrintWriter 객체 = response 객체의 getWriter로 선언
PrintWriter out = resp.getWriter();
out.println("<html><head><title> 쿠키 예제</title></head><body>");
out.println("<br/>");
// 쿠키 생성 + setMaxAge로 생성 만료 시간 1시간으로 설정
Cookie jcookie = new Cookie("jpub", "books");
jcookie.setMaxAge(3600);
resp.addCookie(jcookie);
out.println("<a href='/readcookie'>readcookie</a></body></html>");
}
}
4.2.3 쿠키 읽기
- request 객체의 getCookies 메소드로 저장된 쿠키를 cookies로 가져옴
- cookies가 있다면 모든 쿠키들을 getName하여 name을 확인하고 "jpub"과 일치하는 키를 갖는 쿠키를 가져와서 출력하는 코드
package info.thecodinglive.cookie;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/readcookie")
public class CookieReadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>쿠키 읽기</title></head><body>");
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("jpub")) {
out.println("cookie::" + cookie.getValue());
}
}
}
out.println("<a href='/modicookie'>쿠키수정</a></body></html>");
}
}
4.2.4 쿠키 수정 및 삭제
- 쿠키 값을 수정하려면 같은 이름의 쿠키를 생성하고 새로운 값을 지정해야 함
Cookie modifiedCookie = new Cookie("기존 수정하고자 하는 쿠키의 name", "새로운 값");
- 쿠키를 삭제하는 API가 따로 없고 유효시간을 0으로 설정하여 무효화하는 방식 사용
Cookie deletedCookie = new Cookie("name", "value");
deletedCookie.setMaxAge(0);
response.addCookie(deletedCookie);
4.3 세션
서버와 클라이언트의 유효간 연결을 식별하는 정보
- 클라이언트 요청 → 서버가 요청에 대해 식별할 수 있는 ID (세션 ID) 부여
- 주로 세션 ID는 쿠키로 저장
- 클라이언트 재접속시 해당 쿠키를 사용해서 세션 ID 값을 서버에 전달
- import javax.servlet.http;
4.3.1 세션 생성
- request.getSession();
- 생성한 session 객체의 getId를 통해 세션 id를 얻을 수 있음
- getCreationTime으로 세션 생성 시간 알 수 있음
- getLastAccessedTime으로 웹브라우저가 가장 마지막에 세션에 접근한 시간을 얻을 수 있음
package info.thecodinglive.session;
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 javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/session")
public class DefaultSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>세션</title></head><body>");
// 세션 생성
HttpSession session = req.getSession();
out.println("sessionId::" + session.getId() + "<br/>");
out.println("session created::" + session.getCreationTime() + "<br/>");
out.println("session lastAccessTime" + session.getLastAccessedTime() + "<br/>");
out.println("</body></html>");
}
}
4.3.2 세션 값 저장 및 삭제
- 세션 값 설정하기
HttpSession session = req.getSession();
session.setAttribute("name", "value");
- 세션 값 가져오기
HttpSession session = req.getSession();
String sessionValue = (String) session.getAttribute("name");
5. 디자인 패턴 활용
5.1 Java EE 패턴
자바 기반의 엔터프라이즈 웹 애플리케이션 개발을 위한 패턴
- Java EE 패턴 목록
- intercepting filter : 요청에 대한 전처리 및 후처리 솔루션 제공 (HTTP 요청 처리 메소드 등)
- front controller : 요청 처리 관리하는 중앙 컨트롤러, 보안, 뷰 관리, 탐색 관리
- view helper: 뷰 표현을 위해 비즈니스 로직이 가지고 있는 개념상의 helper, 비즈니스 로직과 뷰 로직을 분리하기 위해 사용
- composite view : 레고 블럭 같은 작은 뷰를 조합해서 만든 전체 뷰
- service to worker : front controller와 view helper를 이용해 dispather 컴포넌트 구성, 뷰에 대한 처리 이전에 동작
- dispatcher view : service to worker과 동일하지만 뷰에 대한 처리 중에 수행되며 작은 시스템에서 더 안정적
5.2 프론트 컨트롤러 패턴
컨트롤러가 공통 요청을 먼저 수행하고 뷰를 호출하는 패턴
- 클라이언트 요청에 대해 컨트롤러가 응답하고 결과에 따라 서블릿이나 JSP로 만든 뷰를 보여줌
- 방법 1) response.sendRedirect(경로)
- 응답을 서버에서 클라이언트로 보내고 나면 속성을 저장할 수 없고 다른 로직을 추가할 수 없음
- 방법 2) requestDispatcher.forward(ServletRequest request, ServletResponse response)
- 서버 내부에서만 흐름이 이동하기 때문에 속성을 저장할 수 있고, 클라이언트에게 바로 전달하지 않고 원하는 작업을 처리한 후에 응답을 전환할 수 있음
- 컨트롤러를 만들때 많이 사용하는 메소드
- 절대 경로로 지정 (상대 경로 불가능)
- url을 if 문으로 분기처리 하여 컨트롤
5.3 커맨드 패턴
로직을 객체 안에 캡슐화해서 저장하여 컨트롤러와 같은 클래스를 수정하지 않고 재사용할 수 있게 하는 패턴
- 커맨드 클래스는 abstract
- request와 response를 변수로 선언하고 setter 메소드로 컨트롤러에서 req, res 인스턴스를 가져올 수 있게 한다.
package info.thecodinglive.pattern;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public abstract class Command {
private HttpServletRequest req;
private HttpServletResponse res;
private ServletContext servletContext;
abstract public void execute();
public void forward(String url){
try{
RequestDispatcher rd = req.getRequestDispatcher(url);
rd.forward(getReq(), getRes());
}catch (IOException ioe){
servletContext.log("forward Error",ioe);
}catch (ServletException servletEx){
servletContext.log("servlet Error", servletEx);
}
}
public HttpServletRequest getReq() {
return req;
}
public void setReq(HttpServletRequest req) {
this.req = req;
}
public HttpServletResponse getRes() {
return res;
}
public void setRes(HttpServletResponse res) {
this.res = res;
}
public ServletContext getServletContext() {
return servletContext;
}
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
- 커맨드 클래스 객체에 제공할 서블릿 클래스
package info.thecodinglive.pattern;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
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.InputStream;
import java.util.Properties;
@WebServlet(urlPatterns = "/controller", initParams = {@WebInitParam(name = "mapping", value = "/WEB-INF/command.properties")})
public class FrontController extends HttpServlet {
private Properties cmdMapping;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
InputStream is = null;
try {
String location = config.getInitParameter("mapping");
is = getServletContext().getResourceAsStream(location);
cmdMapping = new Properties();
cmdMapping.load(is);
} catch (IOException e) {
getServletContext().log("I/O Error", e);
} finally {
try {
is.close();
} catch (IOException iog) {
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String cmd = req.getParameter("cmd");
String cmdClass = (String) cmdMapping.get(cmd);
Command command = null;
try {
command = (Command) Class.forName(cmdClass).newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException ex) {
getServletContext().log("class not found", ex);
}
command.setReq(req);
command.setRes(resp);
command.setServletContext(getServletContext());
command.execute();
}
}
- 모든 요청은 frontContoller 클래스가 받고
- Command 클래스를 상속 받은 뷰 클래스가 jsp 파일을 호출
- 프론트 컨트롤러로 뷰 페이지 요청을 한 곳에서 처리할 수 있게 함.
References
- 스프링 부트로 배우는 자바 웹 개발 (윤석진) 기술서를 참고하여 정리한 내용입니다.
'웹 개발 > Spring Boot' 카테고리의 다른 글
Spring Boot) 4. 스프링 부트 웹 개발 (2) | 2021.04.28 |
---|---|
Spring boot) 3. 스프링 프레임워크 (0) | 2021.04.28 |
Spring Boot) 1. 개발 환경의 변화와 자바 (0) | 2021.04.28 |
Spring Boot) Spring Boot 프로젝트 예제로 기초 이해하기 / Building an Application with Spring Boot (0) | 2021.04.22 |
Spring Boot) Gradle이란 (0) | 2021.04.22 |