국비교육(22-23)

34일차(1)/jsp(19) : fetch, ajax 함수

서리/Seori 2022. 11. 24. 23:37

34일차(1)/jsp(19) : fetch, ajax 함수


- fetch : 익스플로러 안의 빌트인 함수
- fetch 함수의 사용법과, 왜 사용하는 것인지를 익히기

 

[ 웹브라우저에서 서버에 요청(request) 하는 방법 ]

1. 링크 클릭(get) → 페이지 이동 → 응답된 내용이 화면에 나타남
- 링크를 클릭하면 주소창에 변화가 생긴다(페이지가 이동된다)
- 서버는 클라이언트의 요청에 응답하고, 응답되는 내용이 화면에 나타난다.

2. 폼 제출(get or post) → 페이지 이동 → 응답된 내용이 화면에 나타남
- 폼 전송시에는 get/post 중 어떤 방식으로 전송할지 결정할 수 있다.
- 서버는 클라이언트의 요청에 응답하고, 응답되는 내용이 화면에 나타난다.


*** 웹브라우저에서 서버에 요청(request)을 하는 방법 중에 페이지 이동 없이
javascript로 요청을 하고 응답된 내용을 함수의 매개변수로 받아오는 방법이 있다.

- 그 중 하나가 fetch함수이다.

 

 

<test09.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test09.jsp</title>
</head>
<body>
	<h1>fetch() 함수 테스트</h1>
	<button id="myBtn">눌러보셈</button>
	<a href="${pageContext.request.contextPath }/index.jsp">눌러보셈</a>
	<script>
		document.querySelector("#myBtn").addEventListener("click", function(){
			//페이지 전환 없이 index.jsp 페이지로 요청하기 
			fetch("${pageContext.request.contextPath }/index.jsp")
			.then(function(response){
				return response.text();
			})
			.then(function(data){
				//index.jsp 페이지가 응답하는 문자열이 함수의 인자로 전달된다. 
				console.log(data);
			});	
		});
	
	</script>
</body>
</html>

 

 

- 버튼을 누르면 콘솔에 뭔가 출력된다. 페이지 이동은 없다.

- 전체가 따옴표 " " 로 감싸진 문자열을 출력한 것이다.

 

- html형태로 보이지만, 이 문자열이 data 안에 " " 으로 들어가 있는 것.

- 저 내용은 jsp 페이지이다. jsp가 응답한 내용을 javascript가 문자열로 변환해서 받아낸 것이다.

 

 

- fetch 함수에 어디에 요청할지 요청 주소를 적으면 된다.

 

- 어느 servlet/jsp 페이지에 요청할지 적으면 promise 타입을 리턴한다.

  저 요청에 대한 응답이 잘 오면 then 함수가 호출된다.

 


- response 안에는 object타입이 들어있다. { text:function(){ } , .... }

  text 라는 key값으로 fuction(){} value값을 꺼내서 사용하는 것!
- 이 함수를 호출했을 때, text가 리턴해주는것은 promise type이다.

 

- 두번째 then 응답한 data가 console.log에 들어온다. 이것은 문자열이다!

 

- 이것을 Ajax Request (Ajax 요청) 이라고 부른다. 페이지 이동 없이 응답된 내용을 받아오는 것!

- 페이지전환 없이, 응답된 데이터를 화면상에 표시하지 않고, javascript 함수의 매개변수로 받아온다.

 

 

- 검사-Network에서 서버의 응답내용을 볼수있다.

- 상태(status) 200은 정상적인 응답이 되었다는뜻!!

 

- 요청이 잘못되면 404가 나온다.

 

- 보통은 페이지에 출력될 내용을 javascript함수로 받아낸 것이다!

 

 

ex) 일상생활의 ajax요청은 어떤 것이 있을까?

- 회원가입시 회원아이디가 사용이 가능한지 나오는 문구

- 페이지 전환 없이 정보를 서버에 보내서 사용가능/불가능 여부를 응답받은 것이다.

 

