웹 서버에 파일을 업로드하기 위해서
HTML <form>
태그 내 <input type='file'>
태그를 사용하게 된다.
일반적으로 다른 input 태그를 추가하여, 파일 외 여러 종류의 데이터도 같이 전송하게 된다.
이를 처리하기 위하여 multipart라는 형식을 이용한다.
form 태그 속성으로 enctype="multipart/form-data"
을 명시하면,
HTTP Request Header에서 Context-type: multipart/form-data
를 확인할 수 있고,
이는 HTTP Request Body에 여러 종류의 데이터를 구분하여 넣었음을 의미한다.
업로드한 파일을 Http 요청으로 받은 후 복사하여 웹 서버에 저장해놓고,
다운로드 시에도 파일을 복사하여 Http 응답으로 보내게 된다.
자료실 게시판에 파일 업로드와 다운로드 기능을 구현해보자!
: <dependecies>
태그 내 추가
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
upload 설정
CommonsMultipartResolver
download 설정
BeanNameViewResolver
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- upload -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 최대 파일 크기: 1MB -->
<property name="maxUploadSize" value="104857600"/>
<!-- 최대 메모리 크기: 1kB -->
<property name="maxInMemorySize" value="102400"/>
<!-- 요청 파싱할 때의 인코딩 설정 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 디렉토리 경로: webapp/upload -->
<property name="uploadTempDir" value="upload"/>
</bean>
<!-- download -->
<bean id="downloadView" class="com.mypack.sample.util.DownloadView"></bean>
<bean id="downloadViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order">
<value>0</value>
</property>
</bean>
</beans>
: 위에서 설정한 file-context.xml 파일을 등록
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/servlet-context.xml
/WEB-INF/spring/file-context.xml
</param-value>
</init-param>
enctype
: form data가 서버로 제출될 때, 해당 데이터가 인코딩되는 방법을 명시method
가 "post"인 경우에만 사용 가능!<form action="pdsupload.do" method="post" enctype="multipart/form-data">
<input type="file" name="fileload" required="required">
<input type="hidden" name="id" value="<%= login.getId() %>">
<input type="text" name="title" required="required">
<textarea name="content" rows="10" required="required"></textarea>
<input type="submit" value="작성">
</form>
: upload 디렉토리 내 파일이름의 충돌을 막기 위해
시스템 시간으로 새로운 파일명 생성
public class PdsUtility {
// 새 파일명 생성 newfile.txt -> 3237534.txt
public static String getNewFileName(String filename) {
String newfilename = ""; // 새 파일이름
String extension = ""; // 파일 확장자명
if(filename.indexOf('.') >= 0) { // 확장자명 있음
extension = filename.substring(filename.indexOf('.'));
newfilename = new Date().getTime() + extension;
} else { // 확장자명 없음(-1)
newfilename = new Date().getTime() + ".back";
}
return newfilename;
}
}
create table pds(
seq int auto_increment primary key,
id varchar(50) not null,
title varchar(200) not null,
content varchar(4000) not null,
filename varchar(50) not null,
newfilename varchar(50) not null,
readcount decimal(8) not null,
downcount decimal(8) not null,
regdate timestamp not null
);
alter table pds
add foreign key(id) references member(id);
: MyBatis 사용
<insert id="uploadPds" parameterType="com.mypack.sample.dto.PdsDto">
insert into pds(id, title, content, filename, newfilename, readcount, downcount, regdate)
values(#{id}, #{title}, #{content}, #{filename}, #{newfilename}, 0, 0, now())
</insert>
: DB 테이블 참고하여 멤버, 생성자, getter/setter, toString 생성
: mapper.xml 참고하여 인터페이스와 클래스에 uploadPds() 메소드 생성
@PostMapping(value = "pdsupload.do")
public String pdsupload(PdsDto dto,
@RequestParam(value = "fileload", required = false)
MultipartFile fileload,
HttpServletRequest req) {
// filename 취득
String filename = fileload.getOriginalFilename();
dto.setFilename(filename);
// upload 경로 설정
// server에 파일 저장 -> 재시작 시 refresh되면서 파일 삭제됨
String fupload = req.getServletContext().getRealPath("/upload");
// 새 파일명 취득
String newfilename = PdsUtility.getNewFileName(filename);
dto.setNewfilename(newfilename);
// 서버에 실제 파일 생성 및 기입 (= 업로드)
File file = new File(fupload + "/" + newfilename);
try {
FileUtils.writeByteArrayToFile(file, fileload.getBytes());
// db에 저장
service.uploadPds(dto);
} catch (IOException e) {
e.printStackTrace();
}
return "redirect:/pdslist.do"; // 파일 업로드 후 목록으로 이동
}
@PostMapping(value = "filedownload.do")
public String filedownload(int seq, String newfilename, String filename,
HttpServletRequest req, Model model) {
// 경로 설정 (server)
String fupload = req.getServletContext().getRealPath("/upload");
// 다운로드 받을 파일
File downloadFile = new File(fupload + "/" + newfilename);
model.addAttribute("downloadFile", downloadFile);
model.addAttribute("filename", filename);
model.addAttribute("seq", seq);
return "downloadView"; // file-context.xml 설정대로 이동
}
public class DownloadView extends AbstractView {
@Autowired
PdsService service;
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 컨트롤러에서 보낸 값 받기
File downloadFile = (File)model.get("downloadFile");
String filename = (String)model.get("filename");
int seq = (Integer)model.get("seq");
// 파일명으로 한글 사용 시 설정
filename = URLEncoder.encode(filename, "utf-8");
// Http Response Header 설정
response.setContentType(this.getContentType());
response.setContentLength((int)downloadFile.length());
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\";");
response.setHeader("Content-Transfer-Encoding", "binary;"); // 이진수로 저장
response.setHeader("Content-Length", "" + downloadFile.length());
response.setHeader("Pragma", "no-cache;"); // 캐시로 저장 안함
response.setHeader("Expires", "-1;"); // 기한 없음
OutputStream os = response.getOutputStream();
FileInputStream fis = new FileInputStream(downloadFile);
// 실제 데이터 기입 (os에 복사)
FileCopyUtils.copy(fis, os);
// downcount 증가
service.downcountPds(seq);
if(fis != null) {
fis.close();
}
}
}