Jsp & Servlet : Pojo (이미지업로드)

지환·2023년 12월 11일
0

Jsp & Servlet

목록 보기
18/21
post-thumbnail

학습목표

이미지 업로드, 이미지겟 부분 보기 디비에 올리는게 아니라, 톰캣서버에 올려놓고

디비에는 url만 저장 select 날릴 떄 쿼리문을 가져와서 출력하는 방식으로 진행할 예정

출력을 하려면 PrintWriter out = res.getWriter로 받아온다.


Json 처리

이번 테스트 케이스는 서블릿에서 PrintWriter라는 IO를 만들어서 화면에 뿌리고 싶다. (단, List를 Json으로 변환 후 화면에 뿌리고 싶다.)

[초기]

	else if("jsonNoticeList2".equals(upmu[1])) {//select
			logger.info("jsonNoticeList");
			List<Map<String ,Object>> nList = null;
			hmb.bind(pMap);
			nList = nLogic.noticeList(pMap);
			req.setAttribute("nList", nList);
			//실제 플젝에서는 이렇게 하지 않는다(서블릿단에서 직접 내보낸다) 1-2버전에서는 개선해 본다
			isRedirect = false;//false이면 forward처리됨
		}	

현재 문제는 404 에러가 발생한다. 어떻게 처리할까?

이렇게 되면 /notice/를 지워야된다. 어떤 방식으로 지울 수 있을까?

[수정 후]

		else if("jsonNoticeList2".equals(upmu[1])) {//select
			logger.info("jsonNoticeList");
			List<Map<String ,Object>> nList = null;
			hmb.bind(pMap);
			nList = nLogic.noticeList(pMap);
			Gson g = new Gson();
			String temp = g.toJson(nList);
			res.setCharacterEncoding("utf-8");
			res.setContentType("application/json");
			PrintWriter out = res.getWriter();
			//out.print(nList);//List-> [], Map -> {deptno=10, dname=영업부}
			out.print(temp);//[{"deptno":10, "dname":"영업무"}]
			//응답결과가 페이지가 아닌 경우가 존재한다 
			//예를들면) json이거나 quill사용시 이미지 이름 일때도 포함된다.
			//path의 값을 null처리하거나 문자열이 나가는 경우를 고려해야 한다
			int end = path.toString().length();// -> notice/
			path.delete(0, end);
			path.append(temp);//url이 전달되는게 아니라 json형식 즉 문자열이 전달됨
			
		}
		

[FrontMVC 수정전]

//이 지점은 java와 오라클 서버를 경유한 뒤 시점이다.
		if(af !=null) {
			if(af.isRedirect()) {
				res.sendRedirect(af.getPath());
			}
			else{
				RequestDispatcher view = req.getRequestDispatcher(af.getPath());
				view.forward(req, res);
			}
		}

근데 왜 저렇게 작성한 것일까? 오체분시해보자.

다음은 path의 값을 공백처리 하기 위한 코드이다.

			int end = path.toString().length();
			path.delete(0,end);
			path.append(temp);
  • notice/jsonNoticeList2.gd로 경로를 잡아줬다. 그렇게 되면

  • FrontMVC -> NoticeController -> HashMapBinder -> Logic 순서로 작동하는데

  • jsp로 뿌려주는게 아니라 서블릿파일 내 PrintWriter로 out.print로 뿌려준다.

  • path.append해서 path경로를 따로 잡아줄 필요가 없다.

  • 그렇다면 /notice/를 제거해줘야된다.

  • 이것을 제거하는 이유는 404에러가 발생하기 떄문이다.

    • 실질적으로 url로 보여지는 페이지는 무엇일까?

    • localhost:8000이다.

    • path를 공백으로 처리해야된다.

int end = path.toString().length();
로 해서 path의 길이를 구한다. 이 떄는 /notice/이기 떄문에 이 길이를 구하면 

그리고 그 경로를 삭제하기 위해서 path.delete 메소드 를 사용하여 경로을 삭제한다. path.delete(0,end)로 경로를 잡아주면 /notice/ 전체를 삭제한다.

path.append를 하게 되면 아무것도 저장되어 있지 않은 path에 temp(json)를 저장한다.

path.append : [{"N_TITLE":"겨울방학이벤트\n","N_WRITER":"관리자","N_CONTENT":"2년 33만원 겨울방학이벤트\n","N_NO":2},{"N_TITLE":"휴관일","N_WRITER":"관리자","N_CONTENT":"이번주 일요일은 휴관일입니다.","N_NO":1},{"N_TITLE":"겨울방학이벤트\n","N_WRITER":"관리자","N_CONTENT":"1년 33만원 겨울방학이벤트\n","N_NO":0}]

이후엔 path에 이렇게 저장되어있다. url이 전달되는게 아니라 json형식이 전달된 것이다.

즉, path에 Json이 전달될 떄를 고려 했다.

데이터 형식 관점으로 바라보면, nList가 List이기 때문에 [{}] 로 출력된다.

List<Map<String, Object>> 이다.

그래서 결과가

정리하면

