본문 바로가기

Back-End 공부/Spring

실습으로 CSR, SSR 작동원리 이해하기 CORS 에러 해결

csr과 ssr에 대해 하나도 모른다면 아래의 글을 보고 오자.

https://rookie-programmer.tistory.com/203

 

렌더링이란? CSR, SSR 그게 도대체 뭔데?

💡 CSR, SSR 그게 뭔데? CSR과 SSR에 대해 알기 전에, (1)SPA와 MPA와 (2)브라우저 렌더링에 대해 알아야 한다. 📌 SPA, MPA ✅ SPA == CSR SPA(Single Page Application) : 하나의 페이지로 구성된 웹 애플리케이션이

rookie-programmer.tistory.com

 

csr과 ssr은 개념은 글과 사진만으로는 개념을 완벽하기 이해하기 어렵다.

아래 실습을 통해 실제 HTML이 어떻게 요청되고, 데이터는 어떻게 반환되고, 화면이 어떻게 다시 띄워지는지 알아보자.

 

 

CSR

[ PostRepository.java ]

List에 5개의 Post 데이터가 담겨있다. Post는 [번호, 제목, 내용, 글쓴이]를 저장하는 객체이다.

실습 예제에서는 PostRepository.java 파일을 데이터를 가지고 있는 곳이라고 가정하겠다.

다른 클래스에서 findAll 함수를 사용해 저장된 데이터들을 모두 가져올 수 있다. ex) postRepository.findAll()

public class PostRepository {
    List<Post> posts;
    PostRepository(){
        this.posts = new ArrayList<>();
        this.posts.add(new Post(1, "title1", "hello java world1", "ShinYJ1"));
        this.posts.add(new Post(2, "title2", "hello java world2", "ShinYJ2"));
        this.posts.add(new Post(3, "title3", "hello java world3", "ShinYJ3"));
        this.posts.add(new Post(4, "title4", "hello java world4", "ShinYJ4"));
    }
    List<Post> findAll(){
        return this.posts;
    }
}

 

 

 

[ PostService.java ]

Service 클래스에서는 Repository에 있는 데이터를 Controller로 가져다주는 역할을 한다.

사용자는 postService.findAll()를 통해 데이터 저장소에 있는 데이터들을 전달받을 수 있다.

public class PostService {
    private PostRepository postRepository;
    PostService(){
        this.postRepository = new PostRepository();
    }
    List<Post> findAll(){
        return this.postRepository.findAll();
    }
}

 

 

 

[ PostController.java ]

사용자의 URL 입력값에 따라 특정 함수를 호출해 데이터를 화면에 띄워준다.

사용자가 URL에 http://localhost:8080/csr/json를 치고 들어오면,

PostController 클래스의 @GetMapping과 매핑되어 findAll() 함수가 실행되고,

postService.findAll()을 통해 데이터 저장소에서 데이터를 가져온 후 HTML에 데이터를 넣어준다.

사용자는 데이터가 담긴 HTML을 확인할 수 있다.

//CSR : 사용자에게 data return
@GetMapping("/csr/json") //url에 localhost:8080/csr/json치고 들어오면 해당 함수와 매핑
@ResponseBody //json으로 데이터를 받을 때
public List<Post> findAll(){
    return this.postService.findAll();
}

 

 

 

 

하지만 CORS 정책 위반으로 원하는 화면을 가져오지 못하는 것을 볼 수 있다!!!

 

 

 

💡 CORS (Cross Origin Resource Sharing) 정책이란?

도메인 또는 Origin  페이지가 다른 도메인 (도메인 요청) 가진 리소스에 액세스 있게하는 보안 메커니즘으로,

동일한 출처의 리소스에만 접근하도록 제한하는 것이다. 여기서 출처는 프로토콜, 호스트명, 포트가 같다는 것을 의미한다.


쉽게 말해, 현재 예제에서 데이터가 있는 URL은 localhost:8080/csr/json
HTML이 있는 URL은 file:///C:/Users/Playdata/Desktop/html_css_javascript/posts/16.js_postlist_axios.html이기 때문에
URL이 달라 소스의 원천(Origin)이 안맞는다고 하는 것이다.

 

 

현대적인 프로그래밍에서는 CSR 방식이 많이 사용된다.

프론트 URL : www.encore.com

백엔드 URL : api.encore.com

이런식으로 다른 URL을 가지고 있어 CORS 오류가 뜨는 것이 일반적이다.

이렇게 데이터가 있는 곳과, 데이터를 접근해 보여주는 곳의 URL이 다르면 뜨는 것이 CORS 오류!!

 

 

해결방법 1

-> Spring에서 제약을 거는 것이기 때문에 CorsConfig 파일을 추가해 모든(혹은 특정) 포트에 대한 접근을 열어둔다.

package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry corsRegistry){
        corsRegistry.addMapping("/**")
                .allowedOrigins("*") //특정 url에 대해서만 허용
                .allowedMethods("*");
    }
}

 

 

이렇게 설정하게 되면 데이터가 있는 곳과, 데이터를 접근해 보여주는 곳이 같지 않아도 데이터를 정상적으로 보여주게 된다.

 

 

해결방법2

data가 있는 Spring Server에 static 폴더 밑에 html 파일을 둔다.

= 데이터가 있는 위치에 HTML 파일을 넣어줘 URL을 맞춰준다.

이렇게 하면 localhost:8080/~~ 으로 URL이 같아지기 때문에, 따로 CORS 파일을 추가하지 않아도 된다.

 

 

 

 

 

두 가지 중 한 가지 방법을 선택해 CORS 오류를 해결하고 나면, HTML코드를 통해 데이터가 담긴 화면을 받아볼 수 있다.

