본문 바로가기

STUDY/국비과정

[JAVA 웹 개발 공부] 국비지원 71일차 - 비동기, 싱글스레드, Ajax, Promise 객체, CORS, CSS 프레임워크, 캔버스

싱글 스레드와 멀티 스레드

 

1. 싱글 스레드
하나의 프로세스가 한번에 하나의 일만 처리하는 것이다. 

2. 멀티스레드
하나의 응용프로그램을 여러 개의 스레드로 구성하고 각 스레드로 하여금 하나의 작업을 처리하도록 하는 것이다.
하나의 스레드에 문제가 발생하면 전체 프로세스가 영향을 받기에 주의 깊은 설계가 필요하다.
또한 스레드 간의 자원 공유로 동시성 문제가 발생할 수 있다.

 

 

동기 vs 비동기

 

동기는 '직렬적'으로 작동하는 방식이고 비동기는 '병렬적'으로 작동하는 방식이다. 

 

1. 동기(synchronous : 동시에 일어나는)
요청을 하면 (바로) 응답을 받는다는 의미로 말 그대로 동시에 일어난다는 뜻이다.

요청과 그 결과가 동시에 일어난다는 약속으로, 바로 요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 한다.

2. 비동기(Asynchronous : 동시에 일어나지 않는)
동시에 일어나지 않는다를 의미로 요청과 결과가 동시에 일어나지 않을거라는 약속이다. 

요청과 응답이 다른 시간대 존재하기 때문에, 요청내용에 대해 지금 바로 혹은 당장 응답받지 않아도 된다. 

즉, 비동기란 특정 코드가 끝날때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것을 의미한다.

비동기 처리를 예로 Web API, Ajax, setTimeout 등이 있다.

 

 

Ajax

 

1. Ajax

Ajax란 Asynchronous JavaScript and XML의 약자로 빠르게 동작하는 동적인 웹 페이지를 만들기 위한 개발 기법의 하나이다.

비동기적 자바스크립트 동작을 하는 기술들을 통틀어 Ajax라고 부르며, Ajax를 이용해서 데이터를 불러오는 동안 다음 코드를 진행하는 것이 가능해진다.

Ajax는 웹 페이지 전체를 다시 로딩하지 않고도, 웹 페이지의 일부분만을 갱신할 수 있다.
즉 백그라운드 영역에서 서버와 통신하여, 그 결과를 웹 페이지의 일부분에만 표시할 수 있다.
이때 서버와는 다양한 형태의 데이터를 주고받을 수 있다. (JSON, XML, HTML, 텍스트 파일 등)

 

2. Ajax 구성 요소
Ajax는 기존에 사용되던 여러 기술을 함께 사용하여, 웹 페이지의 일부분만을 갱신할 수 있도록 해주는 개발 기법이다.

*Ajax에서 사용하는 기존 기술

HTML / CSS 웹 페이지의 표현
DOM 모델 데이터에 접근하거나 화면 구성을 동적으로 조작하기 위해 사용
JSON / XML 데이터의 교환
XMLHttpRequest 객체 웹 서버와의 비동기식 통신
자바스크립트 모든 기술을 결합하여 사용자의 작업 흐름을 제어하는 데 사용

Ajax의 동작은 Ajax 구성 요소들을 사용하여 이루어진다.
Ajax를 이용한 웹 응용 프로그램은 자바스크립트 코드를 통해 웹 서버와 통신을 하게 된다.
따라서 사용자의 동작에는 영향을 주지 않으면서도 백그라운드에서 지속해서 서버와 통신할 수 있다.

 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="container">
        <input id="user-id" type="number">
        <button id="send">전송</button>
    </div>
    <div id="result"></div>
</body>
<script>
    // ajax
    let send = document.getElementById("send");
    let result = document.getElementById("result");
    let userId = document.getElementById("user-id");

    send.addEventListener("click", function(e) {
        let num = userId.value;

        if (num) {
            fetch("https://reqres.in/api/users/" + num) 
                .then((resp) => resp.text())
                .then((text) => {
                    let p = document.createElement("p");
                    p.innerText = text;

                    result.append(p);
                })
            console.log("아래에 문장");
        }
    })
