본문 바로가기

SpringBoot

[Spring Boot] fetch API와 Server 데이터 서버

 

Fetch API 데이터 전송 (기본)

클라이언트(html, js)

 

- index.js

<!DOCTYPE html>
<html lang=ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-compatible" content="IE=dege">
    <title>My first Web Page</title>
</head>

<body>
    <h1>fetch API 비동기 처리</h1>
    <form>
        <label for="id">아이디 :</label>
        <input type="text" id="id"> <br>
        <label for="pw">비밀번호 :</label>
        <input type="password" id="pw"><br>

        <!-- <input type="file" id="file"> -->
        <button type="submit" id="submit">전송</button>
    </form>
    <script src="/HTML_CSS_JS/index.js"></script>
</body>

</html>

 

 

- index.js

const addIndexEvent = () => {
    const buttonEvent = document.getElementById("submit");
    buttonEvent.addEventListener("click", submitServer);
};

const submitServer = async (event) => {
    event.preventDefault();
    const COMMON_URL = 'http://localhost:8080';

    const param = {
        'id': document.getElementById('id').value,
        'pw': document.getElementById('pw').value
    };

    const option = {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(param)
    };

    try {
        const res = await fetch(`${COMMON_URL}/save`, option);
        if (!res.ok) {
            throw new Error('Network response was not ok');
        }
        const data = await res.json();  
        console.log(data); 
    } catch (error) {
        console.error('Error:', error);
    }
};

addIndexEvent();

async와 await를 사용하는 주된 이유는 비동기 코드를 작성할 때, 보다 간결하고 직관적으로 코드를 작성하고 관리할 수 있기 때문입니다.

 

 

서버(SpringBoot)

@Getter
public class SaveDTO {

    private String id;

    private String pw;

}
@Slf4j
@RestController
public class FetchApiTest {

    @PostMapping("/save")
    public SaveDTO save(@RequestBody SaveDTO saveDTO){
        log.info("name={}, age={}", saveDTO.getId(), saveDTO.getPw());

        return saveDTO;
    }

}

 

 

Fetch API 데이터 전송 + File

JSON 형식으로 같이 보내기가 어렵다. MultipartFile 형태는 클라이언트에서 요청보낸 JSON 타입을 역직렬화(deserialize) 할 수 없기 때문이다. 파일형태의 요청은 json이 아닌 multipart/form-data 형식으로 보내야 한다.

 

서버 측 코드에서 MultipartFile를 사용하여 JSON 데이터를 직접 받으려고 시도: JSON으로 파일 데이터를 받으려고 하면 발생하는 오류입니다. MultipartFile는 일반적으로 multipart/form-data 형식으로 전송된 파일 데이터를 처리하기 위해 사용됩니다. JSON을 사용하여 파일 데이터를 직접 전송하는 것은 지원되지 않습니다.

 

 

- 클라이언트

<!DOCTYPE html>
<html lang=ko">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-compatible" content="IE=dege">
    <title>My first Web Page</title>
</head>

<body>
    <h1>fetch API 비동기 처리</h1>
    <form>
        <label for="id">아이디 :</label>
        <input type="text" id="id"> <br>
        <label for="pw">비밀번호 :</label>
        <input type="password" id="pw"><br>

        <input type="file" id="file">
        <button type="submit" id="submit">전송</button>
    </form>
    <script src="/HTML_CSS_JS/index.js"></script>
</body>

</html>
const addIndexEvent = () => {
    const buttonEvent = document.getElementById("submit");
    buttonEvent.addEventListener("click", submitServer);
};

const submitServer = async (event) => {
    event.preventDefault();
    const COMMON_URL = 'http://localhost:8080';

    const formData = new FormData();

    formData.append('id', document.getElementById('id').value);
    formData.append('pw', document.getElementById('pw').value);
    formData.append('image', document.getElementById('file').files[0]);

    const option = {
        method: 'POST',
        headers: {
        },
        body: formData
    };

    try {
        const res = await fetch(`${COMMON_URL}/save`, option);
        if (!res.ok) {
            throw new Error('Network response was not ok');
        }
        const data = await res.json();  
        console.log(data); 
    } catch (error) {
        console.error('Error:', error);
    }
};

addIndexEvent();

 

- 서버

@Getter
public class SaveDTO {
    private String id;
    private String pw;
    private String fileName;
    private String contentType;

    public SaveDTO(String id, String pw, MultipartFile image) {
        this.id = id;
        this.pw = pw;
        this.fileName = image.getOriginalFilename();
        this.contentType = image.getContentType();
    }

}
@Slf4j
@RestController
public class FetchApiTest {

    @PostMapping("/save")
    public SaveDTO save(
            @RequestParam String id,
            @RequestParam String pw,
            @RequestParam MultipartFile image
    ){
        log.info("name={}, age={}, image={}", id, pw, image.getOriginalFilename());

        return new SaveDTO(id, pw, image);
    }

}

 

 

❗️formData에 파일을 포함하여 서버로 전송하게 될때는 Content-Type을 정의하면 안됩니다.

**formData**에 파일을 포함하여 서버로 전송할 때 **Content-Type**을 수동으로 설정하지 않는 이유는 브라우저가 자동으로 필요한 정보를 추가하기 때문입니다. 이 자동화된 과정은 Content-Type 헤더에 multipart/form-data 뿐만 아니라, 필요한 boundary 값을 포함합니다. 이 boundary 값은 각 파트를 구분하는 데 사용되며, 데이터 스트림 내의 각 부분을 분리하는 역할을 합니다.

 

**Content-Type**을 수동으로 설정할 때 **multipart/form-data**를 포함하지만 적절한 boundary 값을 포함하지 않는 경우, 요청이 제대로 작동하지 않을 수 있습니다. 이는 boundary 값이 각 파트를 구분하는 데 필수적이기 때문입니다.

 

**boundary**는 멀티파트 메시지에서 각 파트를 분리하는 데 사용되는 구분자입니다. 이 구분자는 메시지의 본문에 포함되는 각 파트의 시작과 끝을 명시합니다. 서버가 요청을 받았을 때, 이 **boundary**를 사용하여 본문의 각 파트를 올바르게 인식하고 파싱합니다.

 

멀티파트 데이터 전송에서는 **boundary**를 사용하여 메시지의 각 파트를 구분합니다. 이 **boundary**는 Content-Type 헤더 내의 multipart/form-data 타입과 함께 사용되어, 데이터 스트림 내에서 각 파트의 시작과 끝을 명확하게 식별할 수 있도록 합니다.

 

JSON 형식은 **boundary**가 필요 없는 데이터 형식입니다. boundary는 멀티파트 메시지(multipart/form-data)에서 사용되며, 주로 파일 업로드와 같이 복수의 데이터 스트림이나 대량의 바이너리 데이터를 전송할 때 필요합니다. 각 데이터 스트림을 구분하고, 데이터의 시작과 끝을 명확하게 나타내는 데 사용됩니다.

'SpringBoot' 카테고리의 다른 글

[Spring Boot] @Configuration vs @Component  (0) 2024.04.30
[Spring Boot] Validation Annotation (DTO)  (0) 2024.04.29
[Spring Boot] CORS 설정하기  (0) 2024.04.28