1. 서블릿
1-1. 서블릿
* 서블릿 : 서버에서 실행되는 자바 프로그램
- jakarta.servlet.Servlet <interface>
- 모든 서블릿 클래스가 반드시 구현해야하는 인터페이스다.
- 서블릿의 라이프사이클 메소드가 정의되어 있다. (생성, 구현, 소멸의 과정)
- 주요 메소드
- void init(ServletConfig config)
- 서블릿 객체가 초기화될 때 실행되는 메소드다. - void destroy()
- 서블릿 객체가 폐기될 때 실행되는 메소드다. - void service(ServletReuest request, ServletResponse response)
- 서블릿 객체가 클라이언트의 요청을 처리할 때 실행되는 메소드다.
- void init(ServletConfig config)
- jakarta.servlet.GenericServlet <Abstract Class>
- Servlet 인터페이스를 구현하는 추상 클래스다.
- Servlet 인터페이스의 주요 메소드를 대부분 구현하고 있다.
- Servlet 인터페이스에 정의되어 있지 않은 추가 메소드를 제공한다.
- 주요 메소드
- void init()
- String getinitParameter(String name)
- 서블릿 초기화파라미터값을 반환한다. - ServletConfig getServletConfig()
- ServletConfig 객체를 반환한다. - ServletContext getServletContext()
- ServletContext 객체를 반환한다.
- jakarta.servlet.HttpServlet <Abstract Class>
- HTTP 프로토콜에 특화된 서블릿을 개발할 때 제너릭 서블릿을 상속받는 클래스다.
- 주요 메소드
- void service(HttpServletRequest request, HttpServletResponse response)
- 클라이언트의 요청을 분석해서 요청방식에 맞는 do ~ 메소드를 실행한다.
- 요청방식에 상관없이 항상 실행되는 메소드다.
- service() 메소드의 본래 기능을 버리고, 클라이언트의 요청을 처리하는 코드로 재정의한다.
- void service(HttpServletRequest request, HttpServletResponse response)
* 제네릭서브릿에서 init, destroy, service를 모두 구현하고 있음 -> HttpServlet에서 얘네를 재정의 해도되고 안해도됨 (service는 무조건 재정의)
- HelloServlet.java
package com.sample.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
@Override
public void init() throws ServletException {
System.out.println("HelloServlet의 init() 메소드 실행됨...");
}
@Override
public void destroy() {
System.out.println("HelloServlet의 destroy() 메소드 실행됨...");
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HelloServlet의 service() 메소드 실행됨...");
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<!doctype html>");
out.println("<html lang='ko'>");
out.println("<head>");
out.println("<meta charset='utf-8'>");
out.println("<title>헬로 서블릿</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>헬로 서블릿</h1>");
out.println("<p>나는 서블릿입니다.</p>");
out.println("</body>");
out.println("</html>");
}
}
* 시작할 때 -> init() 메소드 실행
뭔가를 수행할 때마다 -> service() 메소드 실행
새로운 소스를 추가할 때마다 -> 원래 객체가 폐기되고 새로운 객체 생성 -> destroy() 메소드 실행
* 싱글턴 객체 : 프로그램이 실행되는 동안 한번 만들어서 계속 사용 (작업을 수행하는 객체) (ex. 서블릿 객체)
* 프로토타입 객체 : 필요할 때마다 생성해야 하는 객체 (ex. VO객체)
톰캣이 얘네를 담고 있어
- 톰캣
- 웹 애플리케이션 서버 (Web Application Server : WAS)
- 서블릿/JSP 엔진이다. (=서블릿/JSP를 실행시킨다.)
- 서블릿/JSP 컨테이너다. (서블릿/JSP 객체를 생성,관리,유지,저장한다.)
1-2. 필터
필터 : JSP나 서블릿의 실행 전후에 실행
실행 순서
요청이 첫번째 필터로 전달 -> doFlter 전처리 수행문 실행 -> 다음 doFIlter를 실행하도록 매개변수로 요청 전달
-> 두번쨰 필터로 전달 -> doFilter 전처리 수행문 실행 -> 다음 doFilter를 실행하도록 매개변수로 요청 전달
-> 다음에 실행할게 없으면 -> JSP나 서블릿으로 요청을 전달, 요청 처리
-> 응답을 두번째 필터로 전달, 후처리 -> 응답을 첫번째 필터로 전달, 후처리 -> 응답 출력
- SampleFilter.java
package com.sample.filter;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
@WebFilter("/*")
public class SampleFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("SampleFilter의 init() 메소드 실행됨");
}
@Override
public void destroy() {
System.out.println("SampleFilter의 destroy() 메소드 실행됨");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("SampleFilter의 doFilter() 메소드의 전처리 작업 실행됨");
filterChain.doFilter(request, response);
System.out.println("SampleFilter의 doFilter() 메소드의 후처리 작업 실행됨");
}
}
HelloServlet의 init() 메소드 실행됨...
SampleFilter의 doFilter() 메소드의 전처리 작업 실행됨
HelloServlet의 service() 메소드 실행됨...
SampleFilter의 doFilter() 메소드의 후처리 작업 실행됨
* 필터가 생성될 때 쌍으로 생성되는 객체가 있음 -> FilterConfig
1-3. 서블릿 배포하기 (인터넷에서 실행)
- @WebServlet으로 배포하기
public class HelloServlet extends HttpServlet {
} - web.xml에서 <servlet>, <servlet-mapping>으로 배포하기
<servlet>
<servlet-name>별칭</servlet-name>
<servlet-class>서블릿클래스의 전체이름(패키지경로와 클래스명)</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>별칭</servlet-name>
<url-pattern>매핑할 URL</url-pattern>
</servlet-mapping>
: 이런 URL이 오면 그 별칭을 찾아서 서블릿 배포
* 초기화 파라미터
설정정보를 자바 소스코드 내부가 아닌 외부에서 설정하고 값을 불러올 수 있다
초기화 파라미터가 ServletConfig 객체에 담김
-> HelloServlet객체에서 초기화 파라미터 값을 가져가서 쓸 수 있음
- web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="5.0">
<!--
/hello URL 요청이 오면 hello-servlet이라는 별칭으로 배포된 com.sample.servlet.HelloServlet을 실행하라.
-->
<servlet>
<servlet-name>hello-servlet</servlet-name>
<servlet-class>com.sample.servlet.HelloServlet</servlet-class>
<!--
HelloServlet에게 전달할 초기화 파라미터 값 설정하기
-->
<init-param>
<param-name>email</param-name>
<param-value>admin@sample.com</param-value>
</init-param>
<init-param>
<param-name>tel</param-name>
<param-value>02) 1234-5678</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>hello-servlet</servlet-name>
<url-pattern>/hello</url-pattern>
<url-pattern>/hi</url-pattern>
<url-pattern>/ye</url-pattern>
</servlet-mapping>
</web-app>
- HelloServlet.java
package com.sample.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
//@WebServlet("/hello")
public class HelloServlet extends HttpServlet{
@Override
public void init() throws ServletException {
System.out.println("HelloServlet의 init() 메소드 실행됨...");
}
@Override
public void destroy() {
System.out.println("HelloServlet의 destroy() 메소드 실행됨...");
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("HelloServlet의 service() 메소드 실행됨...");
// 애플리케이션의 초기화 파라미터 정보를 제공하는 ServletContext 객체를 획득한다.
ServletContext application = getServletContext();
String companyNameValue = application.getInitParameter("company-name");
// 서블릿의 초기화 파라미터 정보를 제공하는 ServletConfig 객체를 획득한다.
ServletConfig config = getServletConfig();
// String getIntiParameter(String name) 메소드로 초기화 파라미터값을 조회한다.
String emailValue = config.getInitParameter("email");
String telValue = config.getInitParameter("tel");
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
out.println("<!doctype html>");
out.println("<html lang='ko'>");
out.println("<head>");
out.println("<meta charset='utf-8'>");
out.println("<title>헬로 서블릿</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>헬로 서블릿</h1>");
out.println("<p>나는 서블릿입니다.</p>");
out.println("<p>서블릿의 라이프 사이클 메소드를 확인하세요.</p>");
out.println("<p>회사명 : " + companyNameValue + "</p>");
out.println("<p>이메일 : " + emailValue + "</p>");
out.println("<p>연락처 : " + telValue + "</p>");
out.println("</body>");
out.println("</html>");
}
}
HelloServlet의 init() 메소드 실행됨...
FirstFilter의 doFilter() 메소드의 전처리 작업 실행됨
SecondFilter의 doFilter() 메소드의 전처리 작업 실행됨
SecondFilter의 비밀키 : 1a2cb5fde675caed3
HelloServlet의 service() 메소드 실행됨...
SecondFilter의 doFilter() 메소드의 후처리 작업 실행됨
FirstFilter의 doFilter() 메소드의 후처리 작업 실행됨
1-4. 필터 배포하기 (인터넷에서 실행)
- @webFilter로 배포하기
@WebFilter("/*")
public class FirstFilter implements Filter {
} - web.xml에서 <filter>, <filter-mapping>으로 배포하기
<filter>
<filter-name>별칭</filter-name>
<filter-class>서블릿클래스의 전체이름(패키지경로와 클래스명)</filter-class>
</filter>
<filter-mapping>
<filter-name>별칭</filter-name>
<url-pattern>매핑할 URL</url-pattern>
</filter-mapping>
: 이런 URL이 오면 그 별칭을 찾아서 서블릿 배포
- web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="5.0">
<filter>
<filter-name>first</filter-name>
<filter-class>com.sample.filter.FirstFilter</filter-class>
</filter>
<filter>
<filter-name>second</filter-name>
<filter-class>com.sample.filter.SecondFilter</filter-class>
<init-param>
<param-name>secret-key</param-name>
<param-value>1a2cb5fde675caed3</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>first</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>second</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- FirstFilter.java
package com.sample.filter;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
public class FirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("FirstFilter의 init() 메소드 실행됨");
}
@Override
public void destroy() {
System.out.println("FirstFilter의 destroy() 메소드 실행됨");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("FirstFilter의 doFilter() 메소드의 전처리 작업 실행됨");
filterChain.doFilter(request, response);
System.out.println("FirstFilter의 doFilter() 메소드의 후처리 작업 실행됨");
}
}
- SecondFilter.java
package com.sample.filter;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
//@WebFilter("/*")
public class SecondFilter implements Filter{
private String secretKey;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("SecondFilter의 init() 메소드 실행됨");
// 필터의 초기화 파라미터값 조회하기
this.secretKey = filterConfig.getInitParameter("secret-key");
}
@Override
public void destroy() {
System.out.println("SecondFilter의 destroy() 메소드 실행됨");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("SecondFilter의 doFilter() 메소드의 전처리 작업 실행됨");
// 필터의 초기화 파라미터값 사용하기
System.out.println("SecondFilter의 비밀키 : " + secretKey);
filterChain.doFilter(request, response);
System.out.println("SecondFilter의 doFilter() 메소드의 후처리 작업 실행됨");
}
}
HelloServlet의 init() 메소드 실행됨...
FirstFilter의 doFilter() 메소드의 전처리 작업 실행됨
SecondFilter의 doFilter() 메소드의 전처리 작업 실행됨
SecondFilter의 비밀키 : 1a2cb5fde675caed3
HelloServlet의 service() 메소드 실행됨...
SecondFilter의 doFilter() 메소드의 후처리 작업 실행됨
FirstFilter의 doFilter() 메소드의 후처리 작업 실행됨
11월 18, 2022 2:47:47 오후 org.apache.catalina.core.StandardContext reload
정보: 이름이 [/web-sample]인 컨텍스트를 다시 로드하는 작업이 시작되었습니다.
HelloServlet의 destroy() 메소드 실행됨...
FirstFilter의 destroy() 메소드 실행됨
SecondFilter의 destroy() 메소드 실행됨
11월 18, 2022 2:47:50 오후 org.apache.jasper.servlet.TldScanner scanJars
정보: 적어도 하나의 JAR가 TLD들을 찾기 위해 스캔되었으나 아무 것도 찾지 못했습니다. 스캔했으나 TLD가 없는 JAR들의 전체 목록을 보시려면, 로그 레벨을 디버그 레벨로 설정하십시오. 스캔 과정에서 불필요한 JAR들을 건너뛰면, 시스템 시작 시간과 JSP 컴파일 시간을 단축시킬 수 있습니다.
FirstFilter의 init() 메소드 실행됨
SecondFilter의 init() 메소드 실행됨
11월 18, 2022 2:47:50 오후 org.apache.catalina.core.StandardContext reload
정보: 이름이 [/web-sample]인 컨텍스트를 다시 로드하는 것을 완료했습니다.
HelloServlet의 init() 메소드 실행됨...
FirstFilter의 doFilter() 메소드의 전처리 작업 실행됨
SecondFilter의 doFilter() 메소드의 전처리 작업 실행됨
SecondFilter의 비밀키 : 1a2cb5fde675caed3
HelloServlet의 service() 메소드 실행됨...
SecondFilter의 doFilter() 메소드의 후처리 작업 실행됨
FirstFilter의 doFilter() 메소드의 후처리 작업 실행됨
1-5. 서블릿, 필터 등 모든 곳에서 사용 가능한 초기화 파라미터
- 웹 애플리케이션 전체에서 사용가능한 초기화 파라미터 설정
모든 서블릿/JSP/필터/리스너에서 사용가능하다.
<context-param />으로 설정한 초기화파라미터 값은 ServletContext 객체의 초기화파라미터로 저장된다.
ServletContext는 웹 애플리케이션 프로젝트 당 하나 생성되는 객체다.
WAS가 켜질 때 ServletContext 객체가 생성되고, WAS가 종료될 때 ServletContext 객체는 폐기된다.
ServletContext 객체는 서블릿의 여러 객체 중에서 가장 오랫동안 유지되는 객체다.
ServletContext 객체에는 속성과 초기화 파라미터를 저장할 수 있다.
ServletContext 객체에 저장된 속성과 초기화 파라미터는 모든 서블릿/JSP/필터/리스너에게 공유된다.
2. 게시판 - 첨부파일 업로드
0. MultipartRequest.java파일을 유틸에 저장
package com.sample.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.tomcat.util.http.fileupload.FileItem;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
/**
* 첨부파일 업로드를 지원하는 클래스다.
* @author lee_e
*
*/
public class MultipartRequest {
private HttpServletRequest request;
private String directory;
private Map<String, List<FileItem>> parameterMap;
private Map<String, List<String>> formFieldMap = new HashMap<>();
/**
* MultipartRequest 객체를 초기화한다.
* <p> enctype="multipart/form-data" 요청을 처리하는 클래스다.
* <p> MultipartRequest 객체를 생성하면 요청 메시지를 바디부에 저장해 폼 입력값, 첨부파일을 분석하고 처리한다.
* <p> MultipartRequest 객체를 생성하면 업로드된 첨부파일은 지정된 디렉토리에 자동으로 저장된다.
*
* <p> 폼 입력값은 MultipartRequest가 제공하는
* String getParameter(String name), String[] getParameterValues(String name)을 이용해서 조회할 수 있다.
* <p> 첨부파일이름은 MultipartRequest가 제공하는
* String getFilename(String name)을 이용해서 조회할 수 있다.
* @param request 요청객체
* @param saveDirectory 첨부파일 저장디렉토리 경로
*/
public MultipartRequest(HttpServletRequest request, String saveDirectory) throws ServletException, IOException {
this.request = request;
this.directory = saveDirectory;
createParameterMap();
parseParameterMap();
}
private void createParameterMap() throws ServletException, IOException {
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setDefaultCharset("utf-8");
ServletFileUpload upload = new ServletFileUpload(factory);
parameterMap = upload.parseParameterMap(request);
}
private void parseParameterMap() throws IOException {
Set<Entry<String, List<FileItem>>> entries = parameterMap.entrySet();
for (Entry<String, List<FileItem>> entry : entries) {
String filedName = entry.getKey();
List<String> fieldValues = new ArrayList<>();
List<FileItem> items = entry.getValue();
for (FileItem item : items) {
if (!item.isFormField()) {
if (!item.getName().isEmpty()) {
String filename = item.getName();
fieldValues.add(filename);
InputStream in = item.getInputStream();
OutputStream out = new FileOutputStream(new File(directory, filename));
IOUtils.copy(in, out);
in.close();
out.close();
}
} else {
String value = item.getString();
fieldValues.add(value);
}
}
if (!fieldValues.isEmpty()) {
formFieldMap.put(filedName, fieldValues);
}
}
}
/**
* 지정된 이름의 요청파라미터값을 반환한다.
* @param name 요청파라미터 이름
* @return 요청파라미터 값, 지정의 이름의 요청파라미터가 없으면 null을 반환한다.
*/
public String getParameter(String name) {
List<String> values = formFieldMap.get(name);
if (values == null) {
return null;
}
if (values.isEmpty()) {
return null;
}
return values.get(0);
}
/**
* 지정된 이름의 요청파라미터값을 반환한다.
* @param name 요청파라미터 이름
* @return 요청파라미터 값, 지정의 이름의 요청파라미터가 없으면 null을 반환한다.
*/
public String[] getParameterValues(String name) {
List<String> values = formFieldMap.get(name);
if (values == null) {
return null;
}
if (values.isEmpty()) {
return null;
}
return values.toArray(new String[values.size()]);
}
/**
* 지정된 이름의 파일 입력필드로 업로드된 첨부파일이름을 반환한다.
* @param name 파일 입력필드의 이름
* @return 업로드된 첨부파일이름, 업로드된 첨부파일이 없으면 null을 반환한다.
*/
public String getFilename(String name) {
return getParameter(name);
}
/**
* 지정된 이름의 파일 입력필드로 업로드된 첨부파일이름을 반환한다.
* @param name 파일 입력필드의 이름
* @return 업로드된 첨부파일이름, 업로드된 첨부파일이 없으면 null을 반환한다.
*/
public String[] getFilenames(String name) {
return getParameterValues(name);
}
}
1. <form /> 속성에 속성 추가하기
- <form method="post" enctype="mulipart/form-data" action="register.jsp">
- method=post
- post 방식의 요청만 첨부파일 업로드를 지원한다.
- enctype="multipart/form-data"
- emctype은 요청메시지의 바디부에 포함되는 데이터의 인코딩 방식을 지정하는 속성이다.
- enctype="application/x-www-form-urlencoded"
- <form />의 기본 enctype
- 폼입력요소의 값을 url의 쿼리스트링과 같은 형식으로 변환해서 요청메시지의 바디부에 포함시킨다.
- 이 방식은 첨부파일 업로드가 불가능하다. => 첨부파일이 없을 때 이 방식을 씀
- 요청메시지의 요청헤더의 콘텐츠 타입이 아래와 같이 지정된다.
Content-Type: application/x-www-form-urlencoded - 예시)
title=연습&write=홍길동&content=게시글 작성 연습입니다. ( 원래 여태까지 했던 방식 )
- enctype="multipart/form-data"
- 첨부파일 업로드가 있는 폼에서 사용하는 인코딩 방식
- 요청메시지의 요청헤더의 콘텐츠 타입이 아래와 같이 지정된다.
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoPqBs89onRcKRXQd - 요청 메시지의 바디부 예시)
----WebKitFormBoundaryoPqBs89onRcKRXQd
Content-Disposition: form-data; name="title"
파일업로드 연습
----WebKitFormBoundaryoPqBs89onRcKRXQd
Content-Disposition: form-data; name="writer"
홍길동
----WebKitFormBoundaryoPqBs89onRcKRXQd
Content-Disposition: form-data; name="content"
파일업로드 연습입니다. 연습입니다. 연습입니다.
----WebKitFormBoundaryoPqBs89onRcKRXQd
Content-Disposition: form-data; name="attachedFile"; filename="sample.png"
Content-Type: image/png
이미지파일 데이터 ...............
----WebKitFormBoundaryoPqBs89onRcKRXQd
- emctype은 요청메시지의 바디부에 포함되는 데이터의 인코딩 방식을 지정하는 속성이다.
- method=post
- board.java (게시판)에 fileName 변수 추가하기
package com.sample.vo;
import java.util.Date;
public class Board {
private int no;
private String title;
private String writer;
private int readCount;
private int reviewCount;
private String content;
private String deleted;
private Date createdDate;
private Date updatedDate;
private String fileName; // 첨부파일명
public Board() {}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public int getReadCount() {
return readCount;
}
public void setReadCount(int readCount) {
this.readCount = readCount;
}
public int getReviewCount() {
return reviewCount;
}
public void setReviewCount(int reviewCount) {
this.reviewCount = reviewCount;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getDeleted() {
return deleted;
}
public void setDeleted(String deleted) {
this.deleted = deleted;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public Date getUpdatedDate() {
return updatedDate;
}
public void setUpdatedDate(Date updatedDate) {
this.updatedDate = updatedDate;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
- boards.xml 에 board_file_name 추가하기
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="boards">
<insert id="insertBoard" parameterClass="com.sample.vo.Board">
insert into sample_boards
(board_no, board_title, board_writer, board_content, board_file_name)
values
(sample_boards_seq.nextval, #title#, #writer#, #content#, #fileName#)
</insert>
<select id="getBoardByNo" parameterClass="int" resultClass="com.sample.vo.Board">
select
board_no as no,
board_title as title,
board_writer as writer,
board_read_count as readCount,
board_review_count as reviewCount,
board_content as content,
board_deleted as deleted,
board_created_date as createdDate,
board_updated_date as updatedDate,
board_file_name as fileName
from
sample_boards
where
board_no = #value#
</select>
<update id="updateBoard" parameterClass="com.sample.vo.Board">
update
sample_boards
set
board_title = #title#,
board_writer = #writer#,
board_read_count = #readCount#,
board_review_count = #reviewCount#,
board_content = #content#,
board_deleted = #deleted#,
board_file_name = #fileName#,
board_updated_date = sysdate
where
board_no = #no#
</update>
</sqlMap>
- form.jsp (게시글 등록 폼) 에서 첨부파일 등록하기
* enctype="multipart/form-data" 추가
* 첨부파일 항목 추가
<p>제목, 작성자, 내용을 입력해서 새 게시글을 등록하세요</p>
<form class="bg-light border p-3" method="post" action="register.jsp" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">첨부파일</label>
<input type="file" class="form-control" name="attachedFile" />
</div>
- register.jsp (게시물 등록) 에 파일 첨부하는 객체와 메소드 추가
// 멀티파트 요청처리를 지원하는 MultipartRequest 객체를 생성한다.
// MultipartRequest는 multipart/form-data 요청처리를 지원하는 클래스다.
// 아래의 생성자를 실행해서 MultipartRequest 객체를 생성하고 초기화한다.
// MultipartRequest mr = new MultipartRequest(요청객체, 첨부파일저장디렉토리);
MultipartRequest mr = new MultipartRequest(request, "C:\\app\\web-workspace\\temp");
// 요청객체에 저장된 요청파라미터값을 가져온다.
String title = mr.getParameter("title");
String content = mr.getParameter("content");
String filename = mr.getParameter("attachedFile");
System.out.println("게시글 제목: " + title);
System.out.println("게시글 내용: " + content);
System.out.println("게시글 첨부파일명: " + filename);
- detail.jsp (상세정보)에 첨부파일 칸 추가
<tr>
<th>첨부파일</th>
<td colspan="3">
<%=board.getFileName() != null ? board.getFileName() : "없음" %>
<%
if(board.getFileName() != null){
%>
<a href="../download?no=<%=board.getNo() %>" class="ms-5 btn btn-success"
style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .4rem; --bs-btn-font-size: .65rem;">다운로드</a>
<%
}
%>
</td>
</tr>
HttpServletRequest 객체 내
InputStream
- request.getInputstream()을 실행해서 획득
- 브라우저가 웹서버로 보낸 바이너리 데이터 1byte씩 읽어오는 객체
Reader
- request.getReader()를 실행해서 획득
- 브라우저가 웹서버로 보낸 텍스트 데이터 1글자씩 읽어오는 객체
* 개발자가 직접 InputStream, Reader를 사용할 일은 없다.
HttpServletResponse 객체 내
OutputStream
- response.getOutputStream()을 실행해서 획득
- 웹서버가 브라우저로 바이너리 데이터 1byte씩 출력하는 객체
- 주로 파일 다운로드를 구현할 때 사용
Writer
- response.getWriter()을 실행해서 획득
- 웹서버가 브라우저로 텍스트 데이터 1글자씩 출력하는 객체
- 서블릿/JSP에서 HTML 콘텐츠를 응답으로 보낼 때 사용한다.
- 서블릿 : response.getWriter()을 실행하면 PrintWriter가 획득
- JSP : pageContext.getWriter()을 실행하면 JspWriter가 획득
* JSP에서는 out 내장객체 변수에 JspWriter가 대입되어 있고,
out.writer("<태그>내용</태그>"); 로 HTML 콘텐르를 응답으로 보낸다.
* 서블릿에서는 response.getWriter를 실행해서 PrintWriter를 획득하고,
PrintWriter의 println("<태그>내용</태그>")로 응답을 보낸다.
3. 게시판 - 첨부파일 다운로드
- FileDownloader.java 유틸
package com.sample.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import org.apache.tomcat.jakartaee.commons.io.IOUtils;
import com.sample.dao.BoardDao;
import com.sample.vo.Board;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
// 파일다운로드를 요청하는 URL : http://localhost/web-board/download?no=글번호
@WebServlet("/download")
public class FileDownloader extends HttpServlet {
private String directory = "c:\\app\\web-workspace\\temp";
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 쿼리스트링으로 전달된 글번호를 조회한다.
int boardNo = StringUtils.stringToInt(request.getParameter("no"));
// BoardDao 객체를 생성하고, 글번호에 해당하는 게시글 정보를 조회한다.
BoardDao boardDao = new BoardDao();
Board board = boardDao.getBoardByNo(boardNo);
// 첨부파일의 파일명을 조회한다.
String fileName = board.getFileName();
// 파일 다운로드 //
// 1. 응답 콘텐츠의 타입 지정
// application/octet-stream은 바이너리 데이터 타입의 기본값이다.
// 콘텐츠 타입을 알 수 없는 바이너리 데이터는 application/octet-stream을 콘텐츠 타입으로 지정한다.
response.setContentType("application/octet-stream");
// 2. 응답메시지의 헤더부에 다운로드되는 파일이름 정보를 추가한다.
// Content-Disposition은 응답 헤더이름이다.
// Content-Disposition을 이용해서 다운로드되는 파일이름 정보를 응답메시지의 응답헤더부에 포함시킨다.
// Content-Disposition의 응답 헤더값은 "attachment; filename=파일명"이다.
// "attachment;"는 첨부파일을 브라우저창에서 오픈없이 저장시킨다.
// "filename=파일명"은 다운로드되는 파일명을 지정한다.
// "filename=" + URLEncoder.encode(fileName, "utf-8")는 파일명에 한글이 포함되어 있을 경우,
// 한글이 깨지지 않도록 utf-8 방식으로 한글을 변환해서 응답으로 보낸다.
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "utf-8"));
// 3. 지정된 디렉토리와 파일명으로 저장된 파일(다운로드할 파일)을 읽어오는 FileInputStream 객체를 생성한다.
// FileInputStream 객체는 파일을 읽어오는 객체다.
// new File(디렉토리, 파일명)은 파일의 정보를 표현하는 객체를 생성한다.
// new FileInputStream(new File(디렉토리, 파일명)); 은 파일객체가 표현하는 파일을 읽어오는 객체를 생성한다.
InputStream in = new FileInputStream(new File(directory, fileName));
// 4. 응답객체가 브라우저로 바이너리 데이터를 출력할 때 사용하는 객체를 획득한다.
// OutputStream 객체는 브라우저로 바이너리 데이터 출력을 담당하는 객체다.
// response.getOutputStream()을 실행하면 OutputStream 객체를 반환한다.
// response.getOutPutStream()을 실행해서 획득한 OutputStream객체는 브라우저로 출력하는 객체다.
OutputStream out = response.getOutputStream();
// 5. IOUtils클래스의 copy(InputStream in, OutputStream out)은 in으로 읽어온 바이너리 데이터를 out으로 보낸다.
IOUtils.copy(in, out);
// 6. InputStream과 OutputStream은 컴퓨터의 읽기/쓰기 자원을 사용하기 때문에 읽기/쓰기 작업이 완료되면 사용했던 자원을 반납한다.
in.close();
out.close();
}
}
'수업내용 > Web' 카테고리의 다른 글
web-todo 관련 (0) | 2022.11.23 |
---|---|
[2022.11.21.월] IO 입출력 (0) | 2022.11.21 |
[2022.11.17.목] 장바구니 추가기능, 비밀번호 암호화 (0) | 2022.11.17 |
[2022.11.16.수] 국내도서, 장바구니 (0) | 2022.11.16 |
[2022.11.15.화] 로그인 기능 (0) | 2022.11.15 |