</script>
</html>

 

 

Fetch API

 

1. Fetch API

HTTP request 전송 기능을 제공하는 클라이언트 사이드 Web API이다.
*request : 클라이언트 → 서버
*response : 서버 → 클라이언트
즉 서버와의 통신을 통해 CRUD를 구현할 수 있게 하는 프라미스 지원 내장 함수이다.

 

2. fetch()

fetch 메서드는 JavaScript에서 서버로 네트워크 요청을 보내고 응답을 받을 수 있도록 해주는 메서드이다.
XMLHttpRequest와 비슷하지만 fetch는 Promise 기반으로 구성되어 있어서 더 간편하게 사용할 수 있다.

 

fetch(url)
.then(res => {
  console.log(res)
})
.catch(error => {
  console.log(error)
})

기본적인 구조와 동작은 Promise 객체와 동일하다. 파라미터로 요청을 보낼 url을 입력해주고 응답을 받아서 추가적인 작업 또한 해줄 수 있다. then에서 응답 객체 res를 받고, catch에서 에러 요청이 발생했을 때 에러를 받는다.

 

fetch('html').then(function(response){   
    response.text().then(function(text){
        alert(text);                 
    })
})

// fetch('html') : 서버에게 html이라는 파일 요청
// then : fetch 요청이 끝난후 실행되는 함수
// 모든 작업이 끝나면 alert(text)가 실행되도록 호출되어있다.
// text라는 변수 안에는 서버가 응답한 데이터가 들어있다.

 

 

Reqres - Ajax 연습하기

 

https://reqres.in/

 

Reqres - A hosted REST-API ready to respond to your AJAX requests

Native JavaScript If you've already got your own application entities, ie. "products", you can send them in the endpoint URL, like so: var xhr = new XMLHttpRequest(); xhr.open("GET", "https://reqres.in/api/products/3", true); xhr.onload = function(){ conso

reqres.in

 

 

fetch 활용 - 입력 유효성 검사

 

 

*email.html

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <form id="myform">
        <input tpye="email" id="email">
        <button id="emaildupli">중복 확인하기</button>
    </form>
    <p id="message"></p>
</body>
    <script>
        let myform = document.getElementById("myform");
        let btn = document.getElementById("emaildupli");
        myform.addEventListener("submit", function (e) {
            e.preventDefault();
        })

        btn.addEventListener("click", function (e) {
            let input = document.getElementById("email");
            let email = input.value;
            if (email) {
                let p = document.getElementById("message");


                fetch("http://localhost:8080/api/userinfo/emaildupli?email=" + email)
                    .then((resp) => resp.json())
                    .then((obj) => {
                        if (obj.count == 0) {
                            p.innerText = "사용할 수 있는 이메일";
                        } else if (obj.count == 1) {
                            p.innerText = "중복된 이메일"
                        } else {
                            p.innerText = "서비스 장애가 발생했습니다.";
                        }
                    })
                    .catch((e) => console.log(e))
                p.innerText = "잠시만 기다려주세요";
            }
        })
    </script>
</html>

 

*UserInfoServlet.java

package bookapp;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/api/userinfo/emaildupli")
public class UserInfoServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setHeader("Access-Control-Allow-Origin", "*");
		resp.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTION");
		resp.setHeader("Access-Control-Allow-Headers", "*");
		
		String email = req.getParameter("email");
		
		UserInfoDao dao = new UserInfoDao();
		int count = dao.getCount(email);
		
		String json = "{\"count\": " + count + "}";
		
		resp.getWriter().println(json);
	}
}

 

 

Promise 객체

 

1. 프로미스

자바스크립트는 비동기 처리를 위해 콜백함수를 사용한다.