전체적인 로직으로 파악해보자.

  1. FrontMVC(Url: http://localhost:8000/notice/jsonNoticeList2.gd - Notice)

  2. NoticeController(jsonNoticeList2)

  3. HashMapBinder(공통코드)

  4. Logic(noticeList) -> 결과값 받아옴(nList)

  5. NoticeController에서 nList 받은 값을 가지고

    • json으로 변환(Gson g = new Gson())

    • PrintWriter out = res(내보냄).getWriter : 화면에 찍으려고 사용

    • String temp = g.toJson(nList) : 이 부분이 nList를 Json으로 변환하는 스팟

    • 화면 url : http://localhost:8000/notice/jsonNoticeList2.gd 유지한 채로 path 수정 들어감(why? - url에 Json이 들어갈 때 + Json이 들어갈 떄를 학습하기 위해서 이 떄는 FrontMVC NULL 및 공백처리를 하지 않았음)

      • 화면 출력을 ReactJS와 같이 다른 언어 다른 라이브러리를 사용하여 처리해야 할땐 Back-End에서 해야될 일은 JSON포맷으로 응답이 나가도록 처리해주면 된다.
    • 그렇게 하려면 기존의 url에서 /notice/값을 지워야한다. (처음 디폴트로 /notice/가 설정되어있다. 이 부분을 지우지 않았다고 가정해보자.

      • 이 /notice/를 path가 가지고 있고, isRedirect : false(forward) 처리하게 되면 해당 경로는 아무것도 가지고 있지 않은 page를 출력한다. 이 부분이 .gd로 끝나는가?

      • af = path : /notice/ + isRedirect : false(forward)

      • 서버 처리는 /notice/로 나오게된다.

      • /notice/를 지워주면 path문자열에서 delete 하게 되면 처리되지 않을까?

      • 그렇게 처리하기 위해서

        		  int end = path.toString().length();를 하고
            path.delete(0, end);를 진행했다.
        
    • 근데 이렇게 하게 되면 Json을 url에 포함하지 못한다.

    • url에 Json에 포함하는 이유는 다른 리액트같은 이종간 연결을 할 떄, 우리는 JSON 포맷으로 응답이 나가도록 처리해야된다.

    • 그래서 path.append(temp)를 하는 것이다.


      이런식으로 잘 나오는 것을 확인할 수 있다.

      즉, url : http://localhost:8000/notice/jsonNoticeList2.gd 이렇게 입력하면

      응답값으로 Json을 보내는 것이다.

      [{"N_TITLE":"겨울방학이벤트\n","N_WRITER":"관리자","N_CONTENT":"2년 33만원 겨울방학이벤트\n","N_NO":2},{"N_TITLE":"휴관일","N_WRITER":"관리자","N_CONTENT":"이번주 일요일은 휴관일입니다.","N_NO":1},{"N_TITLE":"겨울방학이벤트\n","N_WRITER":"관리자","N_CONTENT":"1년 33만원 겨울방학이벤트\n","N_NO":0}]

리액트랑 붙여보자.

export const noticeListDB = (notice) => {
  //?gubun=n_title&keyword=휴관
  return new Promise((resolve, reject) => {
    try {
      console.log(notice);
      const response = axios({
        method: "get",
        url: "http://localhost:8000/notice/jsonNoticeList2.gd",
        params: notice,
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

리액트에서 이 부분을 비동기적으로 처리할 떄, url응답 페이지로 Json을 보낸다.

Json으로 마임타입으로 결정한 뒤 서블릿에서 페이지를 보여준다.

리액트에서

공지사항을 누르는 순간 디비에서 서버에 url를 요청 한다.
url: "http://localhost:8000/notice/jsonNoticeList2.gd",
이렇게 되면 응답은 이렇게 나온다.

  1. notice/jsonNoticeList2를 서블릿에서 가로채고(.gd) -FrontMVC

  2. NoticeController에서 jsonNoticeLIst2 조건에 걸린다.

    • 그러면 nLogic으로 부터 nList를 리턴 받은 다음에
  3. NoticeController에서 마임타입을 res.setContentType("application/json");으로 지정하면 응답으로 나가는 마임타입이 json이다.

  4. path : 공백(end로 다 지웠다.) 나중에 path.append(temp); 하여 Json을 추가한다. + isRedirct는 : false(forward)

    • 생각해보면 NoticeController path가 "null일 경우"와 "빈공백일 경우"에 대한 처리가 없다. 이 부분을 해결하기 위해서 if문으로 분기했다.
    • null처리가 없다면 nullPointException이 발생한다.
if(path !=null) {//응답페이지가 존재하는 경우만 처리할것
			af.setPath(path.toString());//이 대로 두면 NullPointerException대상임			
		}else {
			af.setPath(null);//이 대로 두면 NullPointerException대상임			
		}
		af.setRedirect(isRedirect);//true-> ActionForward - isRedirect - false->true
		return af;

af.setPath(path.toString())를 타게되고,

af.setPath : json데이터가 들어간다.

그리고 FrontMVC에 와서 경로를 나눈다.

		if(af !=null) {
			if(af.isRedirect()) {//true라는 건 sendRedirect인 경우임
			
				if(af.getPath() == null) {
					return;//해당메소드 탈출
				}else {
					
				
					res.sendRedirect(af.getPath());// -> notice/noticeList.jsp					
				}
			}
			else{//forward인 경우임 - url안바뀜, 화면은 바뀜, 유지됨. a페이지에서 쥐고 있는 정보를 b페이지에서도 사용가능함
				
				if(af.getPath().contains("/")) {
					RequestDispatcher view = req.getRequestDispatcher(af.getPath());
					view.forward(req, res);					
				}
				else if(af.getPath() == null) {//파일 업로드 처리시 ActionForward를 통해서 값을 리턴 받을때 문제가 발생됨. 이부분에 대한 해결  프로세스 추가하였다.
					logger.info("path가 null일때");
				}
				
				else {
					logger.info("슬래쉬가 미포함인 경우 ===> " + af.getPath());
					res.setCharacterEncoding("utf-8");
					res.setContentType("text/plain;utf-8");
					PrintWriter out = res.getWriter();
					out.print(af.getPath());
					return;					
				}
			}
		}
  • af가 null이 아니기 떄문에 조건에 걸리게되고,

    • isRedirect가 false이기 때문에 else(forward)를 실행한다.
    • 이 떄 / 슬래시가 있는가? No
    • null도 아니다.
    • else에 걸리게되어

				else {
					logger.info("슬래쉬가 미포함인 경우 ===> " + af.getPath());
					res.setCharacterEncoding("utf-8");
					res.setContentType("text/plain;utf-8");
					PrintWriter out = res.getWriter();
					out.print(af.getPath());
					return;					
				}

이 때, sendRedirect, Forward를 진행하는가? --> 페이지 처리 ❌

res.setCharacterEncoding("utf-8");
res.setContentType("text/plain;utf-8");

out.print(af.getPath())를 담게되면 안에 nList를 Json으로 변환한 것을 응답페이지로 내보낸다.

만약에 FrontMVC af 분기 else 부분을 주석처리 하게 된다면, out.print가 어떻게 출력되는지 확인해보려고 하는 사례

FrontMVC에서 (out.print & PrintWriter 사용 안 했을 때

else 주석 부분을 해제하면,(FrontMVC else 주석처리)

이렇게 나온다.

이렇게 화면에 page가 변경되어 나타내는 주된 이유는 PrintWriter로 인해 출력이 저렇게 바뀌어 출력된다.

만약에 마임타입을 res를 주석처리하게 되면 어떻게 처리될까?

				else {
					logger.info("슬래쉬가 미포함인 경우 ===> " + af.getPath());
					//res.setCharacterEncoding("utf-8");
					//res.setContentType("text/plain;utf-8");
					PrintWriter out = res.getWriter();
					out.print(af.getPath());
					return;					
				}

한글이 다 꺠져서 나온다.

근데 path에 저장된건 json인데(NoticeController) 왜 마임타입을 application/json이 아니라 text/plain;utf-8를(FrontMVC) 사용했을까?

NoticeController에서 else if("jsonNoticeList2".equals(upmu[1])) if문 분기 되었을 떄,

  • res.setContentType("application/json");
  • res.setCharacterEncoding("utf-8");
    • 이렇게 사용하는 이유는 NoticeController 에서 PrintWriter하기 위함.
    • 이 떄, PrintWriter out 을 하기 위해서 마임타입을 설정했다.

정리하면

  • res.setContentType("application/json")으로 설정된 응답 헤더는
    클라이언트로 마임타입을 전송해야되는데, 이 부분을 출력하기 위한 코드는 PrintWriter이다.
  • 그 후에 FrontMVC는 res.setContentType("text/plain;utf-8") 이 부분을 출력하기 위한 코드는 PrintWriter이다.

여기서는 setContentType("application/json")으로 처리하지 않아도 괜찮을까? yes

-> Path에 저장된것을 봤을 떄, path.append(temp)를 했다. json이 path에 저장되었다.

-> 이것을 out.print(af.getPath());이렇게 출력한다.

path 관점에서 보자. IsRedirect로 전체를 나누는데, SendRedirect에선 ('/'), 문자열, 공백 처리는 왜 안해주는가?

  • null처리는 되어있다. 왜 그렇게 했을까?

  • SendRedirect는 (req,res)에 대한 연결이 끊어진다. 단순히 경로를 이동한다. 그렇다면 path가 null인 경우만 제외하고 나머지는 통일이 가능하다.(값을 뿌리진 않으니깐)

Forward(else) 처리는 왜 ('/'), null일떄, 문자열&공백 일 떄 3가지를 나누어서 작성했는데 왜 이렇게 했을까?

  • forward를 통해서 값을 뿌려야 되기 떄문이다. forward를 사용한다면 요청객체와 응답객체가 끊어지지않고 관리된다.
    이 말은 하나의 원본으로 전체를 경유한다는 얘기인데, 만약에 null일떄, 문자열&공백 일 때를 나누지 않는다면

    • null일 때는 이미지업로드 및 첨부파일을 업로드 할 떄 사용한다.

      • path: null로 반환됐을 떄 if(af.getPath() == null)로처리하지 않는다면. nullPointException이 일어난다.
    • 문자열 & 공백 :

      • path : " " || asdasdasd< 문자열
        • 페이지 이동 없이 화면에 결과값을 뿌리고 싶다(PrintWriter)
          • 중요한 부분은 마임타입이다.(클라이언트에 뿌려준다)
          • 경로처리를 하지 않아도 상관없다.
    • 페이지처리를 원함 ('/') (forward)

      • 페이지처리를 진행
      • 왜 ('/')를 했을까?
        • 슬래쉬가 포함되었다는건 응답으로 나가는 마임타입이 html이다. path.append(notice/noticeList.jsp)

이미지처리

url입력 시 Json이 아니라, 이미지를 넣으면 된다.

이미지 업로드(UPLOAD) , 이미지(GET) 부분 디비에 올리는게 아니라, 톰캣서버에 올려놓고 디비에는 url만 저장한다.

select 날릴 떄 쿼리문을 가져와서 출력하는 방식으로 진행할 예정이다.

Jsp 전체코드


<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<%@include file="/common/quill_common.jsp" %>
</head>
<body>
	<!-- Create the editor container -->
	<div id="editor22"></div>
	<!-- Initialize Quill editor 
	브라우저가 DOM Tree그린다
	CSS 포함하는 DOM Tree 그린다
	출력됨
	-->
	<script>
	const toolbarOptions = [
		  ['bold', 'italic', 'underline'],        // toggled buttons
		  ['blockquote', 'code-block'],
		  [{ 'list': 'ordered'}, { 'list': 'bullet' }],
		  [{ 'indent': '-1'}, { 'indent': '+1' }],          // outdent/indent
		  [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
		  [{ 'color': [] }, { 'background': [] }],          // dropdown with defaults from theme
		  [{ 'font': [] }],
		  [{ 'align': [] }],
		  ['link', 'image']
		];				
	  const quill = new Quill('#editor22', {
		  modules: { toolbar: toolbarOptions },				    
		  theme: 'snow',
		  placeholder: '글 작성하기',
	  });
	  const  selectLocalImage = (event) =>  {
		  console.log('selectLocalImage');
		  //e.preventDefault();이벤트 버블링 방어 코드 작성 - submit이슈, <button type=submit|button>
		  //quill에디터에서 이미지를 클릭했을 때 실제 화면에서는 <input type=file>생성해 주자
		  //이미지를 선택하면 선택하자마자 백엔드에 요청을 post방식으로 넘긴다 - 8000번 서버의 pds폴더에 선택한 이미지를 업로드 처리함
		  //파일을 (바이너리코드) 전송할땐 무조건 post방식으로 해야 함
		    const fileInput = document.createElement('input');//<input> - DOM API의 createElement를 사용하여 태그 생성하는 코드
		    fileInput.setAttribute('type', 'file');//<input type=file>
		    console.log("input.type " + fileInput.type);
		    //이미지 파일만 선택가능하도록 제한
		    fileInput.setAttribute("accept", "image/*");//* -> image/png , image/gif, image/jpg
		    fileInput.setAttribute("name", "image");//req.getParameter("image"); -> <input name="image"/>
		    fileInput.click();

		    fileInput.addEventListener("change", function () {  // change 이벤트로 input 값이 바뀌면 실행
		        const formData = new FormData();
		        const file = fileInput.files[0];
		        formData.append('image', file);

		        $.ajax({//<form method=post enctype=multipart/form-data/> 이럴경우 req.getParameter사용이 불가함 -> cos.jar
		            type: 'post',
		            enctype: 'multipart/form-data',
		            url: '/notice/imageUpload.gd',
		            data: formData,
		            processData: false,
		            contentType: false,
		            success: function (response) {
						console.log('avatar.png'+response);//avartar.png
						const url = "http://localhost:8000/notice/imageGet.gd?imageName="+response;//
						const range = quill.getSelection().index;
						quill.setSelection(range, 1);
						quill.clipboard.dangerouslyPasteHTML(range,
								'<img src='+url+' style="width:100%;height: auto;" alt="image" />');
		            },
		            error: function (err) {
		                console.log(err);
		            }
		        });//////////////////// end of ajax

		    });////////////////////// end of onchange 이벤트 핸들러
		}////////////////////////// end of selectLocalImage	  
		//html 가져오기
		const html = quill.root.innerHTML;
		console.log(html);
		quill.on('text-change', (delta, oldDelta, source) => {
			  console.log('글자가 입력될때 마다 호출');
			  console.log(quill.root.innerHTML);
			  //console.log(source);//user
			  //console.log(delta);
			  //console.log(oldDelta);
		}); ////////////// end of onchage - 텍스트 내용이 변경되었을 때 발동
		quill.getModule('toolbar').addHandler('image',  () => {
			console.log('image가 변경되었을때');
			selectLocalImage();
		})

	</script>
</body>
</html>

이미지 처리 참고 사이트

Controller

else if("imageUpload".equals(upmu[1])) {//delete
			//quill editor에서 이미지를 선택하면 해당 요청을 호출함 - 비동기처리
			//post이면서 enctype 바이너리인 경우 전송이 안됨
			MultipartRequest multi  = null;
			String realFolder = "C:\\Program Files\\workspace_jsp\\nae2Gym\\src\\main\\webapp\\pds";
			String encType = "utf-8";
			int maxSize = 5*1024*1024;
			try {
				multi = new MultipartRequest(req, realFolder, maxSize,  encType, new DefaultFileRenamePolicy());
			} catch (Exception e) {
				logger.info(e.toString());
			}
			Map<String, Object> rmap = nLogic.imageUpload(multi, realFolder);
			String temp = null;
			if(rmap !=null) {
				temp = rmap.get("bs_file").toString();
			}
			int end = path.toString().length();// -> notice/
			path.delete(0, end);
			path.append(temp);//url이 전달되는게 아니라 json형식 즉 문자열이 전달됨
		}// end of imageUpload
		//http://localhost:8000/notice/imageGet.gd?imageName=avatar25.png
		else if("imageGet".equals(upmu[1])) {//첨부파일을 처리할 때 다운로드 처리하는 화면에서 사용할 코드 소개함
			String b_file = req.getParameter("imageName");// avartar.png
			logger.info("111 => " +b_file);
			String filePath = "C:\\Program Files\\workspace_jsp\\nae2Gym\\src\\main\\webapp\\pds" ;
			File file = new File(filePath, b_file.trim());
			logger.info("222 => " + file );
			String mimeType = req.getServletContext().getMimeType(file.toString());
			if(mimeType == null) {
				res.setContentType("application/octet-stream");
			}
			String downName = null;
			FileInputStream fis = null;
			ServletOutputStream sos = null;
			try {
				if(req.getHeader("user-agent").indexOf("MSIE")==-1) {
					downName = new String(b_file.getBytes("UTF-8"), "8859_1");//국제 표준규격- 다국어지원
				}else {
					downName = new String(b_file.getBytes("EUC-KR"), "8859_1");	//한국 표준 규격				
				}
				res.setHeader("Content-Disposition", "attachment;filename="+downName);
				logger.info("333");
				fis = new FileInputStream(file);
				logger.info(fis);
				sos = res.getOutputStream();
				byte b[] = new byte[1024*10];
				int data = 0;
				logger.info("444");
				while((data=(fis.read(b,0, b.length)))!=-1) {
					sos.write(b,0,data);
				}
				sos.flush();//FileInputStream을 사용해서 file객체를 읽음- 메모리에 쌓인 정보를 비우는 메소드 호출 
				isRedirect = true;//null처리를 해둠
				logger.info(path);
				int end = path.toString().length();// -> notice/
				path.delete(0, end);
				path = null;
			} catch (Exception e) {
				logger.info(e.toString());
			} finally {
				try {
					if(sos !=null) sos.close();
					if(fis !=null) fis.close();
				} catch (Exception e2) {
					// TODO: handle exception
				}
			}
		}// end of imageGet
		//파일 업로드 시와 마찬가지로 파일 정보를 얻어올때도 출력페이지를 내보낼 필요가 없는 경우임
		if(path !=null) {//응답페이지가 존재하는 경우만 처리할것
			af.setPath(path.toString());//이 대로 두면 NullPointerException대상임			
		}else {
			af.setPath(null);//이 대로 두면 NullPointerException대상임			
		}
		af.setRedirect(isRedirect);//true-> ActionForward - isRedirect - false->true
		return af;
	}
}

Logic

	public Map<String, Object> imageUpload(MultipartRequest multi, String realFolder) {
		logger.info("imageUpload");
		Map<String,Object> pMap = new HashMap<>();
		Enumeration<String> files = multi.getFileNames();
		String fullPah = null;//파일 정보에 대한 전체경로
		String filename = null;//파일이름
		//첨부파일이 있다면?
		if(files !=null) {
			//파일 이름을 클래스로 정의하는 객체 - 파일객체가 생성되었다고 해서 그 안에 내용까지 포함하진 않음
			//파일 크기를 계산해주는 메소드 지원함
			File file = null;
			while(files.hasMoreElements()) {
				String fname = files.nextElement();
				filename = multi.getFilesystemName(fname);
				pMap.put("bs_file", filename);//avartar.png
				//File객체 생성하기
				file = new File(realFolder+"\\"+filename);
			}
			//첨부파일의 크기를 담기
			double size = 0;
			if(file !=null) {
				size = file.length();
				size = size/(1024);
				pMap.put("bs_size", size);
			}
		}
		return pMap;
		//return pMap;
	}
	
}

FrontMVC

		if(af !=null) {
			if(af.isRedirect()) {//true라는 건 sendRedirect인 경우임
				//첨부파일을 업로드 하는 것은 페이지 이동과 전혀 무관하다
				//첨부파일이 처리된 경우에는 path에 null을 반환하게 한다
				if(af.getPath() == null) {
					return;//해당메소드 탈출
				}else {
					//파일업로드가 아닌 경우 응답으로 나갈 페이지 url이 담기는 변수가 path이다.
					//이런 부분들을 스프링에서는 XXXXViewResolver라는 클래스가 지원하는 부분
					res.sendRedirect(af.getPath());// -> notice/noticeList.jsp					
				}
			}
			else{//forward인 경우임 - url안바뀜, 화면은 바뀜, 유지됨. a페이지에서 쥐고 있는 정보를 b페이지에서도 사용가능함
				//슬래쉬가 포함된 경우
				if(af.getPath().contains("/")) {
					RequestDispatcher view = req.getRequestDispatcher(af.getPath());
					view.forward(req, res);					
				}
				else if(af.getPath() == null) {//파일 업로드 처리시 ActionForward를 통해서 값을 리턴 받을때 문제가 발생됨. 이부분에 대한 해결  프로세스 추가하였다.
					logger.info("path가 null일때");
				}
				//슬래쉬가 미포함인 경우
				//-> 슬래쉬가 포함되었다는건 응답으로 나가는 마임타입이 html이다. path.append(notice/noticeList.jsp)
				//1.  json 포맷이라면 당연히 없음
				//2. 문자열 형식일때 - ReactJS와 같이 이종간의 언어가 뷰계층을 담당할 때 필수템
				//3. null일때 - 이미지 업로드 처리시나 첨부파일 처리시에는 리턴으로 나갈 값이 필요없다.
				else {
					logger.info("슬래쉬가 미포함인 경우 ===> " + af.getPath());
					res.setCharacterEncoding("utf-8");
					res.setContentType("text/plain;utf-8");
					PrintWriter out = res.getWriter();
					out.print(af.getPath());
					return;					
				}
			}

오체분시

Jsp

	  const  selectLocalImage = (event) =>  {
		  console.log('selectLocalImage');
		  //e.preventDefault();이벤트 버블링 방어 코드 작성 - submit이슈, <button type=submit|button>
		  //quill에디터에서 이미지를 클릭했을 때 실제 화면에서는 <input type=file>생성해 주자
		  //이미지를 선택하면 선택하자마자 백엔드에 요청을 post방식으로 넘긴다 - 8000번 서버의 pds폴더에 선택한 이미지를 업로드 처리함
		  //파일을 (바이너리코드) 전송할땐 무조건 post방식으로 해야 함
		    const fileInput = document.createElement('input');//<input> - DOM API의 createElement를 사용하여 태그 생성하는 코드
		    fileInput.setAttribute('type', 'file');//<input type=file>
		    console.log("input.type " + fileInput.type);
		    //이미지 파일만 선택가능하도록 제한
		    fileInput.setAttribute("accept", "image/*");//* -> image/png , image/gif, image/jpg
		    fileInput.setAttribute("name", "image");//req.getParameter("image"); -> <input name="image"/>
		    fileInput.click();
  • input 엘리먼트를 만든다. <input>
    • fileInput.setAttribute('type','file') : <input type = file>

      • setAttribute를 통해서 태그 속성값을 부여하고 있다.

      • fileInput.setAttribute("name", "image"); <input type = file name = image> 로 한다.

		    fileInput.addEventListener("change", function () {  // change 이벤트로 input 값이 바뀌면 실행
		        const formData = new FormData();
		        const file = fileInput.files[0];
		        formData.append('image', file);

		        $.ajax({//<form method=post enctype=multipart/form-data/> 이럴경우 req.getParameter사용이 불가함 -> cos.jar
		            type: 'post',
		            enctype: 'multipart/form-data',
		            url: '/notice/imageUpload.gd',
		            data: formData,
		            processData: false,
		            contentType: false,
		            success: function (response) {
						console.log('avatar.png'+response);//avartar.png
						const url = "http://localhost:8000/notice/imageGet.gd?imageName="+response;//
						const range = quill.getSelection().index;
						quill.setSelection(range, 1);
						quill.clipboard.dangerouslyPasteHTML(range,
								'<img src='+url+' style="width:100%;height: auto;" alt="image" />');
		            }
  • 이 부분은 비동기적으로 처리가 되는데 이 ajax가 요청을 보내게되면,

    • FrontMVC -> NoticeController[imageUpload] -> nLogic[imageUpload] 리턴 -> af -> FrontMVC ->
      path: "bs_file의 value값" + isRedirect : false
      를 쥐고있다. 하지만 경로에 대한 처리가 없다. -> 그 요청이 성공 시 url에 값을 넣고, 이미지를 노출시킨다.quill.clipboard.dangerouslyPasteHTML
  • Input 값이 바뀔 떄 마다 이벤트가 바뀐다.

    • formData 참고

       formData.append(name, value) – name과 value를 가진 폼 필드를 추가한다.
       
       
        formData.append('image', file);
       
        - image 이름에 file 폼 필드를 추가
        
        - 내부동작은 어떻게 동작하는가? 
          
       <form method=post enctype=multipart/form-data/> 이럴경우 req.getParameter사용이 불가함 -> cos.jar를 사용한다.
       
        req.getParameter은 서블릿에 넘겨주기 때문에, 이 부분이 중요하다.
        
        원래는 바이너리 타입으로 받으면 req.getParameter를 못해서
        
        그래서 cos.jar를 이용하여 작성했다.
       
  • multipart/form-data : 바이너리 파일을 보낼 때 사용한다. (enctype=multipart/form-data를 사용하면 req.getParameter 사용하지 못한다.(null값만 찍힘))

  • 성공시에 url 값을 리턴하는데, const url = "http://localhost:8000/notice/imageGet.gd?imageName="+response; 이런 형식을 띈다.

http://localhost:8000/notice/imageGet.gd?imageName=JSP.png

포스트맨 Test Case) url로 서버에 요청을 보내보자.

url 바로 접속 case

이렇게 사진을 리턴하는것을 볼 수 있다.

url에 / 슬래시가 들어가면 마임타입이 HTML이다. 앞서 얘기했던 것 처럼 url에

  1. 문자열
  2. 이미지 ✅
  3. Json
  4. null 값이 들어갈 수 있다.

지금 예제는 이미지일 경우이다. 리턴형식으로 url로 이미지를 리턴하지 않는가?

다르게 단위테스트를 진행해보겠다

http://localhost:8000/notice/imageGet.gd?imageName=오라클.png

잘 실행되는것을 알 수 있다.

이 부분이 비동기적으로 처리되는 부분이다.(페이지에서 사진을 노출시킬 떄 사용)

 $.ajax({//<form method=post enctype=multipart/form-data/> 이럴경우 req.getParameter사용이 불가함 -> cos.jar
		            type: 'post',
		            enctype: 'multipart/form-data',
		            url: '/notice/imageUpload.gd',
		            data: formData,
		            processData: false,
		            contentType: false,
		            success: function (response) {
						console.log('avatar.png'+response);//avartar.png
						const url = "http://localhost:8000/notice/imageGet.gd?imageName="+response;//
						const range = quill.getSelection().index;
						quill.setSelection(range, 1);
						quill.clipboard.dangerouslyPasteHTML(range,
								'<img src='+url+' style="width:100%;height: auto;" alt="image" />');
		            }

성공시 처리 부분을 보면 직관적으로 알 수 있다.

  • quill.getSelection(). : API 참고하자. 간단하게 요약하면,

    • 사용자의 선택 범위를 가져온다. 선택적으로 에디터에 focus를 맞춘다. 이미지태그를 넣기위해 사용한다.
    • quill.clipboard.dangerouslyPasteHTML : 이 부분에서 이미지 삽입이 일어난다.
const html = quill.root.innerHTML;
		console.log(html);
		quill.on('text-change', (delta, oldDelta, source) => {
			  console.log('글자가 입력될때 마다 호출');
			  console.log(quill.root.innerHTML);
			  //console.log(source);//user
			  //console.log(delta);
			  //console.log(oldDelta);
		}); ////////////// end of onchage - 텍스트 내용이 변경되었을 때 발동
		quill.getModule('toolbar').addHandler('image',  () => {
			console.log('image가 변경되었을때');
			selectLocalImage();
		})
  • quill.root.innerHTML : html를 가져온다.
  • quill.on()를 진행하면 이렇게 출력 결과가 나온다.

  • Text를 입력하면 값이 찍혀서 나온다.

정리하면,

업로드 시 [Update]

  1. url : http://localhost:8000/quill/quillTest.jsp. 요청하면

2. 이 응답을 누가받는가? .gd가 아니기 떄문에 서블릿을 경유하지않는다.

3. 그렇다면 어떻게 처리하는가?

  1. 이미지 업로드 시 ajax를 보면 요청 url이
    [JS]

    • /notice/imageUpload.gd를 요청한다. 즉. 이 말은 "이미지 업로드 시" 에만 서블릿을 경유한다는 얘기다.

    • 사용자가 이미지를 업로드 했을 떄, 이 url( /notice/imageUpload.gd)이 요청된다.

    [자바처리]

    • FrontMvc에서 (~~.gd) url를 가로챈다.

    • upmu[0 : notice] + upmu[1 : imageUpload] -> NoticeController로 간다.

    • NoticeController에서(if(imageUpload))로 가고 -> nlogic를 탄 다음 Map 객체를 리턴받는다.

      • 이 떄 path : temp(이미지이름) isRedirect : false 처리
    • FrontMVC에 돌아와서 forward 실시 -> 슬래시도 아니고 null도 아니기 떄문에 마지막 else문 실행한다.

    [JS]

    • JS로 돌아와서 성공했으면, 이미지를 화면에 뿌려줄 것이다. 여기서 응답값은 무엇일까? 어떻게 응답값을 Servelt에서 뿌려줄까?
    success: function (response) {
    							console.log('avatar.png'+response);//avartar.png
    							const url = "http://localhost:8000/notice/imageGet.gd?imageName="+response;//
    							const range = quill.getSelection().index;
    							quill.setSelection(range, 1);
    							quill.clipboard.dangerouslyPasteHTML(range,
    									'<img src='+url+' style="width:100%;height: auto;" alt="image" />');
    			            }
    				res.setCharacterEncoding("utf-8");
    				res.setContentType("text/plain;utf-8");
    				PrintWriter out = res.getWriter();
    				out.print(af.getPath());
    • 응답값으로 이렇게 return한다. 이 말은 가장 중요한 부분인데,
      url페이지처리를 하지 않고 PrintWriter 으로 화면에 값을 뿌려준다. 즉, 이 부분이 응답페이지로 나간다는 얘기다.
    • FrontMVC는 res.setContentType("text/plain;utf-8") 이 부분을 출력하기 위한 코드는 PrintWriter이다. PrintWriter를 통해서
      JSP7.png 가 화면에 이런식으로(예시) 응답으로 나가는 것이다.

  • 그러면 response는 = JSP7.png 이 되고, 이 값을 다시 url에 넣고
    그 값을 quill라이브러리 메소드를 통해 이미지 값을 넣는다.

    추가적으로 마임타입까지 jsp와 서블릿이 동일하다는 것을 볼 수 있다.

    contentType="text/html; charset=UTF-8" == res.setContentType("text/plain;utf-8");

밑에 NoticeController 에서 이미지Get를 호출하는 코드는 JS에서 찾아볼수가 없는데 어디서 호출하는거야?

  • /notice/imageUpload.gd에 POST 요청을 보내고, 성공적인 응답을 받으면 응답으로부터 이미지 URL을 추출하여 Quill 에디터에 이미지를 삽입한다.

    • 이미지 업로드 후에 성공적인 응답을 받은 경우, 서버는 이미지를 다시 가져오기 위해 /notice/imageGet.gd에 대한 요청을 처리한다.

    • 이 부분이 위에서 const url로 요청url를 가져온뒤,

    • 이미지 업로드 후의 성공적인 처리에서는 /notice/imageGet.gd로의 요청이 발생하게 되고, 서블릿에서는 해당 요청에 대한 처리를 else if("imageGet".equals(upmu[1])) 부분에서 수행하게된다.

      			quill.clipboard.dangerouslyPasteHTML(range,
      				'<img src='+url+' style="width:100%;height: auto;" alt="image" />');
      				            },
      		

Controller 오체분시

else if("imageUpload".equals(upmu[1])) {//delete
			//quill editor에서 이미지를 선택하면 해당 요청을 호출함 - 비동기처리
			//post이면서 enctype 바이너리인 경우 전송이 안됨
			MultipartRequest multi  = null;
			String realFolder = "C:\\Program Files\\workspace_jsp\\nae2Gym\\src\\main\\webapp\\pds";
			String encType = "utf-8";
			int maxSize = 5*1024*1024;
			try {
				multi = new MultipartRequest(req, realFolder, maxSize,  encType, new DefaultFileRenamePolicy());
			} catch (Exception e) {
				logger.info(e.toString());
			}
			Map<String, Object> rmap = nLogic.imageUpload(multi, realFolder);
			String temp = null;
			if(rmap !=null) {
				temp = rmap.get("bs_file").toString();
			}
			int end = path.toString().length();// -> notice/
			path.delete(0, end);
			path.append(temp);//url이 전달되는게 아니라 json형식 즉 문자열이 전달됨
		}// end of imageUpload
		//http://localhost:8000/notice/imageGet.gd?imageName=avatar25.png
		else if("imageGet".equals(upmu[1])) {//첨부파일을 처리할 때 다운로드 처리하는 화면에서 사용할 코드 소개함
			String b_file = req.getParameter("imageName");// avartar.png
			logger.info("111 => " +b_file);
			String filePath = "C:\\Program Files\\workspace_jsp\\nae2Gym\\src\\main\\webapp\\pds" ;
			File file = new File(filePath, b_file.trim());
			logger.info("222 => " + file );
			String mimeType = req.getServletContext().getMimeType(file.toString());
			if(mimeType == null) {
				res.setContentType("application/octet-stream");
			}
			String downName = null;
			FileInputStream fis = null;
			ServletOutputStream sos = null;
			try {
				if(req.getHeader("user-agent").indexOf("MSIE")==-1) {
					downName = new String(b_file.getBytes("UTF-8"), "8859_1");//국제 표준규격- 다국어지원
				}else {
					downName = new String(b_file.getBytes("EUC-KR"), "8859_1");	//한국 표준 규격				
				}
				res.setHeader("Content-Disposition", "attachment;filename="+downName);
				logger.info("333");
				fis = new FileInputStream(file);
				logger.info(fis);
				sos = res.getOutputStream();
				byte b[] = new byte[1024*10];
				int data = 0;
				logger.info("444");
				while((data=(fis.read(b,0, b.length)))!=-1) {
					sos.write(b,0,data);
				}
				sos.flush();//FileInputStream을 사용해서 file객체를 읽음- 메모리에 쌓인 정보를 비우는 메소드 호출 
				isRedirect = true;//null처리를 해둠
				logger.info(path);
				int end = path.toString().length();// -> notice/
				path.delete(0, end);
				path = null;
			} catch (Exception e) {
				logger.info(e.toString());
			} finally {
				try {
					if(sos !=null) sos.close();
					if(fis !=null) fis.close();
				} catch (Exception e2) {
					// TODO: handle exception
				}
			}
		}// end of imageGet
		//파일 업로드 시와 마찬가지로 파일 정보를 얻어올때도 출력페이지를 내보낼 필요가 없는 경우임
		if(path !=null) {//응답페이지가 존재하는 경우만 처리할것
			af.setPath(path.toString());//이 대로 두면 NullPointerException대상임			
		}else {
			af.setPath(null);//이 대로 두면 NullPointerException대상임			
		}
		af.setRedirect(isRedirect);//true-> ActionForward - isRedirect - false->true
		return af;
	}
}

if(imageUpload) 일 때,

else if("imageUpload".equals(upmu[1])) {//delete
			//quill editor에서 이미지를 선택하면 해당 요청을 호출함 - 비동기처리
			//post이면서 enctype 바이너리인 경우 전송이 안됨
			MultipartRequest multi  = null;
			String realFolder = "C:\\Program Files\\workspace_jsp\\nae2Gym\\src\\main\\webapp\\pds";
			String encType = "utf-8";
			int maxSize = 5*1024*1024;
			try {
				multi = new MultipartRequest(req, realFolder, maxSize,  encType, new DefaultFileRenamePolicy());
			} catch (Exception e) {
				logger.info(e.toString());
			}
			Map<String, Object> rmap = nLogic.imageUpload(multi, realFolder);
			String temp = null;
			if(rmap !=null) {
				temp = rmap.get("bs_file").toString();
			}
			int end = path.toString().length();// -> notice/
			path.delete(0, end);
			path.append(temp);//url이 전달되는게 아니라 json형식 즉 문자열이 전달됨
		}

MultipartRequest클래스는 무엇일까?

//  ↓ request 객체,               ↓ 저장될 서버 경로,       ↓ 파일 최대 크기,    ↓ 인코딩 방식,       ↓ 같은 이름의 파일명 방지 처리
// (HttpServletRequest request, String saveDirectory, int maxPostSize, String encoding, FileRenamePolicy policy)
// 아래와 같이 MultipartRequest를 생성만 해주면 파일이 업로드 된다.(파일 자체의 업로드 완료)
MultipartRequest multi = new MultipartRequest(request, savePath, sizeLimit, "utf-8", new DefaultFileRenamePolicy());
  1. req : request 객체

  2. realFolder : 저장될 서버 경로를 의미한다.

  3. maxSize : 파일 최대 크기

  4. encType : 인코딩방식

  5. DefaultFileRenamePolicy : 중복제거(같은 이름의 파일명 방지 처리)

MultipartRequest를 "생성"만 해주면 파일이 업로드 된다.(파일 자체의 업로드 완료)

그리고 rmap객체를 생성하는데, nLogic.imageUpload로 부터 처리된 값을 rmap이 받는다.

nLogic.imageUpload 파라미터엔 2개의 인자가 들어가며

multi : 파일 클래스를 참조하는 참조변수(세팅완료)
realFolder : 파일 절대경로

왜 2가지 인자를 넣는것일까?

imageUpload 오체분시

public Map<String, Object> imageUpload(MultipartRequest multi, String realFolder) {
		logger.info("imageUpload");
		Map<String,Object> pMap = new HashMap<>();
		Enumeration<String> files = multi.getFileNames();
		String fullPah = null;//파일 정보에 대한 전체경로
		String filename = null;//파일이름
		//첨부파일이 있다면?
		if(files !=null) {
			//파일 이름을 클래스로 정의하는 객체 - 파일객체가 생성되었다고 해서 그 안에 내용까지 포함하진 않음
			//파일 크기를 계산해주는 메소드 지원함
			File file = null;
			while(files.hasMoreElements()) {
				String fname = files.nextElement();
				filename = multi.getFilesystemName(fname);
				pMap.put("bs_file", filename);//avartar.png
				//File객체 생성하기
				file = new File(realFolder+"\\"+filename);
			}
			//첨부파일의 크기를 담기
			double size = 0;
			if(file !=null) {
				size = file.length();
				size = size/(1024);
				pMap.put("bs_size", size);
			}
		}
		return pMap;
		//return pMap;
	}
Enumeration<String> files = multi.getFileNames();
  • getFileNames() 메소드는 폼 요소중 input 태그에서 file속성으로 지정된 태그의 name 속성의 값
<input type = "file" name = "uploadFile"> 
 태그가 폼 요소에 있다면, 
 getFileNames() 메소드는 uploadFile이란 
 파라미터의 이름을 저장한
 Enumeration 객체를 반환한다.
  • 즉, file 속성을 가진 파라미터의 이름을 Enumeration 객체 타입으로 반환한다.
fileInput.setAttribute("name", "image"); 
 name = image가 들어가기 때문에 

이 원리를 그대로 적용해보면, files는 name속성의 값(image)들을 읽어들이고 그 값을 Enumeration 객체타입으로 반환한다.

		if(files !=null) {
			//파일 이름을 클래스로 정의하는 객체 - 파일객체가 생성되었다고 해서 그 안에 내용까지 포함하진 않음
			//파일 크기를 계산해주는 메소드 지원함
			File file = null;
			while(files.hasMoreElements()) {
				String fname = files.nextElement();
				filename = multi.getFilesystemName(fname);
				pMap.put("bs_file", filename);//avartar.png
				//File객체 생성하기
				file = new File(realFolder+"\\"+filename);
			}
			//첨부파일의 크기를 담기
			double size = 0;
			if(file !=null) {
				size = file.length();
				size = size/(1024);
				pMap.put("bs_size", size);
			}
		}
  • 다중 파일 업로드시 getFileNames()를 통해 모든 파일의 이름을 얻어온다.

  • getFilesystemName(name): 을 사용하여 실제파일명을 얻어온다.

  • files는 Enumeration 객체타입이다. 즉(input의 name을 가지고 있다.) 쉽게 말하면, image이다.

  • File file = null 로 선언함으로서

    • File : 파일 이름클래스로 정의하는 객체다.
    • 파일 객체가 생성되었다고 해서 그 안에 내용까지 포함하지 않는다.
  • while문을 반복한다.

    • fname에는 : files.nextElement() Key값이 들어간다(이미지명).
    • "파일 이름"을 받아올 때는
      MultipartRequest : getFileSystemName("파라미터 Name") 메서드를 사용한다.
  • 하나 눈여겨 볼 점은 파라미터로 받은 multi를 기준으로 getFileSystemName,getFileNames메소드를 사용했다.

    • 왜 multi인가? MultipartRequest를 "생성"만 해주면 파일이 업로드 되기 때문에 파일에 대한 정보를 쥐고있다.
  • 읽어온 값을 pMap.put("bs_file", filename)으로 저장한다.

  • file = new File(realFolder[절대경로]+"\\" + filename);

    • 만약에 filename : avatar.png

    • realFolder : C:\Program Files 라고 가정했을 때,

    • File(C:\Program Files\avatar.png) 이렇게 들어간다.

    • 그리고 파일 사이즈를 구한다. 이 부분은 while문 안쪽에서 작업할 필요가 없다.

      최종적으로 나온 FIle의 사이즈를 구하는게 더 합리적이기 때문에 바깥에 위치시켰다.

      그리고 최종적으로 pMap을 리턴한다

    pMap엔 뭐가 담겨있을까?

    {"bs_file" : filename, "bs_size" : size } 이렇게 담겨있다. 이렇게 들어간 값을 리턴한다.

다시 Controller로 돌아와서

			Map<String, Object> rmap = nLogic.imageUpload(multi, realFolder);
			String temp = null;
			if(rmap !=null) {
				temp = rmap.get("bs_file").toString();
			}
			int end = path.toString().length();// -> notice/
			path.delete(0, end);
			path.append(temp);//url이 전달되는게 아니라 json형식 즉 문자열이 전달됨
		}
  • 리턴받은 rmap에 {"bs_file" : filename, "bs_size" : size } 를 저장한다.

  • rmap에 값이 들어 있다면, 파일의 이름을 알아야하는데, 그 방법을 temp = rmap.get("bs_file").toString()으로 구했다.

    • 그리고 path의 길이를 구했다. 이미지 업로드 처리 같은 경우 path에 대한 경로를 추가할 필요가 없다. 즉, 페이징처리를 하지 않는다. 그렇기 때문에 지금처럼
    • path : /notice/를 갖고 있다면, 에러가 발생한다.(404)
      • 그래서 이 url를 제거해야된다.

      • path.delete(0,end) 하고 path.append(temp(이미지이름)); 를 붙였다.

        path.append(temp(이미지이름)) 포인트 부분이다.

      • path : 이미지이름

      • 하지만 forward 방식이기 때문에 url문제가 없음

업로드 시 pds폴더에 잘 저장되는것을 확인했다.

같은 이미지 이름을 첨부할 시 다른 이름으로 저장 되는 부분을 확인 해 볼 수 있다. DefaultFileRenamePolicy()

MultipartRequest(req, realFolder, maxSize,  encType, new DefaultFileRenamePolicy());

지금까지 업로드 하는 코드를 살펴봤다. 이번엔 첨부파일을 Get하는 메소드를 설계해보자.

image src = "서블릿태움" 해당 주소로 서블릿 태울 수 있다.

요청 시기

  1. 업로드완료 후 JS에서 Ajax로 성공 시 const url = 값을 넣는다.

  2. 이 url를 이미지에 넣는데, 이미지경로도 서블릿을 경유한다.(요청한다) 그 떄 NoiceController가 받아서 처리과정

이런식으로 어떻게 구현할까?

else if("imageGet".equals(upmu[1])) {//첨부파일을 처리할 때 다운로드 처리하는 화면에서 사용할 코드 소개함
			String b_file = req.getParameter("imageName");// avartar.png
			logger.info("111 => " +b_file);
			String filePath = "C:\\Program Files\\workspace_jsp\\nae2Gym\\src\\main\\webapp\\pds" ;
			File file = new File(filePath, b_file.trim());
			logger.info("222 => " + file );
			String mimeType = req.getServletContext().getMimeType(file.toString());
			if(mimeType == null) {
				res.setContentType("application/octet-stream");
			}
			String downName = null;
			FileInputStream fis = null;
			ServletOutputStream sos = null;
			try {
				if(req.getHeader("user-agent").indexOf("MSIE")==-1) {
					downName = new String(b_file.getBytes("UTF-8"), "8859_1");//국제 표준규격- 다국어지원
				}else {
					downName = new String(b_file.getBytes("EUC-KR"), "8859_1");	//한국 표준 규격				
				}
				res.setHeader("Content-Disposition", "attachment;filename="+downName);
				logger.info("333");
				fis = new FileInputStream(file);
				logger.info(fis);
				sos = res.getOutputStream();
				byte b[] = new byte[1024*10];
				int data = 0;
				logger.info("444");
				while((data=(fis.read(b,0, b.length)))!=-1) {
					sos.write(b,0,data);
				}
				sos.flush();//FileInputStream을 사용해서 file객체를 읽음- 메모리에 쌓인 정보를 비우는 메소드 호출 
				isRedirect = true;//null처리를 해둠
				logger.info(path);
				int end = path.toString().length();// -> notice/
				path.delete(0, end);
				path = null;
			} catch (Exception e) {
				logger.info(e.toString());
			} finally {
				try {
					if(sos !=null) sos.close();
					if(fis !=null) fis.close();
				} catch (Exception e2) {
					// TODO: handle exception
				}
			}
		}// end of imageGet

내가 몰랐던 부분

  • 필자는 먼저 Jsp에서 서로 값을 받을 때, setAttribute, getAttribute로 받아야 되는 줄 알았다. (마치 불문율처럼..ㅋㅋ)

  • 근데 JS에서 비동기처리로 보내는 Ajax url 성공시 url 이미지 넣을 떄 src로 imageName을 req.getParameter받을 수 있다니.. 이런 생각을 못했다.

    • url로 쿼리스트링 사용자에대한 입력 값이 나오는데 이 부분을 getParameter로 받을 수 있다

    • 정확히 말하면, url를 이미지태그에 삽입 시 src = "url"이 요청 되는데 이 때, "호출"이 일어난다. 그래서 그 호출 시 imageName의 avatar.png를 가져오는 것이다.✅

String b_file = req.getParameter("imageName"); 이면 

b_file = response가 저장된다.

getParameter란?


request 객체의 getParameter() 메서드로 사용자가 입력한 데이터를 가져올수있다



<from> 데이터를 살펴보면



<input type = "text" name = "id">

<input type = "password" name = "pass"> 



위와 같이 데이터를 입력받고 submit으로 서블릿을 호출할때



request.getParameter("name값") 으로 가져올 수 있다.

GET방식과 POST방식 둘다 request.Parameter()를 사용할 수 있다.

  • 내가 헷갈린 부분은 enctype=multipart/form-data 이 부분이 때문이다.
    form 태그 속성에 enctype=multipart/form-data 바이너리 속성이 들어가면 req.getParameter() 사용하면 null이 나온다.

  • 지금은 폼태그가 아니라 ajax로 보내는 부분인데 혼동했다.

			String b_file = req.getParameter("imageName");// avartar.png
			logger.info("111 => " +b_file);
			String filePath = "C:\\Program Files\\workspace_jsp\\nae2Gym\\src\\main\\webapp\\pds" ;
            File file = new File(filePath, b_file.trim());
			logger.info("222 => " + file );
			String mimeType = req.getServletContext().getMimeType(file.toString());
			if(mimeType == null) {
				res.setContentType("application/octet-stream");
			}
  • mimeType = req.getServletContext().getMimeType(file.toString());

    • getServletContext(): 페이지에 대한 서블릿 실행 환경 정보를 담고 있는 application 내장 객체를 리턴한다. 

    • 서블릿 컨텍스트를 통해 MIME 타입을 가져오는 코드

  • res.setContentType("application/octet-stream");

    • MIME의 개별 타입 중 application에 속하는 타입인디, 8비트 단위의 binary data라는 뜻

    • "특별히 표현할 수 있는 프로그램이 존재하지 않는 데이터의 경우 기본값으로 octet-stream을 사용한다." = 브라우저가 보통 자동으로 실행하지 않거나 실행할지 묻기도 하는 타입이다

            String downName = null;
			FileInputStream fis = null;
			ServletOutputStream sos = null;
			try {
				if(req.getHeader("user-agent").indexOf("MSIE")==-1) {
					downName = new String(b_file.getBytes("UTF-8"), "8859_1");//국제 표준규격- 다국어지원
				}else {
					downName = new String(b_file.getBytes("EUC-KR"), "8859_1");	//한국 표준 규격				
				}
				res.setHeader("Content-Disposition", "attachment;filename="+downName);
				logger.info("333");
				fis = new FileInputStream(file);
				logger.info(fis);
				sos = res.getOutputStream();
				byte b[] = new byte[1024*10];
				int data = 0;
				logger.info("444");
				while((data=(fis.read(b,0, b.length)))!=-1) {
					sos.write(b,0,data);
				}
  • ServletOutputSteam : 파일을 읽어올 때에는 FileInputStream으로 읽어온 뒤 브라우저에 출력할 때에는 ServletOutputStream을 사용한다.

  • FileInputStream: 파일을 읽기 위한 스트림이다.

    • 파일을 바이너리 형태로 읽어와서 메모리에 저장한다.

    • 파일을 읽어와서 바이트 배열에 저장한다.

  • ServletOutputStream: 서블릿에서 클라이언트에게 데이터를 출력하기 위한 스트림이다.

    • 바이트 배열을 클라이언트에게 전송한다.

    • 서블릿 컨테이너는 이 출력 스트림을 통해 데이터를 클라이언트에게 보낸다.

  • (req.getHeader("user-agent").indexOf("MSIE")==-1

    • 브라우저의 종류를 체크한다.

    • MSIE"는 Internet Explorer의 User-Agent 문자열에 해당한다.

    • "MSIE"가 없으면(즉, IE가 아니면), 다른 브라우저로 간주한다.

  • downName = new String(b_file.getBytes("UTF-8"), "8859_1")

    • b_file.getBytes("UTF-8")은 파일 이름을 UTF-8 문자열로 "인코딩"한다.

    • 그리고 다시 new String(..., "8859_1") 은 이 UTF-8 문자열을 ISO-8859-1(또는 Latin-1)로 디코딩하여 새로운 문자열을 생성한다.

    • b_file : 이미지이름

  • res.setHeader("Content-Disposition", "attachment;filename="+downName): (HTTP 응답 헤더를 설정)

    • Content-Disposition : 헤더는 브라우저에게 컨텐츠를 어떻게 처리해야 하는지 알려주는 역할을 한다.
    • "attachment;filename="+downName
      • "브라우저에게 파일을 첨부파일로 다운로드" 하라는 지시를 전달한다.
      • downName은 다운로드될 파일의 이름이다.
File file = new File(filePath, b_file.trim());

fis = new FileInputStream(file);

- java.io.FileInputStream@166edcf2
  • file객체를 생성하였다.
while((data=(fis.read(b,0, b.length)))!=-1) {
					sos.write(b,0,data);
				}
                
                sos.flush();
                
- 최대 b.length 바이트만큼 데이터를 읽어온다.
- 읽어온 데이터는 b 바이트 배열에 저장되고, 읽은 바이트 수가 data 변수에 대입된다.
- 만약 더 이상 읽을 데이터가 없다면 -1을 반환한다


- 읽어온 데이터를 ServletOutputStream 
- (sos)을 통해 클라이언트로 전송한다.
- write(b, 0, data)는 배열 b의 처음부터 읽은 바이트 수 data까지를 출력한다.
                isRedirect = true;//null처리를 해둠
				logger.info(path);
				int end = path.toString().length();// -> notice/
				path.delete(0, end);
				path = null;
			} catch (Exception e) {
				logger.info(e.toString());
			} finally {
				try {
					if(sos !=null) sos.close();
					if(fis !=null) fis.close();
				} catch (Exception e2) {
					// TODO: handle exception
				}
			}
		}// end of imageGet
  • path를 null 처리 했기 때문에, isRedirect 상관이없다.

    • 왜 null 처리를 했을까?

    • 앞에서 얘기했던 것 처럼 파일 업로드 시와 마찬가지로 파일 정보를 얻어올 때도 출력페이지를 내보낼 필요가 없다.

      • 내 식대로 해석하자면, 파일 업로드가 url 페이징 처리가 발생하는가? -> No 그래서 path경로 자체를 null로 준 것.
      • 포인트는 출력페이지를 내보낼 "필요가 없다는 것이다" == null
		af.setPath(path.toString());
		af.setRedirect(isRedirect);

이전에는 이 코드였지만, path = null를 넣었기 때문에 null 처리를 진행해야된다.

		if(path !=null) {//응답페이지가 존재하는 경우만 처리할것
			af.setPath(path.toString());//이 대로 두면 NullPointerException대상임			
		}else {
			af.setPath(null);//이 대로 두면 NullPointerException대상임			
		}
		af.setRedirect(isRedirect);//true-> ActionForward - isRedirect - false->true
		return af;
	}
  • path에 대한 경로 null 처리만 진행하고 setRedirect는 동일하다.

FrontMVC

        if(af !=null) {
			if(af.isRedirect()) {//true라는 건 sendRedirect인 경우임
				//첨부파일을 업로드 하는 것은 페이지 이동과 전혀 무관하다
				//첨부파일이 처리된 경우에는 path에 null을 반환하게 한다
				if(af.getPath() == null) {
					return;//해당메소드 탈출
				}else {
					//파일업로드가 아닌 경우 응답으로 나갈 페이지 url이 담기는 변수가 path이다.
					//이런 부분들을 스프링에서는 XXXXViewResolver라는 클래스가 지원하는 부분
					res.sendRedirect(af.getPath());// -> notice/noticeList.jsp					
				}
			}
			else{//forward인 경우임 - url안바뀜, 화면은 바뀜, 유지됨. a페이지에서 쥐고 있는 정보를 b페이지에서도 사용가능함
				//슬래쉬가 포함된 경우
				if(af.getPath().contains("/")) {
					RequestDispatcher view = req.getRequestDispatcher(af.getPath());
					view.forward(req, res);					
				}
				else if(af.getPath() == null) {//파일 업로드 처리시 ActionForward를 통해서 값을 리턴 받을때 문제가 발생됨. 이부분에 대한 해결  프로세스 추가하였다.
					logger.info("path가 null일때");
				}
				//슬래쉬가 미포함인 경우
				//-> 슬래쉬가 포함되었다는건 응답으로 나가는 마임타입이 html이다. path.append(notice/noticeList.jsp)
				//1.  json 포맷이라면 당연히 없음
				//2. 문자열 형식일때 - ReactJS와 같이 이종간의 언어가 뷰계층을 담당할 때 필수템
				//3. null일때 - 이미지 업로드 처리시나 첨부파일 처리시에는 리턴으로 나갈 값이 필요없다.
				else {
					logger.info("슬래쉬가 미포함인 경우 ===> " + af.getPath());
					res.setCharacterEncoding("utf-8");
					res.setContentType("text/plain;utf-8");
					PrintWriter out = res.getWriter();
					out.print(af.getPath());
					return;					
				}
			}
		}/
  • 첨부파일을 업로드 하는 것과 페이지이동과는 무관하다.

- SendRedirect인 경우 (af.isRedirect())

  • 첨부파일이 처리된 경우에는 path에 null를 반환하게 된다.

    ============파일 업로드인 경우==========
    
    		if(af.getPath() == null) {
    				return;//해당메소드 탈출
    			}
  • 파일 업로드가 아닌경우

     else {
    			//파일업로드가 아닌 경우 응답으로 나갈 페이지 url이 담기는 변수가 path이다.
    			//이런 부분들을 스프링에서는 XXXXViewResolver라는 클래스가 지원하는 부분
    res.sendRedirect(af.getPath());// -> notice/noticeList.jsp					
    					}
    • 파일 업로드가 아닌 경우, 응답으로 나갈 페이지 url이 담기는 변수가 path이다.
    • getter를 통해서 af의 path를 구한다.

- Forward인 경우 (af.isRedirect())

	if(af.getPath().contains("/")) {
		RequestDispatcher view = req.getRequestDispatcher(af.getPath());
		view.forward(req, res);					
				}
  • 슬래시가 포함되어 있다는건, 응답으로 나가는 마임타입이 HTML이다.

  • 슬래쉬가 미포함인 경우

    • Json 포맷이라면 당연히 없음

    • 문자열 형식일때 - ReactJS와 같이 이종간의 언어가 뷰계층을 담당할 때 필수템

    • null일때 - 이미지 업로드 처리시나 첨부파일 처리시에는 리턴으로 나갈 값이 필요없다.

      [null 일 때,] :

    		else if(af.getPath() == null) {//파일 업로드 처리시 ActionForward를 통해서 값을 리턴 받을때 문제가 발생됨. 이부분에 대한 해결  프로세스 추가하였다.
    				logger.info("path가 null일때");
    			}
    

    [슬래시가 미포함인경우] Json, 문자열

    		else {
    				logger.info("슬래쉬가 미포함인 경우 ===> " + af.getPath());
    				res.setCharacterEncoding("utf-8");
    				res.setContentType("text/plain;utf-8");
    				PrintWriter out = res.getWriter();
    				out.print(af.getPath());
    				return;					
    					}
profile
아는만큼보인다.

0개의 댓글