Languages/java

[JAVA] fileUpload03

뱅타 2021. 4. 15. 23:04

FileUpload 중에 part라는 API를 고쳐서 사용해 보기.( AdapterPatter 적용)

우선 파일 업로드 시 주의사항

  1. file은 2진데이터(byte)타입이다.(get사용불가)

    2진데이터 → post

  1. file과 text 등 다른 데이터들을 받으려면 enctype을 설정해야한다.(multipart/form-data)

    다른타입의 데이터를 입력 → multipart

  1. part API를 사용하기 위해 servlet의 버전을 맞춰 주어야 한다.

    part 사용하기 (버전 맞춰주기) → web.xml( multipart-config)

web.xml에 등록되어 있는 Front Servlet에 multipart-config 설정을 집어넣어준다.

이렇게 파트를 불러와서 사용가능.

파일 업로드 시 순서

  1. 어디에다가 저장

    웹 리소스, 클래스패스, 파일시스템으로 저장 가능.

  1. 메타데이터를 어떻게 뽑을것인가
  1. db에다 저장하기

Adapter역할을 할 녀석은 파라미터로 반드시 Adaptee를 받을 수 있어야 한다.

HttpServletRequsetWrapper를 상속받으면 기본적으로 파라미터를 받는 생성자를 만들라고 한다.

  • MultipartHttpServletRequest 파일 코드
    package kr.or.ddit.mvc.filter.wrapper;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.Part;
    
    // 원본 request에 기능들을 추가하고 있음.
    public class MultipartHttpServletRequest extends HttpServletRequestWrapper{
    //	private Map<String, part>
    	private Map<String, List<MultipartFile>> fileMap;
    	
    	// 모든 데이터는 request가 가지고 있다.
    	public MultipartHttpServletRequest(HttpServletRequest request) throws IOException, ServletException {
    		super(request);
    		fileMap = new LinkedHashMap<>();
    		parseRequest(request);
    	}
    
    	private void parseRequest(HttpServletRequest request) throws IOException, ServletException {
    		Collection<Part> parts = request.getParts();
    		for(Part tmp : parts) {
    			if(tmp.getContentType()== null) continue;
    			String partName = tmp.getName();
    			List<MultipartFile> list = fileMap.get(partName);
    			if(list == null) {
    				list = new ArrayList<>();
    				fileMap.put(partName, list);
    			}
    			list.add(new MultipartFile(tmp));
    		}
    	}
    	public Map<String, List<MultipartFile>> getFileMap() {
    		return fileMap;
    	}
    	private MultipartFile getFile(String name) {
    		List<MultipartFile> files = fileMap.get(name);
    		MultipartFile file = null;
    		if(files!=null && files.size() > 0)
    			file = files.get(0);
    		return file;
    	}
    	
    	// 하나의 객체를 multipartFile로 
    	public List<MultipartFile> getFiles(String name) {
    		return fileMap.get(name);
    	}
    }

이렇게 감싸서 보내면 변경된 request(req → wrapper)가 보내지게 된다.

필터가 해야할 일.

  • 파일이 포함된 multipart 요청인지 식별.
  • multipart 요청이라면, 원본 요청을 wrapper로 변경.
  • wrapper를 이용해 Part 데이터를 쉽게 핸들링할 수 있는 구조 설정.

파트가 가지고 있지 않은 메서드를 임의로 추가해서 새로운 어뎁터 만들어 보기.

MultipartFile이란 클래스 생성.

파일 업로드 시 주의할 점.

  1. 확장자 제거
  1. 업로드하는 사람이 최종적으로 저장되는 파일의 이름을 예측하지 못하게 하기위해 랜덤한 파일명 부여.

** 참고 kisa에 있는 보안ppt를 한번읽어보면 좋다.

https://www.kisa.or.kr/public/laws/laws3_View.jsp?cPage=6&mode=view&p_No=259&b_No=259&d_No=88&ST=T&SV=

실제 파일 저장 시 확장자와 이름을 바꿔서 저장한다!

클라이언트가 서버에 올라갈 파일의 확장자와 이름을 알게 된다면 다른 파일을 변경시킨다거나 훼손시킬 수도 있기 때문!(이라고 하는데 상상이 잘 안간다)

이름 뿐만 아니라 이 녀석의 전체 경로를 달라는 것.