하지만 콜백을 너무 남용하게 되면 가독성이 떨어지며 코드가 난잡해진다. 또한 에러처리도 힘들 뿐더러 여러 개의 비동기 처리를 한번에 하는데 한계가 있다.
이런 콜백 함수의 단점을 보완하며 비동기 처리에 사용되는 객체를 프로미스(Promise)라 한다.

 

2. 프로미스의 장점

*비동기 처리 시점을 명확하게 표현할 수 있다.
*연속된 비동기 처리 작업을 수정, 삭제, 추가하기 편하고 유연하다.
*비동기 작업 상태를 쉽게 확인할 수 있다.
*코드의 유지 보수성이 증가한다.


3. 프로미스의 상태

Pending(대기) 비동기 처리 로직이 아직 완료되지 않은 상태
Fulfilled(이행) 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
Rejected(실패) 비동기 처리가 실패하거나 오류가 발생한 상태

 

4. 프로미스 객체

const promise = new Promise((resolve, reject)=>{
	//처리 내용
});

이때 프로미스 객체의 resolve는 비동기 처리의 성공을, reject는 비동기 처리의 실패를 알리기 위한 함수들이다.

resolve(value) 일이 성공적으로 끝난 경우, 그 결과를 나타내는 value와 함께 호출.
state는 pending에서 fulfilled로, result는 value가 된다.
reject(error) 에러 발생 시 에러 객체를 나타내는 error와 함께 호출.
state는 pending에서 rejected로, result는 error가 된다.

5. 후속 처리 메소드
프로미스로 구현된 비동기 함수를 호출하는 측에서는 프로미스 객체의 후속 처리 메소드(then, catch)를 통해 비동기 처리 결과 또는 에러 메세지를 전달받아 처리한다.

then()은 생성한 프로미스 객체에서 인수로 전달한 resolve가 호출되면 then() 부분이 실행이 되고, reject가 호출이 된다면 catch부분이 실행이 된다.

promise.then(
	// resolve가 호출되면 then이 실행
)
.catch(
	// reject가 호출되면 catch가 실행
)
.finally(
	// 콜백 작업을 마치고 무조건 실행되는 finally (생략 가능)
)

 

(1) then()
then 메소드는 두 개의 콜백 함수를 인자로 전달 받는다.
첫 번째 콜백 함수는 성공(fulfilled, resolve 함수가 호출된 경우)시에 실행된다.
두 번째 콜백 함수는 실패(rejected, reject 함수가 호출된 경우)시에 실행된다.
then 메소드는 기본적으로 프로미스를 반환합니다.

(2) catch()
catch 메소드는 비동기 처리 혹은 then 메소드 실행 중 발생한 에러(예외)가 발생하면 호출된다.
catch 메소드 역시 프로미스를 반환한다.
 

(3) finally()

promise의 성공, 실패 여부와 상관없이 promise의 처리가 완료되면 실행된다.


6. 프로미스의 에러 처리 방법
(1) then()의 두 번째 인자로 에러를 처리하는 방법

getData().then(
  handleSuccess,
  handleError
);

(2) catch()를 이용하는 방법(가급적 사용)

getData().then().catch();

 

 

CORS(Cross-Origin Resource Sharing) - 교차 출처 리소스 공유

 

CORS는 다른 출처의 리소스 공유에 대한 허용/비허용 정책이다.

1. 출처(Origin)

Protolcol 과 Host 그리고 Port 까지 모두 합친 URL을 의미한다.

 

2. SOP(Same Origin Policy)

동일한 출처에 대한 정책을 말한다. 동일 출처(Same-Origin) 서버에 있는 리소스는 자유로이 가져올수 있지만, 다른 출처(Cross-Origin) 서버에 있는 이미지나 유튜브 영상 같은 리소스는 상호작용이 불가능하다는 말이다.
출처(Origin)의 동일함은 두 URL의 구성 요소 중 Protocol(Scheme), Host, Port 이 3가지만 동일하다면 동일 출처로 판단한다.

 