- 요청만 하는 것이 아니고 요청 파라미터를 같이 전달할 수 있다.

  (get방식은 파라미터를 주소 뒤에 붙여서 달고 간다./post방식으로 전송하면 몸통에 파라미터를 달고 간다)

- 페이지 전환 없이 요청 파라미터를 보내는 것은 둘다 가능하다. 데이터도 보내고, 응답도 받을 수 있다.

 

- 파일업로드도 페이지전환 없이 할 수 있다.(input type="file" 인 폼)

- 일반 폼과의 차이점은 페이지 전환이 없다는 것이다. 이렇게 만들어야 하는 경우가 많다!

 


 

- fetch("https://daum.net") 은 가능할까? → 오류 발생. 다음 서버에서 응답을 거부한 것이다.

- 주소창에 직접 주소를 적으면 응답하지만, javascript로 보낸 요청에는 응답하지 않는다.

 

- CORS에 의해 블록되었다고 나온다. CORS 정책에 어긋나서 응답하지 않는 것이다.(동일출처정책)

 

- 각각의 서버가 있고, 웹브라우저가 있을 때, 웹페이지는 응답을 받을 수 있다

- 브라우저에서 javascript 실행환경에서 ajax 요청을 하면 응답하지 않는다.

  나에게 응답한 서버에 요청해야지 오는 것이다.

 

- ajax요청도 웹페이지를 받은 동일 서버에다가 해야한다.

- 이 웹페이지의 출처는 다른 서버이기 때문에 응답을 하지 않는 것이다. 이것을 CORS라고 부른다.

 

 

- 현재 이 PC에서는 Tomcat WebServer에게서 받은 서버명을 사용하고 있는데,

다른 외부서버에 요청하면 응답하지않는다. 기본 세팅이 그렇게 되어있다. 보안상의 문제로...

 

- 이렇게 작성하는 것은 fetch 함수를 사용하는 하나의 형식이다.

- fetch를 호출하면서 jsp나 서블릿을 요청 

→ 응답하면 then 함수가 호출되고,

→ object 에 들어있는 text라는 함수를 호출해주면 data에 해당 문자열이 들어온다.

 


 

<test10.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test10.jsp</title>
</head>
<body>
	<input type="text" id="msg" placeholder="메세지 입력..." />
	<button id="sendBtn">전송</button>
	<script>
		/*
			input 요소에 문자열을 입력하고 전송버튼을 누르면 입력한 문자열이 send.jsp페이지로
			전송이 되도록 하려고 한다.
			단, 페이지 전환 없이
		*/
		
		let result1;
		let result2;
		let result3;
		
		document.querySelector("#sendBtn").addEventListener("click", function(){
			//입력한 내용을 읽어온다.
			let msg=document.querySelector("#msg").value;
			//fetch 함수를 호출하면서 GET방식 파라미터로 send.jsp 페이지를 요청하면서 전달한다.
			fetch("send.jsp?msg="+msg)
			.then(function(response) {
				return response.text();
			})
			.then(function(data){
				console.log(data);
				//함수의 매개변수에 전달할 문자열을 result1에 대입
				result1=data;
				//함수의 매개변수에 전달할 문자열을 object로 변환해서 result2에 대입
				result2=JSON.parse(data);
				//object를 json문자열로 변환해서 result3에 대입
				result3=JSON.stringify(result2);
			});
		});
	</script>
</body>
</html>

 

- 페이지 전환이 되면서 전송되려면 form을 구성하면 된다.

 그런데 페이지 전환 없이 전송되려면?

 

 

<send.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//msg라는 파라미터명으로 전달되는 문자열을 읽어온다.
	String msg=request.getParameter("msg");
	//콘솔창에 출력하기
	System.out.println(msg);
	//응답!
%>
{"isSuccess":true}

 

- 클라이언트가 보낸 정보가 주소창 변화 없이 전송되어 이클립스의 콘솔창에 출력된다.

 

- 응답된 문자열이 브라우저의 콘솔창에 출력된다.

 