파일을 저장할 수 있는 함수 생성!

이러한 상황을 대비해서 리스트로

  • name이 같은 input태그들이 존재할 때 집합객체로 받아야 한다.

adaptee가 가지고 있지 않은 것들을 adapter가 완성시킴.

  • adapter
    package kr.or.ddit.mvc.filter.wrapper;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.Part;
    
    // 원본 request에 기능들을 추가하고 있음.
    public class MultipartHttpServletRequest extends HttpServletRequestWrapper{
    //	private Map<String, part>
    	private Map<String, List<MultipartFile>> fileMap;
    	
    	// 모든 데이터는 request가 가지고 있다.
    	public MultipartHttpServletRequest(HttpServletRequest request) throws IOException, ServletException {
    		super(request);
    		fileMap = new LinkedHashMap<>();
    		parseRequest(request);
    	}
    
    	private void parseRequest(HttpServletRequest request) throws IOException, ServletException {
    		Collection<Part> parts = request.getParts();
    		for(Part tmp : parts) {
    			if(tmp.getContentType()== null) continue;
    			String partName = tmp.getName();
    			List<MultipartFile> list = fileMap.get(partName);
    			if(list == null) {
    				list = new ArrayList<>();
    				fileMap.put(partName, list);
    			}
    			list.add(new MultipartFile(tmp));
    		}
    	}
    	public Map<String, List<MultipartFile>> getFileMap() {
    		return fileMap;
    	}
    	private MultipartFile getFile(String name) {
    		List<MultipartFile> files = fileMap.get(name);
    		MultipartFile file = null;
    		if(files!=null && files.size() > 0)
    			file = files.get(0);
    		return file;
    	}
    	
    	// 하나의 객체를 multipartFile로 
    	private List<MultipartFile> getFiles(String name) {
    		return fileMap.get(name);
    	}
    }
  • adaptee
    package kr.or.ddit.mvc.filter.wrapper;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.UUID;
    
    import javax.servlet.http.Part;
    
    import org.apache.commons.lang3.StringUtils;
    
    public class MultipartFile {
    	private Part adaptee;
    	private String originalFilename;
    	// 해킹을 방지하기 위해 랜덤한 이름을 설정하자.
    	private String uniqueSaveName;
    	private boolean empty;
    	
    	public MultipartFile(Part adaptee) {
    		super();
    		this.adaptee = adaptee;
    		String disposition = adaptee.getHeader("Content-Disposition");
    		int index = disposition.indexOf("\"", disposition.indexOf("filename="));
    		if(index!=-1) {
    			originalFilename = disposition.substring(index).replace("\"", "");
    		}
    		empty = StringUtils.isBlank(originalFilename);
    		this.uniqueSaveName = UUID.randomUUID().toString();
    	}
    	private String getOriginalFilename(Part part) {
    			// 	Content-Disposition: form-data; name="uploadFile1"; filename="test.jpg"
    		String disposition = part.getHeader("Content-Disposition");
    		int index = disposition.indexOf("\"", disposition.indexOf("filename="));
    		String originalFilename = null;
    		if(index!=-1) {
    			originalFilename = disposition.substring(index).replace("\"", "");
    		}
    		return originalFilename;
    	}
    	
    	public String getName() {
    		return adaptee.getName();
    	}
    	
    	public String getContentType() {
    		return adaptee.getContentType();
    	}
    	
    	public long getFileSize() {
    		return adaptee.getSize();
    	}
    
    	// 기존의 part가 가지고 있지 않은 메서드 추가하기
    	public String getOriginalFilename() {
    		return originalFilename;
    	}
    	public String getUniqueSaveName() {
    		return uniqueSaveName;
    	}
    	
    	public InputStream getInputStream() throws IOException {
    		return adaptee.getInputStream();
    	}
    	
    	public void saveTo(File saveFolder) throws IOException {
    		File saveFile = new File(saveFolder, uniqueSaveName);
    		adaptee.write(saveFile.getAbsolutePath());
    	}
    	
    	public boolean isEmpty() {
    		return empty;
    	}
    }

part만을 가지고 사용한 파일을 어뎁터를 이용해서 만들어보기

part(adaptee) → multipart(adapter)

어노테이션 변경시키기.

컨트롤러의 책임을 줄이기 위해서.

name이 같은 여러객체를 받기 위해 배열로!

728x90
반응형