이제 CSR 방식으로 데이터를 조회해서 HTML을 띄워보자.

 

📌 CSR 방식으로 데이터 조회해서 HTML 띄워보기 1 - 별도의 프론트엔드 URL 띄우기

 

(1) CORS 오류 해결방법 1번으로 CORS 오류를 잡아주고 C:/Users/Playdata/Desktop/html_css_javascript/posts/post_list.html 폴더에 있는 HTML 파일을 연다. (HTML 코드는 2번 방식과 동일하다)

 

⭐️ CSR로 HTML 가져오기 성공 ⭐️

 

HTML 위치(html_css_javascript/posts 폴더)와 데이터가 있는 위치(localhost:8080/csr/json)가 다르기 때문에 CORS 오류가 발생하는 것이 일반적이지만, 지금은 CORS 오류 해결방법 1번으로 CORS 오류를 잡아주었기 때문에 데이터를 정상적으로 가져온다.

 

(+)

aws를 이용한 심화 실습에서도 HTML은 s3에 있고, 데이터는 ec2에 있기 때문에 aws 실습에서도 cors오류가 발생할 것이다.

 

 

 

📌 CSR 방식으로 데이터 조회해서 HTML 띄워보기 2 - static 폴더의 HTML 띄우기

 

(1) CORS 오류 해결방법 2번으로 CORS 오류를 잡아주고 http://localhost:8080/post_list.html을 입력한다.

 

 

[ resuorce > static > post_list.html ]

html 코드에 서버에 데이터를 요청하는 ajax, axios가 있으면 CSR이다.

axios를 통해 /csr/json에서 데이터를 받아 html에 데이터를 넣어준다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>css_bootstrap</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>
        object {
            width:100%;
        }
    </style>
</head>
<body>
<object type="text/html" data="header.html"></object>
<div class="container">
    <div class="page-header"><h1>게시글 목록</h1></div>
    <div class="float-end"><a href="./7.css_bootstrap_postregister.html"><button class="btn btn-primary">글쓰기</button></a></div>
    <table class="table">
        <tr>
            <th>id</th>
            <th>title</th>
            <th>userId</th>
        </tr>
    </table>
</div>
<script>
    async function callBackImp(){
        try{
            let myTarget = document.getElementsByClassName("table")[0];
            const response1 = await axios.get('http://localhost:8080/csr/json');
            console.log(response1);
            response1.data.forEach(element => {
                myTarget.innerHTML += `<tr><td>${element.id}</td><td>${element.title}</td><td>${element.body}</td><td>${element.authorName}</td></tr>`
            });
        }catch(e){
            console.log(e);
        }
    }
    callBackImp();
</script>
</body>
</html>

 

 

[ PostController ]

package com.example.demo.post;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;

@Controller
public class PostController {
    private PostService postService;
    PostController(){
        this.postService = new PostService();
    }

    //CSR : 사용자에게 data return
    @GetMapping("/csr/json") //전체 url에다가 localhost:8080/csr/json치고 들어오면 해당 함수와 매핑
    @ResponseBody //json으로 데이터를 받을 때
    public List<Post> findAll(){
        return this.postService.findAll();
    }
}

 

 

⭐️ CSR로 HTML 가져오기 성공 ⭐️

 

CSR = @ResponseBody ⭐

-> static 폴더에 있는 html을 넘김

=> 데이터 없는 HTML/CSS/JS 반환 -> 반환된 브라우저가 /csr/json에 data요청 -> 데이터 담긴 최종 화면을 띄워준다. 

 

 

 

 

SSR

📌 SSR 방식으로 데이터 조회해서 HTML 띄워보기 - 타임리프를 통해 데이터가 담긴 HTML 반환

 

[ resuorce > templates > post_list.html ]

HTML 파일에 있는 타임리프를 통해 가져온 데이터를 삽입한다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>css_bootstrap</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body>
    <object type="text/html" data="header.html"></object>
    <div class="container">
        <div class="page-header"><h1>게시글 목록</h1></div>
        <div class="float-end">
            <a href="./7.css_bootstrap_postregister.html"><button class="btn btn-primary">글쓰기</button></a></div>
    <table class="table">
        <tr>
            <th>id</th>
            <th>title</th>
            <th>body</th>
            <th>authorName</th>
        </tr>
        <tr th:each="post : ${posts}">
            <td th:text="${post.id}"></td>
            <td th:text="${post.title}"></td>
            <td th:text="${post.body}"></td>
            <td th:text="${post.authorName}"></td>
        </tr>
    </table>
    </div>
</body>
</html>

 

 

[ PostController ]

package com.example.demo.post;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;

@Controller
public class PostController {
    private PostService postService;
    PostController(){
        this.postService = new PostService();
    }
    //SSR : 사용자에게 data가 들어간 화면 return
    @GetMapping("/ssr")
    public String findAllSsr(Model model){
        List<Post> posts = postService.findAll();
        //model을 통해 data를 html에 넣어주기
        model.addAttribute("posts", posts); //"key-value" => "posts-posts"
        return "post_list";
    }
}

 

SSR = @ResponseBody 없음 ⭐

-> String으로 HTML 파일명을 return하면 templates안에 있는 해당 HTML 파일을 넘김

이때, 데이터들은 model에 넣어서 넘겨주게 되면,

HTML 파일은 넘어온 model에 있는 데이터를 읽어와 알맞은 자리에 넣어준다.

=> 데이터 들어가 있는 HTML/CSS/JS 반환

 

 

⭐️ SSR로 HTML 가져오기 성공 ⭐️