- 클라이언트 요청 → 서버 전달 → 서버에서 send.jsp 이어서 실행 → 클라이언트에게 응답을 되돌려줌

 

- 응답하는 것만이 아니라 함수의 매개변수로도 받아왔다.

 

 

- 서버가 msg를 읽어내면 응답이 문자열로 전달된다

- 응답되는 문자열이 함수의 매개변수로 받아진다.

 

- 첫번째 then에서 response.text 로 호출해 주어야만 저기에 값이 들어올 수 있다.

 

- 클라이언트(브라우저)의 동작 / 서버(jsp 파일)의 동작을 분리해서 생각하기.

 

- 페이지 전환 없이, 서버에 데이터를 보낼 수도 있고 응답을 받아올 수도 있다.

- 응답은 javascript의 매개변수로 받는다.

 


Q) response.text 가 왜 object타입인지?

 

A) fetch() 함수 안에서 작업의 결과를 object로 전달했기 때문에.

  console.log로 찍어보면 response가 {key:value}가 들어있는 object타입이다.

  promise에서 object 값을 넣어준 것이다. promise내부에서 작업을 하고 넣어준 값!

 

 

- fetch는 기본 object이다. 열어보면 여러 개의 방이 있다. text, json 등등의 방에는 함수가 들어있다

return response.text(); : text라는 방에서 호출한 것이 promise 타입인 것이다.(뒤에 .then이 나오므로)

 

return response.text(); 를 사용하는 것은 그냥 fetch 함수의 사용법이므로 따라야 한다...

response.text(); 는 response object 안에 있는 함수를 호출하는 표현식이다. 해당 함수를 호출하면 promise type이 리턴된다.

 

- 함수 기본 작성 양식을 따르면서 어디에 요청할 것인지, 응답된 데이터를 어떻게 사용할 것인지를 정하면 된다.

 

- 이런 형태보다는 'json문자열'을 많이 받아서 쓴다. 

- 그래야 javascript 함수로 사용하기가 편하다.

 


 

[ json (Java Script Object Notation) ] ★★★

 

- JSON : Java Script Object Notation (자바스크립트 객체 표기법) 을 따르는 문자열 형식

 

- xml 문자열 형식과 비교할 수 있다.

 

(필기하고 이미지 삭제하기)

 

ex) 회원의 번호, 이름, 성별을 xml로 표기하면 

<!-- xml -->
<data>
    <num>1</num>
    <name>바나나</name>
    <isMan>False</isMan>
</data>
<!-- or -->
<data num="1" name="바나나" isMan="false" />

- xml: 어떤정보를 구조화해서 가지고 있는 것. 속성, 자식 요소등으로 가지고 있을 수도 있다.

 

ex) 회원의 번호, 이름, 성별을 json형식으로 표기하면

<!-- JSON -->
{"num":1, "name":"바나나", "isMan":true}

 {"num":1, "name":"바나나", "isMan":true)}

- javascript 객체의 표기법과 닮아있다. 단 반드시 key값을 " "으로 감싸야 한다.

 

- java라면 값을 각각 추출해서 int num, String name, boolean isMan 에 담은 형태로 사용할 것

 

- 위와 같은 문자열로 전달받아서 필요한 정보를 빼내는 작업을 할 것이다.

- java의 기능을 사용해서 빼내는데, 그러려면 문자열 형식이 맞아야 한다. → {key:value, key2:value, ...}

 

* key 값 : 반드시 " " 으로 감싸야한다.
* value의 형식 :
 - 숫자: 10 or 20 or 10.1 or 10.2
 - 문자: "abcd" or "xxxx"
 - 논리: true or false
 - 빈 값: null
 - object : {  }
 - array : [    ]
 - 함수 : 없음 (javascript가 아니기 때문에 value에 함수를 넣을 수는 없다)

 

- 이 형식에 어긋나면 error가 발생한다..

- 왜 위와같은 형식이 자주 쓰이는가? 저런 형식의 문자열은 app에서 주로 사용하는 문자열 형식이기 때문에.

 