3. CORS 에러 메시지

에러 메시지는 브라우저의 SOP 정책에 따라 다른 출처의 리소스를 차단하면서 발생된 에러이며, CORS는 다른 출처의 리소스를 얻기위한 해결 방안 이었던 것이다. 요약하자면 SOP 정책을 위반해도 CORS 정책에 따르면 다른 출처의 리소스라도 허용한다는 뜻이다.


4. 브라우저의 CORS 기본 동작

 

(1) 클라이언트에서 HTTP요청의 헤더에 Origin을 담아 전달
기본적으로 웹은 HTTP 프로토콜을 이용하여 서버에 요청을 보내게 되는데,
이때 브라우저는 요청 헤더에 Origin 이라는 필드에 출처를 함께 담아 보내게 된다.

(2) 서버는 응답헤더에 Access-Control-Allow-Origin을 담아 클라이언트로 전달
이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더에 Access-Control-Allow-Origin이라는 필드를 추가하고 값으로 '이 리소스를 접근하는 것이 허용된 출처'를 내려보낸다.

(3) 클라이언트에서 자신이 보냈던 요청의 Origin과 서버가 보내준 Access-Control-Allow-Origin을 비교
이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 차단할지 말지를 결정한다.

유효할 경우 다른 출처의 리소스를 문제없이 가져오며, 만약 유효하지 않다면 그 응답을 사용하지 않고 버린다. (CORS 에러 !!)


5. CORS 해결


(1) Chrome 확장 프로그램 이용
'Allow CORS: Access-Control-Allow-Origin' 크롬 확장 프로그램을 설치하여 활성화 시켜 해결할 수 있다.


(2) 서버에서 Access-Control-Allow-Origin 헤더 세팅
직접 서버에서 HTTP 헤더 설정을 통해 출처를 허용하게 설정하는 가장 정석적인 해결책이다.
서버의 종류도 노드 서버, 스프링 서버, 아파치 서버 등 여러가지가 있다.

*JSP / Servlet 세팅

import javax.servlet.*;

public class CORSInterceptor implements Filter {

    private static final String[] allowedOrigins = {
            "http://localhost:3000", "http://localhost:5500", "http://localhost:5501",
            "http://127.0.0.1:3000", "http://127.0.0.1:5500", "http://127.0.0.1:5501"
    };

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        String requestOrigin = request.getHeader("Origin");
        if(isAllowedOrigin(requestOrigin)) {
            // Authorize the origin, all headers, and all methods
            ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Origin", requestOrigin);
            ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Headers", "*");
            ((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Methods",
                    "GET, OPTIONS, HEAD, PUT, POST, DELETE");

            HttpServletResponse resp = (HttpServletResponse) servletResponse;

            // CORS handshake (pre-flight request)
            if (request.getMethod().equals("OPTIONS")) {
                resp.setStatus(HttpServletResponse.SC_ACCEPTED);
                return;
            }
        }
        // pass the request along the filter chain
        filterChain.doFilter(request, servletResponse);
    }

    private boolean isAllowedOrigin(String origin){
        for (String allowedOrigin : allowedOrigins) {
            if(origin.equals(allowedOrigin)) return true;
        }
        return false;
    }
}

 

 

 

CSS 프레임워크 - Tailwind 

 

미리 만들어진 컴포넌트와 스타일을 제공하는 라이브러리이다.

https://tailwindcss.com/docs/installation

 

Installation - Tailwind CSS

The simplest and fastest way to get up and running with Tailwind CSS from scratch is with the Tailwind CLI tool.

tailwindcss.com

 

 

캔버스(canvas)

 

canvas 요소는 웹 페이지에 그래픽을 그려주는 쉽고 강력한 방법을 제공하며, 그래픽을 위한 컨테이너(container) 역할만을 수행한다. 그래픽을 그리기 위해서는 자바스크립트(JavaScript) 등의 스크립트 언어를 이용해야 한다.