JavaScript
mime type : 특정 파일이 어떤 종류의 파일인지 지정 가능
➜ ex ) accept="image/*"
매개변수 e : event object 전달
e.target : 해당 이벤트가 일어나는 요소의 참조값
<div class="container">
<h1>선택한 이미지 데이터 다루기</h1>
<!-- accept 속성을 활용하면 이미지 파일만 선택할 수 있다 -->
<!-- 이미지 선택 시 change 이벤트 발생-->
<input type="file" id="myImage" accept="image/*" multiple>
<div id="dropZone">Click or Drag Drop</div>
<div id="wrapper"></div>
</div>
document.querySelector("#myImage").addEventListener("change", (e) => {
// e.target.files 에는 여러 개의 파일 객체가 들어 있을 수 있다
const files = Array.from(e.target.files);
// 배열의 filter() 함수를 이용해서 이미지 파일만 들어 있는 배열을 얻어낸다
const imageFiles = files.filter(item => item.type.startsWith("image/"));
// 반복문 돌면서 모든 이미지 파일을 로딩
for (let i = 0; i < imageFiles.length; i++) {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result;
const img = `<img src="${dataUrl}">`;
document.querySelector("#wrapper").insertAdjacentHTML("beforeend", img);
};
// i번 방에 들어 있는 이미지 파일을 읽어들인다
reader.readAsDataURL(imageFiles[i]);
}
});
#dropZone {
min-height: 300px;
border: 3px solid #cecece;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
#dropZone img {
width: 120px;
height: auto;
margin: 10px;
border-radius: 8px;
object-fit: cover;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
const dropZone = document.querySelector("#dropZone");
// drag 해서 위에 올라가 있을 때
dropZone.addEventListener("dragover", (e) => {
e.preventDefault(); // 기본 동작 막기
});
// drop 했을 때
dropZone.addEventListener("drop", (e) => {
e.preventDefault(); // 기본 동작 막기
// e.dataTransfer.files 는 drop 된 파일의 정보가 들어 있는 배열 (일반 배열이 아니다)
// 일반 배열처럼 사용하고 싶으면 Array.from() 함수에 넣어서 리턴되는 배열은 일반 배열
const files = Array.from(e.dataTransfer.files);
// 배열의 filter() 함수를 이용해서 이미지 파일만 들어 있는 배열을 얻어낸다
const imageFiles = files.filter(item => item.type.startsWith("image/"));
if (imageFiles.length == 0) {
alert("이미지 파일을 drop 하세요!");
return; // 함수를 여기서 종료하기
}
// drop zone 클리어
dropZone.innerHTML = "";
for (let i = 0; i < imageFiles.length; i++) {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result;
const img = `<img src="${dataUrl}">`;
dropZone.insertAdjacentHTML("beforeend", img);
};
reader.readAsDataURL(imageFiles[i]);
}
document.querySelector("#myImage").files = e.dataTransfer.files;
});
// drop zone 을 클릭했을 때 input type="file" 을 강제로 클릭해서 파일을 선택할 수 있게 한다
dropZone.addEventListener("click", () => {
document.querySelector("#myImage").click();
});
const reader = xxx;
reader.read(이미지 파일)
reader.onload = ()=>{
다 읽었을 때 호출되는 함수
}
또 다른 동작...
const reader = xxx;
// 다 읽을 때까지 여기서 대기
const result = reader.read(이미지 파일)
// result 에는 읽은 정보가 들어 있다
또다른 동작...
JAVA & JSP
<li><a href="${pageContext.request.contextPath}/test/file-form.jsp">파일 업로드 테스트</a></li>
SecurityFilter 화이트 리스트에 "/test" 추가
webapp 폴더 안에 test 폴더 생성 후 file-form.jsp 생성
<div class="container">
<h1>파일 업로드 테스트</h1>
<form action="${pageContext.request.contextPath}/test/fileup"
enctype="multipart/form-data" method="post">
<input type="text" name="caption" placeholder="설명 입력"/>
<br />
<input type="file" name="myFile"/>
<br />
<button type="submit">업로드</button>
</form>
</div>
⭐ - file 을 전송해야 하기 때문에 필요한 form 의 속성
enctype="multipart/form-data
input type="file"
- 이 요청을 처리할 서블릿이 필요 (@WebServlet("/test/fileup")
/test/fileup
- 전송되는 parameter의 이름
myFile , caption
FileupServlet 생성@WebServlet("/test/fileup")
@MultipartConfig(
fileSizeThreshold = 1024*1024*10, // 업로드 처리하기 위한 메모리 사이즈(10 Mega byte)
maxFileSize = 1024*1024*50, // 업로드되는 최대 파일 사이즈 (50 Mega byte)
maxRequestSize = 1024*1024*60) // 이 요청의 최대 사이즈(60 Mega byte), 파일 외의 다른 문자열도 전송되기 때문에
public class FileupServlet extends HttpServlet{
// 업로드된 파일 저장경로를 저장할 필드 선언
String fileLocation;
// 이 서블릿이 초기화되는 시점에 최초 1번 호출되는 메소드
@Override
public void init() throws ServletException {
// 무언가 초기화 작업을 여기서 하면 된다
ServletContext context = getServletContext();
// web.xml 파일에 "fileLocation" 이라는 이름으로 저장된 정보를 읽어와서 필드에 저장하기
fileLocation = context.getInitParameter("fileLocation");
// "C:/playground/upload" 에 저장될 예정
}
// post 방식 요청만 처리할 거라면 doPost() 메소드를 오버라이드 한다
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*
* "caption" 이라는 파라미터 명으로 문자열이 전송되고
* "myFile" 이라는 파라미터 명으로 파일이 전송된다 (주출 방식이 다름)
* */
String caption = req.getParameter("caption");
// 파일이 하나라면 getPart, 여러 개의 파일이라며 Parts (list 형태)
// <input type="file" name="myFile">
Part filePart = req.getPart("myFile");
if(filePart != null && filePart.getSize() > 0) {
// 원본 파일의 이름 얻어내기
String orgFileName = filePart.getSubmittedFileName();
// 파일명이 겹치지 않게 랜덤한 id 값 얻어내기
String uid = UUID.randomUUID().toString();
// 저장될 파일명을 구성한다
String saveFileName = uid+orgFileName;
// 저장할 파일의 경로 구성하기
String filePath = fileLocation+"/"+saveFileName;
/*
* 업로드된 파일은 임시 폴더에 임시 파일로 저장할 수 있다
* 해당 파일에서 byte 알갱이를 읽어들일 수 있는 InputStream 객체를 얻어내서
* */
InputStream is = filePart.getInputStream();
// 원하는 목적지에 copy 를 해야 한다
Files.copy(is, Paths.get(filePath));
}
}
}
⭐ 파일 다운로드 처리 시
업로드된 파일은 임시 폴더에 변경된 파일명으로 저장
원래 파일이름과 크기 등의 정보는 DB에 저장
다운로드 시 사용자에게 원본 정보로 표시
💡 요청받은 서블릿에서 최종 응답 출력을 JSP에 위임 가능
➜ 서블릿에서 복잡한 처리를 수행한 후 응답에 필요한 데이터를 JSP로 전달해 응답 처리를 맡긴다
// 요청을 전달한 객체 얻어내기
RequestDispatcher rd = req.getRequestDispatcher("/test/응답.jsp");
// 응답을 위임하기
rd.forward(req, resp);
<div class="container">
<p>응답합니다</p>
</div>
/*
* 원래는 DB 에 저장해야 하지만 테스트로 응답에 필요한 데이터를
* HttpServletRequest 객체의 .setAttribute(키, 값) 메소도를 이용해서 저장
* jsp 페이지가 사용할 수 있도록 한다
* jsp 페이지에서 해당 정보를 얻어날 때는
* HttpServletRequest 객체의 .getAttribute(키) 메소드를 이용해서 얻어낸다
* Object type 으로 리턴되기 때문에 원래 type 으로 casting 을 해야 한다
* */
req.setAttribute("orgFileName", orgFileName); // String type
req.setAttribute("saveFileName", saveFileName); // String type
req.setAttribute("fileSize", fileSize); // long type
<%
// request 객체에 특정 key 값을로 담긴 정보 추출하기
String orgFileName = (String)request.getAttribute("orgFileName");
String saveFileName = (String)request.getAttribute("saveFileName");
long fileSize = (long)request.getAttribute("fileSize");
%>
<div class="container">
<p>응답합니다</p>
<p>원본 파일명 : <%=orgFileName%></p>
<p>저장된 파일명 : <%=saveFileName%></p>
<p>파일의 크기 : <%=fileSize%></p>
<!-- el 을 이용하면 casting 필요없이 바로 추출할 수도 있다 -->
<p>원본 파일명 : ${requestScope.orgFileName}</p>
<p>저장된 파일명 : ${requestScope.saveFileName}</p>
<!-- requestScope. 은 생략 가능 -->
<p>파일의 크기 : ${fileSize}</p>
</div>
원본 파일명 , 저장된 파일명 , 파일의 크기
➜ 3가지를 알면 다운로드 가능한 로직 처리 가능
<a href="${pageContext.request.contextPath}/test/download?orgFileName=${orgFileName}&saveFileName=${saveFileName}&fileSize=${fileSize}">다운로드</a>
TestDownloadServlet 생성@WebServlet("/test/download")
public class TestDownloadServlet extends HttpServlet{
// 이미지 저장 경로
private String fileLocation;
@Override
public void init() throws ServletException {
ServletContext context = getServletContext();
fileLocation = context.getInitParameter("fileLocation");
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// get 방식 파라미터로 전달되는 다운로드해 줄 파일의 정보를 얻어온다 (원래는 pk 를 이용해서 DB 에서 읽어와야 한다)
String orgFileName = request.getParameter("orgFileName");
String saveFileName = request.getParameter("saveFileName");
// 여기서에서는 굳이 long type 이 필요 없지만 테스트로 바꿔봄
long fileSize = Long.parseLong(request.getParameter("fileSize"));
//응답 헤더 정보 설정
// application/octet-stream : 형식을 알 수 없는 파일 지칭
response.setHeader("Content-Type", "application/octet-stream; charset=UTF-8");
//다운로드 시켜줄 파일명 인코딩
String encodedName=URLEncoder.encode(orgFileName, "utf-8");
//파일명에 공백이있는 경우 처리 (공백을 +로 전환 후 다시 +를 공백으로 치환)
encodedName=encodedName.replaceAll("\\+"," ");
response.setHeader("Content-Disposition", "attachment;filename="+encodedName);
response.setHeader("Content-Transfer-Encoding", "binary");
//다운로드할 파일의 크기
response.setContentLengthLong(fileSize);
//다운로드 시켜줄 파일의 실제 경로
String path=fileLocation+File.separator+saveFileName;
FileInputStream fis=null;
BufferedOutputStream bos=null;
try {
//파일에서 byte 을 읽어들일 객체
fis=new FileInputStream(path);
//클라이언트에게 출력할수 있는 스트림 객체 얻어오기
bos=new BufferedOutputStream(response.getOutputStream());
//한번에 최대 1M byte 씩 읽어올수 있는 버퍼
byte[] buffer=new byte[1024*1024];
int readedByte=0;
//반복문 돌면서 출력해주기
while(true) {
//byte[] 객체를 이용해서 파일에서 byte 알갱이 읽어오기
readedByte = fis.read(buffer);
if(readedByte == -1)break; //더이상 읽을 데이터가 없다면 반복문 빠져 나오기
//읽은 만큼 출력하기
bos.write(buffer, 0, readedByte);
bos.flush(); //출력
}
}catch(Exception e) {
e.printStackTrace();
}finally {
if(fis!=null)fis.close();
if(bos!=null)bos.close();
}
}
}
⚠️ DB에서 파일 정보를 사용할 경우, FileDto에 파일명과 크기 정보를 포함