- json문자열은 java에서 사용하기엔 좀 불편하지만, javascript에서 사용하기엔 유리한 모양이다.

- 이런 형식의 문자열은 쉽게 javascript 객체로 바꿀 수가 있다.

 

{"isSuccess":true} 를 입력하면 콘솔에 문자열이 들어온다.

 


 

[JSON으로 응답하기]

 

<test11.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test10.jsp</title>
</head>
<body>
	<input type="text" id="msg" placeholder="메세지 입력..." />
	<button id="sendBtn">전송</button>	
	<script>
		/*
			input 요소에 문자열을 입력하고 전송버튼을 누르면 입력한 문자열이 send.jsp페이지로
			전송이 되도록 하려고 한다.
			단, 페이지 전환 없이
		*/
		
		let result1;
		let result2;
		let result3;
		
		document.querySelector("#sendBtn").addEventListener("click", function(){
			//입력한 내용을 읽어온다.
			let msg=document.querySelector("#msg").value;
			//fetch 함수를 호출하면서 GET방식 파라미터로 send.jsp 페이지를 요청하면서 전달한다.
			fetch("send2.jsp?msg="+msg)
			.then(function(response) {
				//서버에서 응답된 문자열이 json형식이면 response.json() 함수를 호출해서 리턴해준다.
				return response.json();
			})
			.then(function(data){
				//여기에 전달된 data는 바로 사용할 수 있는 object이다. 
				console.log(data);
				console.log(data.isSuccess);
			});
		});
	</script>
</body>
</html>

 

result1=data;
result2=JSON.parse(data);

- 전역변수를 만들어서 then 안에 넣어준다.

 

 

- JSON.parse 함수를 사용해서 변환한 값은 바로 true를 내보낸다.

- JSON이라는 기본 오브젝트가 있다. 여기의 parse 함수를 이용하면 JSON형식의 문자열을 실제 object로 바꿀 수 있다.

 

result1=data; (문자열 그대로 출력)
result2=JSON.parse(data); (문자열을 object로)
result3=JSON.stringify(result2); (object를 String으로)

- 전역변수 3개를 만들어 출력되는 형태를 비교하기.

 

- json데이터는 result2와 같이 변환해서 사용하면 편리하다. (바로 점 찍어서 사용가능!)

 


 

return response.json();

- text가 아닌 json함수를 호출한다.

 

- text면 문자열 리턴, json이면 object 리턴

- json을 호출하면 알아서 parse() 작업까지 해서 저기에 전달해준다. . 점을 찍어서 바로 사용할 수 있다.

- text로 호출하는 경우보다 좀더 편리하게 사용 가능하다.

 

 

<send2>

<%@ page language="java" contentType="application/json; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//msg라는 파라미터명으로 전달되는 문자열을 읽어온다.
	String msg=request.getParameter("msg");
	//콘솔창에 출력하기
	System.out.println(msg);
	//응답!
%>
{"isSuccess":true}

 

- json을 쓰지 않으면 무조건 text를 호출해야 한다. json을 호출할 수도 있다.(json이 편리하다...)

 

- json을 응답할 예정이면 위의 <% %>을 아래와 같이 바꿔주어야한다.

<%@ page language="java" contentType="application/json"; charset=UTF-8" pageEncoding="UTF-8"%> 

 

- 앞으로 json문서를 많이 사용하게 될 것...

 

ex) 일상생활에서 사용하는 버스도착정보 등도 json으로 날아온다. 서버에 값을 전달하는 것은 json형식.

 그중 원하는 값을 빼내서 화면 UI에 보여주는 것이다.

 


 

webapp-js 폴더 생성

webapp/js/sy_util.js 라는 파일 생성 (외부 js파일)

 

<sy_util.js>

// webapp/js/sy_util.js

   /*
      ajaxPromise("요청url", "요청메소드", query string or object)
      와 같은 형식으로 사용하고 
      Promise type 을 리턴해주는 함수 
   */
   function ajaxPromise(url, method, data){
      //만일 필요한 값이 전달 되지 않으면 기본값을 method 와 data 에 넣어준다. 
      if(method == undefined || method == null){
         method="GET";
      }
      if(data == undefined || data == null){
         data={};
      }
      
      let queryString;
      if(typeof data == "string"){
         queryString=data;
      }else{
         queryString=toQueryString(data);
      }
      
      // Promise 객체를 담을 변수 만들기 
      let promise;
      if(method=="GET" || method=="get"){//만일 GET 방식 전송이면 
         //fetch() 함수를 호출하고 리턴되는 Promise 객체를 변수에 담는다. 
         promise=fetch(url+"?"+queryString);
      }else if(method=="POST" || method=="post"){//만일 POST 방식 전송이면
         //fetch() 함수를 호출하고 리턴되는 Promise 객체를 변수에 담는다. 
         promise=fetch(url,{
            method:"POST",
            headers:{"Content-Type":"application/x-www-form-urlencoded; charset=utf-8"},
            body:queryString
         });
      }
      return promise;
   }
   
   //함수의 인자로 ajax 전송할 폼의 참조값을 넣어주면 알아서 ajax 전송되도록 하는 함수 
   function ajaxFormPromise(form){
      const url=form.getAttribute("action");
      const method=form.getAttribute("method");
      
      // Promise 객체를 담을 변수 만들기 
      let promise;
      //파일 업로드 폼인지 확인해서
      if(form.getAttribute("enctype") == "multipart/form-data"){
         //폼에 입력한 데이터를 FormData 에 담고 
         let data=new FormData(form);
         // fetch() 함수가 리턴하는 Promise 객체를 
         promise=fetch(url,{
            method:"post",
            body:data
         });
         return promise;//리턴해 준다 (여기서 함수가 종료 된다.)         
      }
      
      const queryString=new URLSearchParams(new FormData(form)).toString();
      
      if(method=="GET" || method=="get"){//만일 GET 방식 전송이면 
         //fetch() 함수를 호출하고 리턴되는 Promise 객체를 변수에 담는다. 
         promise=fetch(url+"?"+queryString);
      }else if(method=="POST" || method=="post"){//만일 POST 방식 전송이면
         //fetch() 함수를 호출하고 리턴되는 Promise 객체를 변수에 담는다. 
         promise=fetch(url,{
            method:"POST",
            headers:{"Content-Type":"application/x-www-form-urlencoded; charset=utf-8"},
            body:queryString
         });
      }
      return promise;
   }
   
   //함수의 인자로 요청 url 과 ajax 전송할 내용이 있는 input 요소의 참조값을 전달하면 ajax 전송해주는 함수
   function ajaxInputPromise(url, input){
      const type=input.getAttribute("type");
      const name=input.getAttribute("name");
      
      let promise;
      if(type=="file"){ // input type="file" 인 경우 
         
         let data=new FormData();
         data.append(name, input.files[0]);
         
         promise=fetch(url,{
            method:"post",
            body:data
         });
      }else{ //아닌경우 
         
         //전송할 쿼리 문자열 구성
         const data=name+"="+encodeURIComponent(input.value);
         promise=fetch(url,{
            method:"POST",
            headers:{"Content-Type":"application/x-www-form-urlencoded; charset=utf-8"},
            body:data
         });
      }
      return promise;
   }
   
   //인자로 전달하는 object 를 이용해서 query  문자열을 만들어서 리턴해주는 함수
   function toQueryString(obj){
      //빈배열을 일단 만든다.
      let arr=[];
      //반복문 돌면서 obj 에 있는 정보를 "key=value" 형태로 만들어서 배열에 저장한다.
      for(let key in obj){
         //value 는 인코딩도 해준다. 
         let tmp=key+"="+encodeURIComponent(obj[key]);
         arr.push(tmp);
      }
      //query 문자열을 얻어낸다
      let queryString=arr.join("&");
      //결과를 리턴해준다.
      return queryString;
   }

 

- 유틸리티처럼 쓸 수 있는 함수가 몇개 들어있다.

- ajaxPromise, ajaxFormPromise, ajaxInputPromise

 

 

- fetch 함수를 사용해서 promise를 리턴해주고 있다.

- 위의 3개 함수를 사용해서 ajax요청을 편하게 할 수 있다.

 

- jsp파일에서 이 파일을 사용하려면 아래 한줄을 추가해주면 된다.

<script src="${pageContext.request.contextPath }/js/sy_util.js"></script>

- 이렇게 추가해두면 그 js파일 안의 함수를 사용할 수 있다.

 

ajaxInputPromise("send2.jsp", document.querySelector("#msg"){ })

- url과 input요소의 참조값을 받는 함수

- 단, name 값이 필수이다!  name="msg" 추가

 

- fetch함수가 promise 객체를 리턴해주고 있다.

 


 

<test12>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/promise/test12.jsp</title>
</head>
<body>
	<h3>ajax form 예제</h3>
	<form action="insert.jsp" method="post" id="myForm">
		<input type="text" name="name" placeholder="이름 입력..." />
		<input type="text" name="addr" placeholder="주소 입력..." />
		<button type="submit">추가</button>
	</form>
	<script src="${pageContext.request.contextPath }/js/sy_util.js"></script>
	<script>
		//id가 myForm인 요소에 "submit"이벤트가 일어나면 실행할 함수 등록
		document.querySelector("#myForm").addEventListener("submit", function(event){
			//폼 제출을 강제로 막기
			event.preventDefault();
			//sy_util.js에 있는 함수를 이용해서 폼에 입력한 내용을 ajax 요청을 통해 전송한다.
			//this대신에 document.querySelector("#myForm") 해도 된다.
			ajaxFormPromise(this)  // 여기서 this는 submit이벤트가 일어난 폼의 참조값이다.
			.then(function(response){
				return response.json();
			})
			.then(function(data){
				console.log(data);
			});
		});
		
	</script>
</body>
</html>

 

<insert.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//요청 파라미터 추출
	String name=request.getParameter("name");
	String addr=request.getParameter("addr");
	//추출한 문자열을 콘솔창에 출력
	System.out.println(name+" | "+addr);
	
	//응답
%>
{"isSuccess":true}

- {"isSuccess":true}에는 ; 를 붙이면 안 된다.

- 이름, 주소를 읽어와서 이클립스 콘솔에 출력해주는 코드

 

- 콘솔에 출력되고, 브라우저에도 페이지 전환이 일어나면서 응답이 나타난다.

 

- 그런데 이걸 페이지 전환 없이 하고 싶다면?

- 폼 안에서 submit 버튼을 누르면 폼에 submit event가 일어난다. 마치 click 이벤트처럼!

 

document.querySelector("#myForm").addEventListener("submit", function(event){
});

- 이벤트 오브젝트가 매개변수로 전달되는 함수를 javascript로 만들어준다.

 

- 폼 전송을 강제로 막아두고 ajax로 처리되도록 한다.

 

- 모든 input 요소의 값을 얻어내서 전송해준다.

 

- 폼 전송도 되고, 응답도 받을 수 있다.

 

ajaxInputPromise() : input요소에 입력한 내용을 전송해 주는 함수

ajaxFormPromise() : form요소에 입력한 모든 내용을 전송해주는 함수

 

 

- Promise의 이 형태를 외워두기!

 

- 호출하면서 참조값을 넘겨주면 ajaxformpromise 함수 안에서 알아서 ajax로 전송해 준다.

- 응답된 데이터를 console.log에서 받아서 쓰면 된다.

 

- sy_util.js 에 이미 대부분의 메소드가 만들어져있다.

- 파일을 전송하는 폼이면 알아서 이 함수안에서 해준다. 이미 코딩이 되어 있다.

- 안에서 여러 작업을 하고(조건절 등), fetch해서 promise를 리턴해준다.

 

 

- get방식과 post방식의 전송방식 차이도 이미 만들어져 있다. 참고해서 코딩하기.